summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:35:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 17:35:35 +0000
commit331441e10889478d5a88eeda5d74057904e5e384 (patch)
tree0e63feed70b3e3fa3bf3096bb7985d4a80425cb3
parentInitial commit. (diff)
downloadgolang-golang-x-exp-upstream.tar.xz
golang-golang-x-exp-upstream.zip
Adding upstream version 0.0~git20231006.7918f67.upstream/0.0_git20231006.7918f67upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.gitattributes10
-rw-r--r--.gitignore2
-rw-r--r--CONTRIBUTING.md26
-rw-r--r--LICENSE27
-rw-r--r--PATENTS22
-rw-r--r--README.md22
-rw-r--r--apidiff/README.md730
-rw-r--r--apidiff/apidiff.go324
-rw-r--r--apidiff/apidiff_test.go339
-rw-r--r--apidiff/compatibility.go364
-rw-r--r--apidiff/correspondence.go303
-rw-r--r--apidiff/messageset.go111
-rw-r--r--apidiff/report.go71
-rw-r--r--apidiff/testdata/README.md12
-rw-r--r--apidiff/testdata/aliases.go29
-rw-r--r--apidiff/testdata/basics.go110
-rw-r--r--apidiff/testdata/constants.go53
-rw-r--r--apidiff/testdata/exported_fields/ef.go38
-rw-r--r--apidiff/testdata/functions.go71
-rw-r--r--apidiff/testdata/generics.go84
-rw-r--r--apidiff/testdata/method_sets.go118
-rw-r--r--apidiff/testdata/multi_levels.go31
-rw-r--r--apidiff/testdata/order.go37
-rw-r--r--apidiff/testdata/other_packages.go44
-rw-r--r--apidiff/testdata/splitting.go77
-rw-r--r--apidiff/testdata/structs.go143
-rw-r--r--apidiff/testdata/types.go253
-rw-r--r--apidiff/testdata/variables.go72
-rw-r--r--apidiff/testdata/whole.go49
-rw-r--r--cmd/apidiff/main.go293
-rw-r--r--cmd/apidiff/main_test.go144
-rw-r--r--cmd/gorelease/errors.go81
-rw-r--r--cmd/gorelease/gorelease.go1517
-rw-r--r--cmd/gorelease/gorelease_test.go503
-rw-r--r--cmd/gorelease/path.go87
-rw-r--r--cmd/gorelease/path_test.go227
-rw-r--r--cmd/gorelease/proxy_test.go206
-rw-r--r--cmd/gorelease/report.go463
-rw-r--r--cmd/gorelease/testdata/README.md95
-rw-r--r--cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test24
-rw-r--r--cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test23
-rw-r--r--cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test23
-rw-r--r--cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test18
-rw-r--r--cmd/gorelease/testdata/basic/README.txt11
-rw-r--r--cmd/gorelease/testdata/basic/v0_compatible_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v0_compatible_suggest_git.test16
-rw-r--r--cmd/gorelease/testdata/basic/v0_compatible_suggest_hg.test16
-rw-r--r--cmd/gorelease/testdata/basic/v0_compatible_verify.test16
-rw-r--r--cmd/gorelease/testdata/basic/v0_incompatible_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v0_incompatible_verify.test16
-rw-r--r--cmd/gorelease/testdata/basic/v0_nobase_suggest.test6
-rw-r--r--cmd/gorelease/testdata/basic/v0_patch_suggest.test7
-rw-r--r--cmd/gorelease/testdata/basic/v0_patch_verify.test8
-rw-r--r--cmd/gorelease/testdata/basic/v0_pre_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v0_v1_incompatible_verify.test15
-rw-r--r--cmd/gorelease/testdata/basic/v1_autobase_suggest.test6
-rw-r--r--cmd/gorelease/testdata/basic/v1_autobase_verify.test7
-rw-r--r--cmd/gorelease/testdata/basic/v1_compatible_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v1_compatible_verify.test16
-rw-r--r--cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test11
-rw-r--r--cmd/gorelease/testdata/basic/v1_fork_base_verify.test15
-rw-r--r--cmd/gorelease/testdata/basic/v1_incompatible_suggest.test16
-rw-r--r--cmd/gorelease/testdata/basic/v1_incompatible_verify.test18
-rw-r--r--cmd/gorelease/testdata/basic/v1_patch_suggest.test7
-rw-r--r--cmd/gorelease/testdata/basic/v1_patch_verify.test8
-rw-r--r--cmd/gorelease/testdata/basic/v1_pre_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v1_querybase_higher.test7
-rw-r--r--cmd/gorelease/testdata/basic/v1_querybase_suggest.test8
-rw-r--r--cmd/gorelease/testdata/basic/v1_querybase_verify.test8
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test17
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_base_modpath_suggest.test17
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test17
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test16
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_base_version_suggest.test8
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_base_version_verify.test7
-rw-r--r--cmd/gorelease/testdata/basic/v1_v2_incompatible_verify.test18
-rw-r--r--cmd/gorelease/testdata/basic/v2_compatible_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v2_compatible_verify.test16
-rw-r--r--cmd/gorelease/testdata/basic/v2_incompatible_suggest.test16
-rw-r--r--cmd/gorelease/testdata/basic/v2_incompatible_verify.test18
-rw-r--r--cmd/gorelease/testdata/basic/v2_nobase_suggest.test6
-rw-r--r--cmd/gorelease/testdata/basic/v2_patch_suggest.test7
-rw-r--r--cmd/gorelease/testdata/basic/v2_patch_verify.test8
-rw-r--r--cmd/gorelease/testdata/basic/v2_pre_suggest.test15
-rw-r--r--cmd/gorelease/testdata/basic/v3_autobase_suggest.test9
-rw-r--r--cmd/gorelease/testdata/basic/v3_autobase_verify_error.test6
-rw-r--r--cmd/gorelease/testdata/basic/v3_autobase_verify_first.test7
-rw-r--r--cmd/gorelease/testdata/cgo/README.txt7
-rw-r--r--cmd/gorelease/testdata/cgo/cgo.test16
-rw-r--r--cmd/gorelease/testdata/cycle/README.md1
-rw-r--r--cmd/gorelease/testdata/cycle/cycle_suggest.test6
-rw-r--r--cmd/gorelease/testdata/cycle/cycle_suggest_error.test8
-rw-r--r--cmd/gorelease/testdata/cycle/cycle_verify.test9
-rw-r--r--cmd/gorelease/testdata/empty/README.txt2
-rw-r--r--cmd/gorelease/testdata/empty/empty.test8
-rw-r--r--cmd/gorelease/testdata/errors/README.txt2
-rw-r--r--cmd/gorelease/testdata/errors/bad_filenames.test17
-rw-r--r--cmd/gorelease/testdata/errors/bad_release.test8
-rw-r--r--cmd/gorelease/testdata/errors/base_higher.test8
-rw-r--r--cmd/gorelease/testdata/errors/base_modpath_none.test7
-rw-r--r--cmd/gorelease/testdata/errors/errors.test38
-rw-r--r--cmd/gorelease/testdata/errors/same_base_release.test8
-rw-r--r--cmd/gorelease/testdata/errors/upgrade_base.test6
-rw-r--r--cmd/gorelease/testdata/first/README.txt1
-rw-r--r--cmd/gorelease/testdata/first/v0_0_0.test12
-rw-r--r--cmd/gorelease/testdata/first/v0_0_1.test12
-rw-r--r--cmd/gorelease/testdata/first/v0_1_0-alpha.1.test12
-rw-r--r--cmd/gorelease/testdata/first/v0_1_0.test12
-rw-r--r--cmd/gorelease/testdata/first/v0_err.test19
-rw-r--r--cmd/gorelease/testdata/first/v1_0_0.test12
-rw-r--r--cmd/gorelease/testdata/first/v1_2_3_explicit_none_base.test13
-rw-r--r--cmd/gorelease/testdata/first/v2_err.test19
-rw-r--r--cmd/gorelease/testdata/first/v2_moderr.test15
-rw-r--r--cmd/gorelease/testdata/fix/README.txt7
-rw-r--r--cmd/gorelease/testdata/fix/compatible_other_suggest.test19
-rw-r--r--cmd/gorelease/testdata/fix/compatible_other_verify.test19
-rw-r--r--cmd/gorelease/testdata/fix/compatible_other_verify_err.test21
-rw-r--r--cmd/gorelease/testdata/fix/compatible_same_suggest.test17
-rw-r--r--cmd/gorelease/testdata/fix/compatible_same_verify.test17
-rw-r--r--cmd/gorelease/testdata/fix/incompatible_other_suggest.test19
-rw-r--r--cmd/gorelease/testdata/fix/incompatible_other_verify.test20
-rw-r--r--cmd/gorelease/testdata/fix/incompatible_same_suggest.test16
-rw-r--r--cmd/gorelease/testdata/fix/incompatible_same_verify.test16
-rw-r--r--cmd/gorelease/testdata/fix/patch_suggest.test15
-rw-r--r--cmd/gorelease/testdata/fix/patch_verify.test15
-rw-r--r--cmd/gorelease/testdata/generics/changed_param.test25
-rw-r--r--cmd/gorelease/testdata/generics/changed_return.test19
-rw-r--r--cmd/gorelease/testdata/generics/unchanged.test15
-rw-r--r--cmd/gorelease/testdata/internalcompat/README.txt31
-rw-r--r--cmd/gorelease/testdata/internalcompat/internalcompat.test8
-rw-r--r--cmd/gorelease/testdata/main/README.txt3
-rw-r--r--cmd/gorelease/testdata/main/add.test21
-rw-r--r--cmd/gorelease/testdata/main/change.test15
-rw-r--r--cmd/gorelease/testdata/main/remove.test13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v0.0.1.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v0.1.0.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v0.1.1-pre.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v0.1.1.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v0.1.2.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v1.0.1.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v1.1.0.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v1.1.1-pre.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v1.1.1.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v1.1.2.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v2_v2.0.1.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.0.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1-pre.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.2.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_v3_v3.0.0-ignore.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt2
-rw-r--r--cmd/gorelease/testdata/mod/example.com_basicfork_v1.1.2.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt15
-rw-r--r--cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_cycle_v2_v2.0.0.txt19
-rw-r--r--cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt10
-rw-r--r--cmd/gorelease/testdata/mod/example.com_cycledep_v2_v2.0.0.txt12
-rw-r--r--cmd/gorelease/testdata/mod/example.com_empty_v0.0.1.txt4
-rw-r--r--cmd/gorelease/testdata/mod/example.com_empty_v0.0.2.txt4
-rw-r--r--cmd/gorelease/testdata/mod/example.com_errors_v0.1.0.txt26
-rw-r--r--cmd/gorelease/testdata/mod/example.com_errors_v0.2.0.txt18
-rw-r--r--cmd/gorelease/testdata/mod/example.com_fix_v1.0.0.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_fix_v1.0.1-patch.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-other.txt14
-rw-r--r--cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-same.txt14
-rw-r--r--cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-other.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-same.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_generics_v0.0.1.txt10
-rw-r--r--cmd/gorelease/testdata/mod/example.com_internalcompat_a_v1.0.0.txt16
-rw-r--r--cmd/gorelease/testdata/mod/example.com_internalcompat_b_v1.0.0.txt16
-rw-r--r--cmd/gorelease/testdata/mod/example.com_issue37756_v1.0.0.txt18
-rw-r--r--cmd/gorelease/testdata/mod/example.com_issue37756_v1.1.0.txt21
-rw-r--r--cmd/gorelease/testdata/mod/example.com_main_v0.0.1.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_nomod_v0.0.1.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_nomod_v0.0.2.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_prerelease_v0.0.0-20300101000000-000000000000.txt4
-rw-r--r--cmd/gorelease/testdata/mod/example.com_private_v1.0.0.txt17
-rw-r--r--cmd/gorelease/testdata/mod/example.com_private_v1.0.1.txt15
-rw-r--r--cmd/gorelease/testdata/mod/example.com_private_v1.0.2-break.txt17
-rw-r--r--cmd/gorelease/testdata/mod/example.com_require_v0.0.1.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_require_v0.1.0.txt14
-rw-r--r--cmd/gorelease/testdata/mod/example.com_require_v0.1.1.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt13
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt11
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt12
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt10
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt12
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt11
-rw-r--r--cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt15
-rw-r--r--cmd/gorelease/testdata/mod/example.com_sub_nest_v1.0.0.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_sub_nest_v2_v2.0.0.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_sub_v2_v2.0.0.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_tidy_a_v0.1.0.txt10
-rw-r--r--cmd/gorelease/testdata/mod/example.com_tidy_b_v0.1.0.txt6
-rw-r--r--cmd/gorelease/testdata/mod/example.com_tidy_b_v0.2.0.txt8
-rw-r--r--cmd/gorelease/testdata/mod/example.com_tidy_v0.0.1.txt6
-rw-r--r--cmd/gorelease/testdata/nomod/README.txt2
-rw-r--r--cmd/gorelease/testdata/nomod/nomod.test8
-rw-r--r--cmd/gorelease/testdata/patherrors/README.txt1
-rw-r--r--cmd/gorelease/testdata/patherrors/abspath.test8
-rw-r--r--cmd/gorelease/testdata/patherrors/badmajor.test10
-rw-r--r--cmd/gorelease/testdata/patherrors/dup_roots_branch.test20
-rw-r--r--cmd/gorelease/testdata/patherrors/dup_roots_dir.test20
-rw-r--r--cmd/gorelease/testdata/patherrors/dup_roots_ok.test14
-rw-r--r--cmd/gorelease/testdata/patherrors/gopkginsub.test16
-rw-r--r--cmd/gorelease/testdata/patherrors/pathsub.test17
-rw-r--r--cmd/gorelease/testdata/patherrors/pathsubv2.test17
-rw-r--r--cmd/gorelease/testdata/prerelease/README.txt1
-rw-r--r--cmd/gorelease/testdata/prerelease/prerelease_2.test16
-rw-r--r--cmd/gorelease/testdata/prerelease/prerelease_3.test16
-rw-r--r--cmd/gorelease/testdata/prerelease/prerelease_beta2.test10
-rw-r--r--cmd/gorelease/testdata/prerelease/prerelease_build_metadata.test11
-rw-r--r--cmd/gorelease/testdata/prerelease/prerelease_wordsonly.test10
-rw-r--r--cmd/gorelease/testdata/private/README.txt3
-rw-r--r--cmd/gorelease/testdata/private/break.test12
-rw-r--r--cmd/gorelease/testdata/private/unreported.test7
-rw-r--r--cmd/gorelease/testdata/regress/README.txt1
-rw-r--r--cmd/gorelease/testdata/regress/issue37756.test21
-rw-r--r--cmd/gorelease/testdata/require/README.txt2
-rw-r--r--cmd/gorelease/testdata/require/add_requirement.test19
-rw-r--r--cmd/gorelease/testdata/require/decrement_go_version.test13
-rw-r--r--cmd/gorelease/testdata/require/decrement_requirement_minor.test18
-rw-r--r--cmd/gorelease/testdata/require/increment_go_version.test13
-rw-r--r--cmd/gorelease/testdata/require/increment_requirement_minor.test19
-rw-r--r--cmd/gorelease/testdata/require/increment_requirement_patch.test19
-rw-r--r--cmd/gorelease/testdata/require/remove_requirements.test13
-rw-r--r--cmd/gorelease/testdata/retract/retract_verify_direct_dep.test10
-rw-r--r--cmd/gorelease/testdata/retract/retract_verify_long_msg.test22
-rw-r--r--cmd/gorelease/testdata/retract/retract_verify_no_msg.test22
-rw-r--r--cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test12
-rw-r--r--cmd/gorelease/testdata/sub/README.txt10
-rw-r--r--cmd/gorelease/testdata/sub/nest.test13
-rw-r--r--cmd/gorelease/testdata/sub/nest_v2.test13
-rw-r--r--cmd/gorelease/testdata/sub/nest_v2_dir.test13
-rw-r--r--cmd/gorelease/testdata/sub/v2_dir.test13
-rw-r--r--cmd/gorelease/testdata/sub/v2_root.test12
-rw-r--r--cmd/gorelease/testdata/tidy/README.txt5
-rw-r--r--cmd/gorelease/testdata/tidy/empty_sum.test20
-rw-r--r--cmd/gorelease/testdata/tidy/misleading_req.test31
-rw-r--r--cmd/gorelease/testdata/tidy/missing_go.test13
-rw-r--r--cmd/gorelease/testdata/tidy/missing_req_basic.test19
-rw-r--r--cmd/gorelease/testdata/tidy/missing_req_nested.test25
-rw-r--r--cmd/gorelease/testdata/tidy/missing_req_submodule.test20
-rw-r--r--cmd/gorelease/testdata/tidy/missing_req_twice_nested.test25
-rw-r--r--cmd/gorelease/testdata/tidy/no_sum.test19
-rw-r--r--cmd/macos-roots-test/main.go121
-rw-r--r--cmd/macos-roots-test/root_cgo_darwin.go290
-rw-r--r--cmd/macos-roots-test/root_darwin.go173
-rw-r--r--cmd/macos-roots-test/root_nocgo_darwin.go16
-rw-r--r--cmd/modgraphviz/main.go166
-rw-r--r--cmd/modgraphviz/main_test.go116
-rw-r--r--cmd/txtar/txtar.go221
-rw-r--r--cmd/txtar/txtar_test.go195
-rw-r--r--codereview.cfg1
-rw-r--r--constraints/constraints.go50
-rw-r--r--constraints/constraints_test.go161
-rw-r--r--devtools/checklib.sh73
-rw-r--r--ebnf/ebnf.go267
-rw-r--r--ebnf/ebnf_test.go73
-rw-r--r--ebnf/parser.go189
-rw-r--r--ebnflint/doc.go21
-rw-r--r--ebnflint/ebnflint.go122
-rw-r--r--ebnflint/ebnflint_test.go22
-rw-r--r--errors/errors.go36
-rw-r--r--errors/errors_test.go54
-rw-r--r--errors/example_test.go50
-rw-r--r--errors/fmt/adaptor.go31
-rw-r--r--errors/fmt/doc.go380
-rw-r--r--errors/fmt/errors.go245
-rw-r--r--errors/fmt/errors_test.go535
-rw-r--r--errors/fmt/example_test.go29
-rw-r--r--errors/fmt/export_test.go8
-rw-r--r--errors/fmt/fmt_test.go1850
-rw-r--r--errors/fmt/format.go542
-rw-r--r--errors/fmt/format_example_test.go47
-rw-r--r--errors/fmt/print.go1131
-rw-r--r--errors/fmt/scan.go1201
-rw-r--r--errors/fmt/scan_test.go1292
-rw-r--r--errors/fmt/stringer_test.go61
-rw-r--r--errors/format.go34
-rw-r--r--errors/frame.go56
-rw-r--r--errors/go.mod1
-rw-r--r--errors/internal/internal.go8
-rw-r--r--errors/stack_test.go61
-rw-r--r--errors/wrap.go98
-rw-r--r--errors/wrap_test.go203
-rw-r--r--event/adapter/gokit/bench_test.go71
-rw-r--r--event/adapter/gokit/gokit.go53
-rw-r--r--event/adapter/gokit/gokit_test.go36
-rw-r--r--event/adapter/logfmt/logfmt.go258
-rw-r--r--event/adapter/logfmt/logfmt_test.go198
-rw-r--r--event/adapter/logr/logr.go104
-rw-r--r--event/adapter/logr/logr_test.go38
-rw-r--r--event/adapter/logrus/bench_test.go87
-rw-r--r--event/adapter/logrus/logrus.go84
-rw-r--r--event/adapter/logrus/logrus_test.go44
-rw-r--r--event/adapter/stdlib/bench_test.go126
-rw-r--r--event/adapter/zap/bench_test.go115
-rw-r--r--event/adapter/zap/zap.go176
-rw-r--r--event/adapter/zap/zap_test.go40
-rw-r--r--event/adapter/zerolog/bench_test.go78
-rw-r--r--event/alloc_test.go35
-rw-r--r--event/bench_test.go134
-rw-r--r--event/common.go159
-rw-r--r--event/disabled.go40
-rw-r--r--event/doc.go18
-rw-r--r--event/event.go138
-rw-r--r--event/event_test.go347
-rw-r--r--event/eventtest/bench.go151
-rw-r--r--event/eventtest/capture.go31
-rw-r--r--event/eventtest/eventtest.go65
-rw-r--r--event/export.go116
-rw-r--r--event/go.mod27
-rw-r--r--event/go.sum104
-rw-r--r--event/keys/keys.go198
-rw-r--r--event/label.go272
-rw-r--r--event/label_test.go152
-rw-r--r--event/metric.go158
-rw-r--r--event/otel/metric.go131
-rw-r--r--event/otel/metric_test.go75
-rw-r--r--event/otel/trace.go58
-rw-r--r--event/otel/trace_test.go159
-rw-r--r--event/severity/severity.go154
-rw-r--r--event/severity/severity_test.go44
-rw-r--r--event/source.go210
-rw-r--r--event/source_test.go86
-rw-r--r--go.mod11
-rw-r--r--go.sum9
-rw-r--r--inotify/README.txt3
-rw-r--r--io/i2c/devfs.go79
-rw-r--r--io/i2c/devfs_nonlinux.go24
-rw-r--r--io/i2c/driver/driver.go24
-rw-r--r--io/i2c/example/displayip/main.go89
-rw-r--r--io/i2c/example_test.go24
-rw-r--r--io/i2c/i2c.go75
-rw-r--r--io/i2c/i2c_test.go31
-rw-r--r--io/spi/devfs.go176
-rw-r--r--io/spi/devfs_nonlinux.go39
-rw-r--r--io/spi/driver/driver.go50
-rw-r--r--io/spi/example_test.go38
-rw-r--r--io/spi/spi.go107
-rw-r--r--jsonrpc2/conn.go476
-rw-r--r--jsonrpc2/defs.go36
-rw-r--r--jsonrpc2/frame.go179
-rw-r--r--jsonrpc2/go.mod10
-rw-r--r--jsonrpc2/go.sum7
-rw-r--r--jsonrpc2/internal/stack/gostacks/gostacks.go23
-rw-r--r--jsonrpc2/internal/stack/parse.go175
-rw-r--r--jsonrpc2/internal/stack/process.go112
-rw-r--r--jsonrpc2/internal/stack/stack.go170
-rw-r--r--jsonrpc2/internal/stack/stack_test.go193
-rw-r--r--jsonrpc2/internal/stack/stacktest/stacktest.go50
-rw-r--r--jsonrpc2/jsonrpc2.go99
-rw-r--r--jsonrpc2/jsonrpc2_test.go409
-rw-r--r--jsonrpc2/messages.go181
-rw-r--r--jsonrpc2/net.go129
-rw-r--r--jsonrpc2/serve.go283
-rw-r--r--jsonrpc2/serve_test.go144
-rw-r--r--jsonrpc2/wire.go74
-rw-r--r--jsonrpc2/wire_test.go118
-rw-r--r--maps/maps.go94
-rw-r--r--maps/maps_test.go181
-rw-r--r--mmap/manual_test_program.go60
-rw-r--r--mmap/mmap_other.go75
-rw-r--r--mmap/mmap_test.go34
-rw-r--r--mmap/mmap_unix.go126
-rw-r--r--mmap/mmap_windows.go133
-rw-r--r--rand/arith128_test.go126
-rw-r--r--rand/example_test.go163
-rw-r--r--rand/exp.go221
-rw-r--r--rand/modulo_test.go50
-rw-r--r--rand/normal.go156
-rw-r--r--rand/race_test.go48
-rw-r--r--rand/rand.go372
-rw-r--r--rand/rand_test.go617
-rw-r--r--rand/regress_test.go490
-rw-r--r--rand/rng.go91
-rw-r--r--rand/zipf.go77
-rw-r--r--shiny/driver/driver.go25
-rw-r--r--shiny/driver/driver_darwin.go16
-rw-r--r--shiny/driver/driver_fallback.go18
-rw-r--r--shiny/driver/driver_windows.go14
-rw-r--r--shiny/driver/driver_x11.go16
-rw-r--r--shiny/driver/gldriver/buffer.go32
-rw-r--r--shiny/driver/gldriver/cocoa.go670
-rw-r--r--shiny/driver/gldriver/cocoa.m332
-rw-r--r--shiny/driver/gldriver/context.go37
-rwxr-xr-xshiny/driver/gldriver/egl.go106
-rw-r--r--shiny/driver/gldriver/gldriver.go133
-rw-r--r--shiny/driver/gldriver/other.go29
-rw-r--r--shiny/driver/gldriver/screen.go149
-rw-r--r--shiny/driver/gldriver/texture.go160
-rwxr-xr-xshiny/driver/gldriver/win32.go357
-rw-r--r--shiny/driver/gldriver/window.go389
-rw-r--r--shiny/driver/gldriver/x11.c330
-rw-r--r--shiny/driver/gldriver/x11.go318
-rw-r--r--shiny/driver/internal/drawer/drawer.go34
-rw-r--r--shiny/driver/internal/errscreen/errscreen.go25
-rw-r--r--shiny/driver/internal/event/event.go68
-rw-r--r--shiny/driver/internal/lifecycler/lifecycler.go80
-rw-r--r--shiny/driver/internal/swizzle/swizzle_amd64.go17
-rw-r--r--shiny/driver/internal/swizzle/swizzle_amd64.s76
-rw-r--r--shiny/driver/internal/swizzle/swizzle_common.go31
-rw-r--r--shiny/driver/internal/swizzle/swizzle_other.go15
-rw-r--r--shiny/driver/internal/swizzle/swizzle_test.go86
-rw-r--r--shiny/driver/internal/win32/key.go350
-rw-r--r--shiny/driver/internal/win32/syscall.go7
-rw-r--r--shiny/driver/internal/win32/syscall_windows.go186
-rw-r--r--shiny/driver/internal/win32/win32.go519
-rw-r--r--shiny/driver/internal/win32/zsyscall_windows.go293
-rw-r--r--shiny/driver/internal/x11key/gen.go73
-rw-r--r--shiny/driver/internal/x11key/table.go1577
-rw-r--r--shiny/driver/internal/x11key/x11key.go323
-rw-r--r--shiny/driver/mtldriver/buffer.go19
-rw-r--r--shiny/driver/mtldriver/internal/appkit/appkit.go68
-rw-r--r--shiny/driver/mtldriver/internal/appkit/appkit.h10
-rw-r--r--shiny/driver/mtldriver/internal/appkit/appkit.m20
-rw-r--r--shiny/driver/mtldriver/internal/coreanim/coreanim.go140
-rw-r--r--shiny/driver/mtldriver/internal/coreanim/coreanim.h20
-rw-r--r--shiny/driver/mtldriver/internal/coreanim/coreanim.m58
-rw-r--r--shiny/driver/mtldriver/mtldriver.go252
-rw-r--r--shiny/driver/mtldriver/screen.go42
-rw-r--r--shiny/driver/mtldriver/texture.go32
-rw-r--r--shiny/driver/mtldriver/window.go119
-rw-r--r--shiny/driver/mtldriver_darwin.go16
-rw-r--r--shiny/driver/windriver/buffer.go101
-rw-r--r--shiny/driver/windriver/doc.go44
-rw-r--r--shiny/driver/windriver/other.go26
-rw-r--r--shiny/driver/windriver/screen.go84
-rw-r--r--shiny/driver/windriver/syscall.go7
-rw-r--r--shiny/driver/windriver/syscall_windows.go167
-rw-r--r--shiny/driver/windriver/texture.go161
-rw-r--r--shiny/driver/windriver/window.go273
-rw-r--r--shiny/driver/windriver/windraw.go122
-rw-r--r--shiny/driver/windriver/windriver.go25
-rw-r--r--shiny/driver/windriver/zsyscall_windows.go214
-rw-r--r--shiny/driver/x11driver/buffer.go175
-rw-r--r--shiny/driver/x11driver/buffer_fallback.go124
-rw-r--r--shiny/driver/x11driver/screen.go690
-rw-r--r--shiny/driver/x11driver/shm_linux_ipc.go48
-rw-r--r--shiny/driver/x11driver/shm_openbsd_syscall.go43
-rw-r--r--shiny/driver/x11driver/shm_other.go22
-rw-r--r--shiny/driver/x11driver/shm_shmopen_syscall.go43
-rw-r--r--shiny/driver/x11driver/texture.go200
-rw-r--r--shiny/driver/x11driver/window.go170
-rw-r--r--shiny/driver/x11driver/x11driver.go64
-rw-r--r--shiny/example/basic/main.go187
-rw-r--r--shiny/example/basicgl/main.go232
-rw-r--r--shiny/example/fluid/main.go396
-rw-r--r--shiny/example/goban/asset/blackstone01.jpgbin0 -> 12955 bytes
-rw-r--r--shiny/example/goban/asset/blackstone02.jpgbin0 -> 12364 bytes
-rw-r--r--shiny/example/goban/asset/blackstone03.jpgbin0 -> 12334 bytes
-rw-r--r--shiny/example/goban/asset/blackstone04.jpgbin0 -> 12126 bytes
-rw-r--r--shiny/example/goban/asset/blackstone05.jpgbin0 -> 11438 bytes
-rw-r--r--shiny/example/goban/asset/blackstone06.jpgbin0 -> 12126 bytes
-rw-r--r--shiny/example/goban/asset/blackstone07.jpgbin0 -> 12088 bytes
-rw-r--r--shiny/example/goban/asset/blackstone08.jpgbin0 -> 12487 bytes
-rw-r--r--shiny/example/goban/asset/blackstone09.jpgbin0 -> 12494 bytes
-rw-r--r--shiny/example/goban/asset/blackstone10.jpgbin0 -> 11353 bytes
-rw-r--r--shiny/example/goban/asset/blackstone11.jpgbin0 -> 11663 bytes
-rw-r--r--shiny/example/goban/asset/blackstone12.jpgbin0 -> 12266 bytes
-rw-r--r--shiny/example/goban/asset/blackstone13.jpgbin0 -> 12438 bytes
-rw-r--r--shiny/example/goban/asset/goboard.jpgbin0 -> 473808 bytes
-rw-r--r--shiny/example/goban/asset/resize.go59
-rw-r--r--shiny/example/goban/asset/whitestone01.jpgbin0 -> 6902 bytes
-rw-r--r--shiny/example/goban/asset/whitestone02.jpgbin0 -> 6787 bytes
-rw-r--r--shiny/example/goban/asset/whitestone03.jpgbin0 -> 6501 bytes
-rw-r--r--shiny/example/goban/asset/whitestone04.jpgbin0 -> 6107 bytes
-rw-r--r--shiny/example/goban/asset/whitestone05.jpgbin0 -> 6464 bytes
-rw-r--r--shiny/example/goban/asset/whitestone06.jpgbin0 -> 6272 bytes
-rw-r--r--shiny/example/goban/asset/whitestone07.jpgbin0 -> 6573 bytes
-rw-r--r--shiny/example/goban/asset/whitestone08.jpgbin0 -> 5900 bytes
-rw-r--r--shiny/example/goban/asset/whitestone09.jpgbin0 -> 6562 bytes
-rw-r--r--shiny/example/goban/asset/whitestone10.jpgbin0 -> 6362 bytes
-rw-r--r--shiny/example/goban/asset/whitestone11.jpgbin0 -> 6219 bytes
-rw-r--r--shiny/example/goban/board.go312
-rw-r--r--shiny/example/goban/main.go113
-rw-r--r--shiny/example/goban/xy.go133
-rw-r--r--shiny/example/goban/xy_test.go79
-rw-r--r--shiny/example/icongallery/main.go1162
-rw-r--r--shiny/example/imageview/main.go71
-rw-r--r--shiny/example/layout/main.go104
-rw-r--r--shiny/example/textedit/main.go150
-rw-r--r--shiny/example/tile/main.go237
-rw-r--r--shiny/example/widgetgallery/main.go84
-rw-r--r--shiny/gesture/gesture.go326
-rw-r--r--shiny/go.mod12
-rw-r--r--shiny/go.sum34
-rw-r--r--shiny/iconvg/buffer.go343
-rw-r--r--shiny/iconvg/buffer_test.go332
-rw-r--r--shiny/iconvg/color.go180
-rw-r--r--shiny/iconvg/decode.go699
-rw-r--r--shiny/iconvg/decode_test.go340
-rw-r--r--shiny/iconvg/doc.go31
-rw-r--r--shiny/iconvg/encode.go605
-rw-r--r--shiny/iconvg/encode_test.go708
-rw-r--r--shiny/iconvg/example_test.go67
-rw-r--r--shiny/iconvg/iconvg.go163
-rw-r--r--shiny/iconvg/internal/gradient/gradient.go242
-rw-r--r--shiny/iconvg/rasterizer.go595
-rw-r--r--shiny/iconvg/testdata/README91
-rw-r--r--shiny/iconvg/testdata/action-info.hires.ivgbin0 -> 73 bytes
-rw-r--r--shiny/iconvg/testdata/action-info.hires.ivg.disassembly56
-rw-r--r--shiny/iconvg/testdata/action-info.hires.pngbin0 -> 3719 bytes
-rw-r--r--shiny/iconvg/testdata/action-info.lores.ivgbin0 -> 63 bytes
-rw-r--r--shiny/iconvg/testdata/action-info.lores.ivg.disassembly56
-rw-r--r--shiny/iconvg/testdata/action-info.lores.pngbin0 -> 3735 bytes
-rw-r--r--shiny/iconvg/testdata/action-info.svg1
-rw-r--r--shiny/iconvg/testdata/arcs.ivgbin0 -> 147 bytes
-rw-r--r--shiny/iconvg/testdata/arcs.ivg.disassembly129
-rw-r--r--shiny/iconvg/testdata/arcs.pngbin0 -> 5104 bytes
-rw-r--r--shiny/iconvg/testdata/blank.ivgbin0 -> 5 bytes
-rw-r--r--shiny/iconvg/testdata/blank.ivg.disassembly2
-rw-r--r--shiny/iconvg/testdata/blank.pngbin0 -> 820 bytes
-rw-r--r--shiny/iconvg/testdata/cowbell.ivgbin0 -> 1017 bytes
-rw-r--r--shiny/iconvg/testdata/cowbell.ivg.disassembly611
-rw-r--r--shiny/iconvg/testdata/cowbell.pngbin0 -> 18555 bytes
-rw-r--r--shiny/iconvg/testdata/cowbell.svg25
-rw-r--r--shiny/iconvg/testdata/elliptical.ivgbin0 -> 85 bytes
-rw-r--r--shiny/iconvg/testdata/elliptical.ivg.disassembly79
-rw-r--r--shiny/iconvg/testdata/elliptical.pngbin0 -> 15992 bytes
-rw-r--r--shiny/iconvg/testdata/favicon.ivgbin0 -> 1599 bytes
-rw-r--r--shiny/iconvg/testdata/favicon.ivg.disassembly994
-rw-r--r--shiny/iconvg/testdata/favicon.pink.pngbin0 -> 12506 bytes
-rw-r--r--shiny/iconvg/testdata/favicon.pngbin0 -> 12523 bytes
-rw-r--r--shiny/iconvg/testdata/favicon.svg16
-rw-r--r--shiny/iconvg/testdata/gradient.ivgbin0 -> 225 bytes
-rw-r--r--shiny/iconvg/testdata/gradient.ivg.disassembly186
-rw-r--r--shiny/iconvg/testdata/gradient.pngbin0 -> 10626 bytes
-rw-r--r--shiny/iconvg/testdata/lod-polygon.64.pngbin0 -> 662 bytes
-rw-r--r--shiny/iconvg/testdata/lod-polygon.ivgbin0 -> 68 bytes
-rw-r--r--shiny/iconvg/testdata/lod-polygon.ivg.disassembly53
-rw-r--r--shiny/iconvg/testdata/lod-polygon.pngbin0 -> 3533 bytes
-rw-r--r--shiny/iconvg/testdata/video-005.jpegbin0 -> 6783 bytes
-rw-r--r--shiny/iconvg/testdata/video-005.primitive.ivgbin0 -> 625 bytes
-rw-r--r--shiny/iconvg/testdata/video-005.primitive.ivg.disassembly380
-rw-r--r--shiny/iconvg/testdata/video-005.primitive.pngbin0 -> 24265 bytes
-rw-r--r--shiny/iconvg/testdata/video-005.primitive.svg35
-rw-r--r--shiny/iconvg/upgrade.go1100
-rw-r--r--shiny/iconvg/upgrade_test.go47
-rw-r--r--shiny/imageutil/imageutil.go101
-rw-r--r--shiny/imageutil/imageutil_test.go74
-rw-r--r--shiny/materialdesign/colornames/colornames.go11
-rw-r--r--shiny/materialdesign/colornames/colornames_test.go46
-rw-r--r--shiny/materialdesign/colornames/gen.go178
-rw-r--r--shiny/materialdesign/colornames/table.go784
-rw-r--r--shiny/materialdesign/icons/LICENSE202
-rw-r--r--shiny/materialdesign/icons/data.go10969
-rw-r--r--shiny/materialdesign/icons/data_test.go970
-rw-r--r--shiny/materialdesign/icons/gen.go592
-rw-r--r--shiny/materialdesign/icons/icons.go12
-rw-r--r--shiny/materialdesign/icons/icons_test.go64
-rw-r--r--shiny/screen/screen.go354
-rw-r--r--shiny/screen/screen_test.go53
-rw-r--r--shiny/text/caret.go795
-rw-r--r--shiny/text/example_test.go93
-rw-r--r--shiny/text/text.go788
-rw-r--r--shiny/text/text_test.go1381
-rw-r--r--shiny/unit/unit.go137
-rw-r--r--shiny/widget/flex/flex.go663
-rw-r--r--shiny/widget/flex/flex_test.go420
-rw-r--r--shiny/widget/flow.go207
-rw-r--r--shiny/widget/glwidget/glwidget.go108
-rw-r--r--shiny/widget/image.go62
-rw-r--r--shiny/widget/label.go71
-rw-r--r--shiny/widget/node/node.go511
-rw-r--r--shiny/widget/padder.go77
-rw-r--r--shiny/widget/sheet.go127
-rw-r--r--shiny/widget/sizer.go41
-rw-r--r--shiny/widget/space.go25
-rw-r--r--shiny/widget/text.go152
-rw-r--r--shiny/widget/theme/theme.go246
-rw-r--r--shiny/widget/theme/theme_test.go42
-rw-r--r--shiny/widget/uniform.go45
-rw-r--r--shiny/widget/widget.go154
-rw-r--r--shootout/README4
-rw-r--r--shootout/binary-tree-freelist.go132
-rw-r--r--shootout/binary-tree-freelist.txt8
-rw-r--r--shootout/binary-tree.c166
-rw-r--r--shootout/binary-tree.go95
-rw-r--r--shootout/binary-tree.txt8
-rw-r--r--shootout/chameneosredux.c332
-rw-r--r--shootout/chameneosredux.go183
-rw-r--r--shootout/chameneosredux.txt29
-rw-r--r--shootout/fannkuch-parallel.go227
-rw-r--r--shootout/fannkuch-parallel.txt31
-rw-r--r--shootout/fannkuch.c136
-rw-r--r--shootout/fannkuch.go125
-rw-r--r--shootout/fannkuch.txt31
-rw-r--r--shootout/fasta-1000.txt171
-rw-r--r--shootout/fasta.c221
-rw-r--r--shootout/fasta.go208
-rw-r--r--shootout/fasta.txt171
-rw-r--r--shootout/k-nucleotide-parallel.go160
-rw-r--r--shootout/k-nucleotide-parallel.txt27
-rw-r--r--shootout/k-nucleotide.c230
-rw-r--r--shootout/k-nucleotide.go143
-rw-r--r--shootout/k-nucleotide.txt27
-rw-r--r--shootout/mandelbrot.c93
-rw-r--r--shootout/mandelbrot.go98
-rw-r--r--shootout/mandelbrot.txtbin0 -> 5011 bytes
-rw-r--r--shootout/meteor-contest.c628
-rw-r--r--shootout/meteor-contest.go659
-rw-r--r--shootout/meteor-contest.txt24
-rw-r--r--shootout/nbody.c172
-rw-r--r--shootout/nbody.go180
-rw-r--r--shootout/nbody.txt2
-rw-r--r--shootout/pidigits.c125
-rw-r--r--shootout/pidigits.go138
-rw-r--r--shootout/pidigits.txt3
-rw-r--r--shootout/regex-dna-parallel.go127
-rw-r--r--shootout/regex-dna-parallel.txt13
-rw-r--r--shootout/regex-dna.c156
-rw-r--r--shootout/regex-dna.go109
-rw-r--r--shootout/regex-dna.txt13
-rw-r--r--shootout/reverse-complement.c102
-rw-r--r--shootout/reverse-complement.go108
-rw-r--r--shootout/reverse-complement.txt171
-rw-r--r--shootout/spectral-norm-parallel.go114
-rw-r--r--shootout/spectral-norm.c84
-rw-r--r--shootout/spectral-norm.go96
-rw-r--r--shootout/spectral-norm.txt1
-rw-r--r--shootout/threadring.c115
-rw-r--r--shootout/threadring.go74
-rw-r--r--shootout/threadring.txt1
-rw-r--r--shootout/timing.log1254
-rwxr-xr-xshootout/timing.sh252
-rw-r--r--slices/cmp.go44
-rw-r--r--slices/slices.go499
-rw-r--r--slices/slices_race_test.go9
-rw-r--r--slices/slices_test.go1054
-rw-r--r--slices/sort.go195
-rw-r--r--slices/sort_benchmark_test.go284
-rw-r--r--slices/sort_test.go441
-rw-r--r--slices/zsortanyfunc.go479
-rw-r--r--slices/zsortordered.go481
-rw-r--r--slog/attr.go102
-rw-r--r--slog/attr_test.go41
-rw-r--r--slog/benchmarks/Makefile25
-rw-r--r--slog/benchmarks/benchmarks.go60
-rw-r--r--slog/benchmarks/benchmarks_test.go147
-rw-r--r--slog/benchmarks/handlers.go147
-rw-r--r--slog/benchmarks/handlers_test.go43
-rw-r--r--slog/benchmarks/slog-nopc.bench206
-rw-r--r--slog/benchmarks/slog.bench206
-rw-r--r--slog/benchmarks/zap_benchmarks/go.mod15
-rw-r--r--slog/benchmarks/zap_benchmarks/go.sum18
-rw-r--r--slog/benchmarks/zap_benchmarks/out.bench96
-rw-r--r--slog/benchmarks/zap_benchmarks/zap_benchmarks_test.go138
-rw-r--r--slog/benchmarks/zap_benchmarks/zap_encoders.go150
-rw-r--r--slog/benchmarks/zap_benchmarks/zap_encoders_test.go60
-rw-r--r--slog/benchmarks/zerolog_benchmarks/go.mod16
-rw-r--r--slog/benchmarks/zerolog_benchmarks/go.sum14
-rw-r--r--slog/benchmarks/zerolog_benchmarks/out.bench26
-rw-r--r--slog/benchmarks/zerolog_benchmarks/zerolog_benchmarks_test.go55
-rw-r--r--slog/doc.go316
-rw-r--r--slog/example_custom_levels_test.go92
-rw-r--r--slog/example_level_handler_test.go74
-rw-r--r--slog/example_logvaluer_group_test.go35
-rw-r--r--slog/example_logvaluer_secret_test.go32
-rw-r--r--slog/example_test.go32
-rw-r--r--slog/example_wrap_test.go48
-rw-r--r--slog/handler.go559
-rw-r--r--slog/handler_test.go517
-rw-r--r--slog/internal/buffer/buffer.go84
-rw-r--r--slog/internal/buffer/buffer_test.go22
-rw-r--r--slog/internal/buffer/norace_test.go20
-rw-r--r--slog/internal/ignorepc.go9
-rw-r--r--slog/internal/testutil/testutil.go18
-rw-r--r--slog/json_handler.go336
-rw-r--r--slog/json_handler_test.go280
-rw-r--r--slog/level.go201
-rw-r--r--slog/level_test.go168
-rw-r--r--slog/logger.go343
-rw-r--r--slog/logger_test.go509
-rw-r--r--slog/noplog.bench36
-rw-r--r--slog/norace_test.go17
-rw-r--r--slog/race_test.go13
-rw-r--r--slog/record.go207
-rw-r--r--slog/record_test.go154
-rw-r--r--slog/slogtest/example_test.go40
-rw-r--r--slog/slogtest/slogtest.go303
-rw-r--r--slog/slogtest/slogtest_119.go27
-rw-r--r--slog/slogtest/slogtest_120.go11
-rw-r--r--slog/slogtest_test.go105
-rw-r--r--slog/text_handler.go161
-rw-r--r--slog/text_handler_test.go172
-rw-r--r--slog/value.go456
-rw-r--r--slog/value_119.go53
-rw-r--r--slog/value_120.go39
-rw-r--r--slog/value_access_benchmark_test.go215
-rw-r--r--slog/value_test.go241
-rw-r--r--sumdb/go.mod5
-rw-r--r--sumdb/go.sum3
-rw-r--r--sumdb/gosumcheck/main.go211
-rwxr-xr-xsumdb/gosumcheck/test.bash7
-rw-r--r--sumdb/gosumcheck/test.sum6
-rw-r--r--sumdb/internal/note/example_test.go128
-rw-r--r--sumdb/internal/note/note.go683
-rw-r--r--sumdb/internal/note/note_test.go473
-rw-r--r--sumdb/internal/sumweb/cache.go59
-rw-r--r--sumdb/internal/sumweb/client.go663
-rw-r--r--sumdb/internal/sumweb/client_test.go460
-rw-r--r--sumdb/internal/sumweb/encode.go167
-rw-r--r--sumdb/internal/sumweb/encode_test.go67
-rw-r--r--sumdb/internal/sumweb/server.go182
-rw-r--r--sumdb/internal/sumweb/test.go133
-rw-r--r--sumdb/internal/tkv/tkv.go57
-rw-r--r--sumdb/internal/tkv/tkvtest/mem.go116
-rw-r--r--sumdb/internal/tkv/tkvtest/mem_test.go14
-rw-r--r--sumdb/internal/tkv/tkvtest/test.go77
-rw-r--r--sumdb/internal/tlog/ct_test.go96
-rw-r--r--sumdb/internal/tlog/note.go135
-rw-r--r--sumdb/internal/tlog/note_test.go117
-rw-r--r--sumdb/internal/tlog/tile.go418
-rw-r--r--sumdb/internal/tlog/tlog.go600
-rw-r--r--sumdb/internal/tlog/tlog_test.go269
-rw-r--r--typeparams/common.go182
-rw-r--r--typeparams/common_test.go238
-rw-r--r--typeparams/copytermlist.go99
-rw-r--r--typeparams/example/README.md898
-rw-r--r--typeparams/example/findtypeparams/main.go156
-rw-r--r--typeparams/example/generic-go-types.md448
-rw-r--r--typeparams/example/genericmethods/main.go122
-rw-r--r--typeparams/example/implicit/main.go52
-rw-r--r--typeparams/example/instantiation/main.go158
-rw-r--r--typeparams/example/interfaces/main.go138
-rw-r--r--typeparams/example/methoddecls/main.go137
-rw-r--r--typeparams/example/predicates/main.go114
-rw-r--r--typeparams/example/typesets/main.go71
-rw-r--r--typeparams/go.mod3
-rw-r--r--typeparams/normalize.go200
-rw-r--r--typeparams/normalize_test.go104
-rw-r--r--typeparams/termlist.go172
-rw-r--r--typeparams/typeparams_go117.go201
-rw-r--r--typeparams/typeparams_go118.go147
-rw-r--r--typeparams/typeparams_test.go137
-rw-r--r--typeparams/typeterm.go169
-rw-r--r--utf8string/string.go203
-rw-r--r--utf8string/string_test.go123
741 files changed, 102476 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..d2f212e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+# Treat all files in this repo as binary, with no git magic updating
+# line endings. Windows users contributing to Go will need to use a
+# modern version of git and editors capable of LF line endings.
+#
+# We'll prevent accidental CRLF line endings from entering the repo
+# via the git-review gofmt checks.
+#
+# See golang.org/issue/9281
+
+* -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8339fd6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# Add no patterns to .hgignore except for files generated by the build.
+last-change
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d0485e8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+# Contributing to Go
+
+Go is an open source project.
+
+It is the work of hundreds of contributors. We appreciate your help!
+
+## Filing issues
+
+When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
+
+1. What version of Go are you using (`go version`)?
+2. What operating system and processor architecture are you using?
+3. What did you do?
+4. What did you expect to see?
+5. What did you see instead?
+
+General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
+The gophers there will answer or ask you to file an issue if you've tripped over a bug.
+
+## Contributing code
+
+Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
+before sending patches.
+
+Unless otherwise noted, the Go source files are distributed under
+the BSD-style license found in the LICENSE file.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6a66aea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. 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.
+ * Neither the name of Google Inc. 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
+OWNER 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.
diff --git a/PATENTS b/PATENTS
new file mode 100644
index 0000000..7330990
--- /dev/null
+++ b/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c5a95b1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+# exp
+
+[![PkgGoDev](https://pkg.go.dev/badge/golang.org/x/exp)](https://pkg.go.dev/golang.org/x/exp)
+
+This subrepository holds experimental and deprecated (in the `old`
+directory) packages.
+
+The idea for this subrepository originated as the `pkg/exp` directory
+of the main repository, but its presence there made it unavailable
+to users of the binary downloads of the Go installation. The
+subrepository has therefore been created to make it possible to `go
+get` these packages.
+
+**Warning:** Packages here are experimental and unreliable. Some may
+one day be promoted to the main repository or other subrepository,
+or they may be modified arbitrarily or even disappear altogether.
+
+In short, code in this subrepository is not subject to the Go 1
+compatibility promise. (No subrepo is, but the promise is even more
+likely to be violated by go.exp than the others.)
+
+Caveat emptor.
diff --git a/apidiff/README.md b/apidiff/README.md
new file mode 100644
index 0000000..4f3b22f
--- /dev/null
+++ b/apidiff/README.md
@@ -0,0 +1,730 @@
+# Checking Go API Compatibility
+
+The `apidiff` tool in this directory determines whether two examples of a
+package or module are compatible. The goal is to help the developer make an
+informed choice of semantic version after they have changed the code of their
+module.
+
+`apidiff` reports two kinds of changes: incompatible ones, which require
+incrementing the major part of the semantic version, and compatible ones, which
+require a minor version increment. If no API changes are reported but there are
+code changes that could affect client code, then the patch version should
+be incremented.
+
+`apidiff` may be used to display API differences between any two packages or
+modules, not just different versions of the same thing. It does this by ignoring
+the package import paths when directly comparing two packages, and
+by ignoring module paths when comparing two modules. That is to say, when
+comparing two modules, the package import paths **do** matter, but are compared
+_relative_ to their respective module root.
+
+## Compatibility Desiderata
+
+Any tool that checks compatibility can offer only an approximation. No tool can
+detect behavioral changes; and even if it could, whether a behavioral change is
+a breaking change or not depends on many factors, such as whether it closes a
+security hole or fixes a bug. Even a change that causes some code to fail to
+compile may not be considered a breaking change by the developers or their
+users. It may only affect code marked as experimental or unstable, for
+example, or the break may only manifest in unlikely cases.
+
+For a tool to be useful, its notion of compatibility must be relaxed enough to
+allow reasonable changes, like adding a field to a struct, but strict enough to
+catch significant breaking changes. A tool that is too lax will miss important
+incompatibilities, and users will stop trusting it; one that is too strict may
+generate so much noise that users will ignore it.
+
+To a first approximation, this tool reports a change as incompatible if it could
+cause client code to stop compiling. But `apidiff` ignores five ways in which
+code may fail to compile after a change. Three of them are mentioned in the
+[Go 1 Compatibility Guarantee](https://golang.org/doc/go1compat).
+
+### Unkeyed Struct Literals
+
+Code that uses an unkeyed struct literal would fail to compile if a field was
+added to the struct, making any such addition an incompatible change. An example:
+
+```
+// old
+type Point struct { X, Y int }
+
+// new
+type Point struct { X, Y, Z int }
+
+// client
+p := pkg.Point{1, 2} // fails in new because there are more fields than expressions
+```
+Here and below, we provide three snippets: the code in the old version of the
+package, the code in the new version, and the code written in a client of the package,
+which refers to it by the name `pkg`. The client code compiles against the old
+code but not the new.
+
+### Embedding and Shadowing
+
+Adding an exported field to a struct can break code that embeds that struct,
+because the newly added field may conflict with an identically named field
+at the same struct depth. A selector referring to the latter would become
+ambiguous and thus erroneous.
+
+
+```
+// old
+type Point struct { X, Y int }
+
+// new
+type Point struct { X, Y, Z int }
+
+// client
+type z struct { Z int }
+
+var v struct {
+ pkg.Point
+ z
+}
+
+_ = v.Z // fails in new
+```
+In the new version, the last line fails to compile because there are two embedded `Z`
+fields at the same depth, one from `z` and one from `pkg.Point`.
+
+
+### Using an Identical Type Externally
+
+If it is possible for client code to write a type expression representing the
+underlying type of a defined type in a package, then external code can use it in
+assignments involving the package type, making any change to that type incompatible.
+```
+// old
+type Point struct { X, Y int }
+
+// new
+type Point struct { X, Y, Z int }
+
+// client
+var p struct { X, Y int } = pkg.Point{} // fails in new because of Point's extra field
+```
+Here, the external code could have used the provided name `Point`, but chose not
+to. I'll have more to say about this and related examples later.
+
+### unsafe.Sizeof and Friends
+
+Since `unsafe.Sizeof`, `unsafe.Offsetof` and `unsafe.Alignof` are constant
+expressions, they can be used in an array type literal:
+
+```
+// old
+type S struct{ X int }
+
+// new
+type S struct{ X, y int }
+
+// client
+var a [unsafe.Sizeof(pkg.S{})]int = [8]int{} // fails in new because S's size is not 8
+```
+Use of these operations could make many changes to a type potentially incompatible.
+
+
+### Type Switches
+
+A package change that merges two different types (with same underlying type)
+into a single new type may break type switches in clients that refer to both
+original types:
+
+```
+// old
+type T1 int
+type T2 int
+
+// new
+type T1 int
+type T2 = T1
+
+// client
+switch x.(type) {
+case T1:
+case T2:
+} // fails with new because two cases have the same type
+```
+This sort of incompatibility is sufficiently esoteric to ignore; the tool allows
+merging types.
+
+## First Attempt at a Definition
+
+Our first attempt at defining compatibility captures the idea that all the
+exported names in the old package must have compatible equivalents in the new
+package.
+
+A new package is compatible with an old one if and only if:
+- For every exported package-level name in the old package, the same name is
+ declared in the new at package level, and
+- the names denote the same kind of object (e.g. both are variables), and
+- the types of the objects are compatible.
+
+We will work out the details (and make some corrections) below, but it is clear
+already that we will need to determine what makes two types compatible. And
+whatever the definition of type compatibility, it's certainly true that if two
+types are the same, they are compatible. So we will need to decide what makes an
+old and new type the same. We will call this sameness relation _correspondence_.
+
+## Type Correspondence
+
+Go already has a definition of when two types are the same:
+[type identity](https://golang.org/ref/spec#Type_identity).
+But identity isn't adequate for our purpose: it says that two defined
+types are identical if they arise from the same definition, but it's unclear
+what "same" means when talking about two different packages (or two versions of
+a single package).
+
+The obvious change to the definition of identity is to require that old and new
+[defined types](https://golang.org/ref/spec#Type_definitions)
+have the same name instead. But that doesn't work either, for two
+reasons. First, type aliases can equate two defined types with different names:
+
+```
+// old
+type E int
+
+// new
+type t int
+type E = t
+```
+Second, an unexported type can be renamed:
+
+```
+// old
+type u1 int
+var V u1
+
+// new
+type u2 int
+var V u2
+```
+Here, even though `u1` and `u2` are unexported, their exported fields and
+methods are visible to clients, so they are part of the API. But since the name
+`u1` is not visible to clients, it can be changed compatibly. We say that `u1`
+and `u2` are _exposed_: a type is exposed if a client package can declare variables of that type.
+
+We will say that an old defined type _corresponds_ to a new one if they have the
+same name, or one can be renamed to the other without otherwise changing the
+API. In the first example above, old `E` and new `t` correspond. In the second,
+old `u1` and new `u2` correspond.
+
+Two or more old defined types can correspond to a single new type: we consider
+"merging" two types into one to be a compatible change. As mentioned above,
+code that uses both names in a type switch will fail, but we deliberately ignore
+this case. However, a single old type can correspond to only one new type.
+
+So far, we've explained what correspondence means for defined types. To extend
+the definition to all types, we parallel the language's definition of type
+identity. So, for instance, an old and a new slice type correspond if their
+element types correspond.
+
+## Definition of Compatibility
+
+We can now present the definition of compatibility used by `apidiff`.
+
+### Module Compatibility
+
+> A new module is compatible with an old one if:
+>1. Each package present in the old module also appears in the new module,
+> with matching import paths relative to their respective module root, and
+>2. Each package present in both modules fulfills Package Compatibility as
+> defined below.
+>
+>Otherwise the modules are incompatible.
+
+If a package is converted into a nested module of the original module then
+comparing two versions of the module, before and after nested module creation,
+will produce an incompatible package removal message. This removal message does
+not necessarily mean that client code will need to change. If the package API
+retains Package Compatibility after nested module creation, then only the
+`go.mod` of the client code will need to change. Take the following example:
+
+```
+./
+ go.mod
+ go.sum
+ foo.go
+ bar/bar.go
+```
+
+Where `go.mod` is:
+
+```
+module example.com/foo
+
+go 1.20
+```
+
+Where `bar/bar.go` is:
+
+```
+package bar
+
+var V int
+```
+
+And `foo.go` is:
+
+```
+package foo
+
+import "example.com/foo/bar"
+
+_ = bar.V
+```
+
+Creating a nested module with the package `bar` while retaining Package
+Compatibility is _code_ compatible, because the import path of the package does
+not change:
+
+```
+./
+ go.mod
+ go.sum
+ foo.go
+ bar/
+ bar.go
+ go.mod
+ go.sum
+```
+
+Where `bar/go.mod` is:
+```
+module example.com/foo/bar
+
+go 1.20
+```
+
+And the top-level `go.mod` becomes:
+```
+module example.com/foo
+
+go 1.20
+
+// New dependency on nested module.
+require example.com/foo/bar v1.0.0
+```
+
+If during nested module creation either Package Compatibility is broken, like so
+in `bar/bar.go`:
+
+```
+package bar
+
+// Changed from V to T.
+var T int
+```
+
+Or the nested module uses a name other than the original package's import path,
+like so in `bar/go.mod`:
+
+```
+// Completely different module name
+module example.com/qux
+
+go 1.20
+```
+
+Then the move is backwards incompatible for client code.
+
+### Package Compatibility
+
+> A new package is compatible with an old one if:
+>1. Each exported name in the old package's scope also appears in the new
+>package's scope, and the object (constant, variable, function or type) denoted
+>by that name in the old package is compatible with the object denoted by the
+>name in the new package, and
+>2. For every exposed type that implements an exposed interface in the old package,
+> its corresponding type should implement the corresponding interface in the new package.
+>
+>Otherwise the packages are incompatible.
+
+As an aside, the tool also finds exported names in the new package that are not
+exported in the old, and marks them as compatible changes.
+
+Clause 2 is discussed further in "Whole-Package Compatibility."
+
+### Object Compatibility
+
+This section provides compatibility rules for constants, variables, functions
+and types.
+
+#### Constants
+
+>A new exported constant is compatible with an old one of the same name if and only if
+>1. Their types correspond, and
+>2. Their values are identical.
+
+It is tempting to allow changing a typed constant to an untyped one. That may
+seem harmless, but it can break code like this:
+
+```
+// old
+const C int64 = 1
+
+// new
+const C = 1
+
+// client
+var x = C // old type is int64, new is int
+var y int64 = x // fails with new: different types in assignment
+```
+
+A change to the value of a constant can break compatibility if the value is used
+in an array type:
+
+```
+// old
+const C = 1
+
+// new
+const C = 2
+
+// client
+var a [C]int = [1]int{} // fails with new because [2]int and [1]int are different types
+```
+Changes to constant values are rare, and determining whether they are compatible
+or not is better left to the user, so the tool reports them.
+
+#### Variables
+
+>A new exported variable is compatible with an old one of the same name if and
+>only if their types correspond.
+
+Correspondence doesn't look past names, so this rule does not prevent adding a
+field to `MyStruct` if the package declares `var V MyStruct`. It does, however, mean that
+
+```
+var V struct { X int }
+```
+is incompatible with
+```
+var V struct { X, Y int }
+```
+I discuss this at length below in the section "Compatibility, Types and Names."
+
+#### Functions
+
+>A new exported function or variable is compatible with an old function of the
+>same name if and only if their types (signatures) correspond.
+
+This rule captures the fact that, although many signature changes are compatible
+for all call sites, none are compatible for assignment:
+
+```
+var v func(int) = pkg.F
+```
+Here, `F` must be of type `func(int)` and not, for instance, `func(...int)` or `func(interface{})`.
+
+Note that the rule permits changing a function to a variable. This is a common
+practice, usually done for test stubbing, and cannot break any code at compile
+time.
+
+#### Exported Types
+
+> A new exported type is compatible with an old one if and only if their
+> names are the same and their types correspond.
+
+This rule seems far too strict. But, ignoring aliases for the moment, it demands only
+that the old and new _defined_ types correspond. Consider:
+```
+// old
+type T struct { X int }
+
+// new
+type T struct { X, Y int }
+```
+The addition of `Y` is a compatible change, because this rule does not require
+that the struct literals have to correspond, only that the defined types
+denoted by `T` must correspond. (Remember that correspondence stops at type
+names.)
+
+If one type is an alias that refers to the corresponding defined type, the
+situation is the same:
+
+```
+// old
+type T struct { X int }
+
+// new
+type u struct { X, Y int }
+type T = u
+```
+Here, the only requirement is that old `T` corresponds to new `u`, not that the
+struct types correspond. (We can't tell from this snippet that the old `T` and
+the new `u` do correspond; that depends on whether `u` replaces `T` throughout
+the API.)
+
+However, the following change is incompatible, because the names do not
+denote corresponding types:
+
+```
+// old
+type T = struct { X int }
+
+// new
+type T = struct { X, Y int }
+```
+### Type Literal Compatibility
+
+Only five kinds of types can differ compatibly: defined types, structs,
+interfaces, channels and numeric types. We only consider the compatibility of
+the last four when they are the underlying type of a defined type. See
+"Compatibility, Types and Names" for a rationale.
+
+We justify the compatibility rules by enumerating all the ways a type
+can be used, and by showing that the allowed changes cannot break any code that
+uses values of the type in those ways.
+
+Values of all types can be used in assignments (including argument passing and
+function return), but we do not require that old and new types are assignment
+compatible. That is because we assume that the old and new packages are never
+used together: any given binary will link in either the old package or the new.
+So in describing how a type can be used in the sections below, we omit
+assignment.
+
+Any type can also be used in a type assertion or conversion. The changes we allow
+below may affect the run-time behavior of these operations, but they cannot affect
+whether they compile. The only such breaking change would be to change
+the type `T` in an assertion `x.T` so that it no longer implements the interface
+type of `x`; but the rules for interfaces below disallow that.
+
+> A new type is compatible with an old one if and only if they correspond, or
+> one of the cases below applies.
+
+#### Defined Types
+
+Other than assignment, the only ways to use a defined type are to access its
+methods, or to make use of the properties of its underlying type. Rule 2 below
+covers the latter, and rules 3 and 4 cover the former.
+
+> A new defined type is compatible with an old one if and only if all of the
+> following hold:
+>1. They correspond.
+>2. Their underlying types are compatible.
+>3. The new exported value method set is a superset of the old.
+>4. The new exported pointer method set is a superset of the old.
+
+An exported method set is a method set with all unexported methods removed.
+When comparing methods of a method set, we require identical names and
+corresponding signatures.
+
+Removing an exported method is clearly a breaking change. But removing an
+unexported one (or changing its signature) can be breaking as well, if it
+results in the type no longer implementing an interface. See "Whole-Package
+Compatibility," below.
+
+#### Channels
+
+> A new channel type is compatible with an old one if
+> 1. The element types correspond, and
+> 2. Either the directions are the same, or the new type has no direction.
+
+Other than assignment, the only ways to use values of a channel type are to send
+and receive on them, to close them, and to use them as map keys. Changes to a
+channel type cannot cause code that closes a channel or uses it as a map key to
+fail to compile, so we need not consider those operations.
+
+Rule 1 ensures that any operations on the values sent or received will compile.
+Rule 2 captures the fact that any program that compiles with a directed channel
+must use either only sends, or only receives, so allowing the other operation
+by removing the channel direction cannot break any code.
+
+
+#### Interfaces
+
+> A new interface is compatible with an old one if and only if:
+> 1. The old interface does not have an unexported method, and it corresponds
+> to the new interfaces (i.e. they have the same method set), or
+> 2. The old interface has an unexported method and the new exported method set is a
+> superset of the old.
+
+Other than assignment, the only ways to use an interface are to implement it,
+embed it, or call one of its methods. (Interface values can also be used as map
+keys, but that cannot cause a compile-time error.)
+
+Certainly, removing an exported method from an interface could break a client
+call, so neither rule allows it.
+
+Rule 1 also disallows adding a method to an interface without an existing unexported
+method. Such an interface can be implemented in client code. If adding a method
+were allowed, a type that implements the old interface could fail to implement
+the new one:
+
+```
+type I interface { M1() } // old
+type I interface { M1(); M2() } // new
+
+// client
+type t struct{}
+func (t) M1() {}
+var i pkg.I = t{} // fails with new, because t lacks M2
+```
+
+Rule 2 is based on the observation that if an interface has an unexported
+method, the only way a client can implement it is to embed it.
+Adding a method is compatible in this case, because the embedding struct will
+continue to implement the interface. Adding a method also cannot break any call
+sites, since no program that compiles could have any such call sites.
+
+#### Structs
+
+> A new struct is compatible with an old one if all of the following hold:
+> 1. The new set of top-level exported fields is a superset of the old.
+> 2. The new set of _selectable_ exported fields is a superset of the old.
+> 3. If the old struct is comparable, so is the new one.
+
+The set of selectable exported fields is the set of exported fields `F`
+such that `x.F` is a valid selector expression for a value `x` of the struct
+type. `F` may be at the top level of the struct, or it may be a field of an
+embedded struct.
+
+Two fields are the same if they have the same name and corresponding types.
+
+Other than assignment, there are only four ways to use a struct: write a struct
+literal, select a field, use a value of the struct as a map key, or compare two
+values for equality. The first clause ensures that struct literals compile; the
+second, that selections compile; and the third, that equality expressions and
+map index expressions compile.
+
+#### Numeric Types
+
+> A new numeric type is compatible with an old one if and only if they are
+> both unsigned integers, both signed integers, both floats or both complex
+> types, and the new one is at least as large as the old on both 32-bit and
+> 64-bit architectures.
+
+Other than in assignments, numeric types appear in arithmetic and comparison
+expressions. Since all arithmetic operations but shifts (see below) require that
+operand types be identical, and by assumption the old and new types underly
+defined types (see "Compatibility, Types and Names," below), there is no way for
+client code to write an arithmetic expression that compiles with operands of the
+old type but not the new.
+
+Numeric types can also appear in type switches and type assertions. Again, since
+the old and new types underly defined types, type switches and type assertions
+that compiled using the old defined type will continue to compile with the new
+defined type.
+
+Going from an unsigned to a signed integer type is an incompatible change for
+the sole reason that only an unsigned type can appear as the right operand of a
+shift. If this rule is relaxed, then changes from an unsigned type to a larger
+signed type would be compatible. See [this
+issue](https://github.com/golang/go/issues/19113).
+
+Only integer types can be used in bitwise and shift operations, and for indexing
+slices and arrays. That is why switching from an integer to a floating-point
+type--even one that can represent all values of the integer type--is an
+incompatible change.
+
+
+Conversions from floating-point to complex types or vice versa are not permitted
+(the predeclared functions real, imag, and complex must be used instead). To
+prevent valid floating-point or complex conversions from becoming invalid,
+changing a floating-point type to a complex type or vice versa is considered an
+incompatible change.
+
+Although conversions between any two integer types are valid, assigning a
+constant value to a variable of integer type that is too small to represent the
+constant is not permitted. That is why the only compatible changes are to
+a new type whose values are a superset of the old. The requirement that the new
+set of values must include the old on both 32-bit and 64-bit machines allows
+conversions from `int32` to `int` and from `int` to `int64`, but not the other
+direction; and similarly for `uint`.
+
+Changing a type to or from `uintptr` is considered an incompatible change. Since
+its size is not specified, there is no way to know whether the new type's values
+are a superset of the old type's.
+
+## Whole-Package Compatibility
+
+Some changes that are compatible for a single type are not compatible when the
+package is considered as a whole. For example, if you remove an unexported
+method on a defined type, it may no longer implement an interface of the
+package. This can break client code:
+
+```
+// old
+type T int
+func (T) m() {}
+type I interface { m() }
+
+// new
+type T int // no method m anymore
+
+// client
+var i pkg.I = pkg.T{} // fails with new because T lacks m
+```
+
+Similarly, adding a method to an interface can cause defined types
+in the package to stop implementing it.
+
+The second clause in the definition for package compatibility handles these
+cases. To repeat:
+> 2. For every exposed type that implements an exposed interface in the old package,
+> its corresponding type should implement the corresponding interface in the new package.
+Recall that a type is exposed if it is part of the package's API, even if it is
+unexported.
+
+Other incompatibilities that involve more than one type in the package can arise
+whenever two types with identical underlying types exist in the old or new
+package. Here, a change "splits" an identical underlying type into two, breaking
+conversions:
+
+```
+// old
+type B struct { X int }
+type C struct { X int }
+
+// new
+type B struct { X int }
+type C struct { X, Y int }
+
+// client
+var b B
+_ = C(b) // fails with new: cannot convert B to C
+```
+Finally, changes that are compatible for the package in which they occur can
+break downstream packages. That can happen even if they involve unexported
+methods, thanks to embedding.
+
+The definitions given here don't account for these sorts of problems.
+
+
+## Compatibility, Types and Names
+
+The above definitions state that the only types that can differ compatibly are
+defined types and the types that underly them. Changes to other type literals
+are considered incompatible. For instance, it is considered an incompatible
+change to add a field to the struct in this variable declaration:
+
+```
+var V struct { X int }
+```
+or this alias definition:
+```
+type T = struct { X int }
+```
+
+We make this choice to keep the definition of compatibility (relatively) simple.
+A more precise definition could, for instance, distinguish between
+
+```
+func F(struct { X int })
+```
+where any changes to the struct are incompatible, and
+
+```
+func F(struct { X, u int })
+```
+where adding a field is compatible (since clients cannot write the signature,
+and thus cannot assign `F` to a variable of the signature type). The definition
+should then also allow other function signature changes that only require
+call-site compatibility, like
+
+```
+func F(struct { X, u int }, ...int)
+```
+The result would be a much more complex definition with little benefit, since
+the examples in this section rarely arise in practice.
diff --git a/apidiff/apidiff.go b/apidiff/apidiff.go
new file mode 100644
index 0000000..06d2e92
--- /dev/null
+++ b/apidiff/apidiff.go
@@ -0,0 +1,324 @@
+// TODO: test swap corresponding types (e.g. u1 <-> u2 and u2 <-> u1)
+// TODO: test exported alias refers to something in another package -- does correspondence work then?
+// TODO: CODE COVERAGE
+// TODO: note that we may miss correspondences because we bail early when we compare a signature (e.g. when lengths differ; we could do up to the shorter)
+// TODO: if you add an unexported method to an exposed interface, you have to check that
+// every exposed type that previously implemented the interface still does. Otherwise
+// an external assignment of the exposed type to the interface type could fail.
+// TODO: check constant values: large values aren't representable by some types.
+// TODO: Document all the incompatibilities we don't check for.
+
+package apidiff
+
+import (
+ "fmt"
+ "go/constant"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+// Changes reports on the differences between the APIs of the old and new packages.
+// It classifies each difference as either compatible or incompatible (breaking.) For
+// a detailed discussion of what constitutes an incompatible change, see the README.
+func Changes(old, new *types.Package) Report {
+ return changesInternal(old, new, old.Path(), new.Path())
+}
+
+// changesInternal contains the core logic for comparing a single package, shared
+// between Changes and ModuleChanges. The root package path arguments refer to the
+// context of this apidiff invocation - when diffing a single package, they will be
+// that package, but when diffing a whole module, they will be the root path of the
+// module. This is used to give change messages appropriate context for object names.
+// The old and new root must be tracked independently, since each side of the diff
+// operation may be a different path.
+func changesInternal(old, new *types.Package, oldRootPackagePath, newRootPackagePath string) Report {
+ d := newDiffer(old, new)
+ d.checkPackage(oldRootPackagePath)
+ r := Report{}
+ for _, m := range d.incompatibles.collect(oldRootPackagePath, newRootPackagePath) {
+ r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
+ }
+ for _, m := range d.compatibles.collect(oldRootPackagePath, newRootPackagePath) {
+ r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
+ }
+ return r
+}
+
+// ModuleChanges reports on the differences between the APIs of the old and new
+// modules. It classifies each difference as either compatible or incompatible
+// (breaking). This includes the addition and removal of entire packages. For a
+// detailed discussion of what constitutes an incompatible change, see the README.
+func ModuleChanges(old, new *Module) Report {
+ var r Report
+
+ oldPkgs := make(map[string]*types.Package)
+ for _, p := range old.Packages {
+ oldPkgs[old.relativePath(p)] = p
+ }
+
+ newPkgs := make(map[string]*types.Package)
+ for _, p := range new.Packages {
+ newPkgs[new.relativePath(p)] = p
+ }
+
+ for n, op := range oldPkgs {
+ if np, ok := newPkgs[n]; ok {
+ // shared package, compare surfaces
+ rr := changesInternal(op, np, old.Path, new.Path)
+ r.Changes = append(r.Changes, rr.Changes...)
+ } else {
+ // old package was removed
+ r.Changes = append(r.Changes, packageChange(op, "removed", false))
+ }
+ }
+
+ for n, np := range newPkgs {
+ if _, ok := oldPkgs[n]; !ok {
+ // new package was added
+ r.Changes = append(r.Changes, packageChange(np, "added", true))
+ }
+ }
+
+ return r
+}
+
+func packageChange(p *types.Package, change string, compatible bool) Change {
+ return Change{
+ Message: fmt.Sprintf("package %s: %s", p.Path(), change),
+ Compatible: compatible,
+ }
+}
+
+// Module is a convenience type for representing a Go module with a path and a
+// slice of Packages contained within.
+type Module struct {
+ Path string
+ Packages []*types.Package
+}
+
+// relativePath computes the module-relative package path of the given Package.
+func (m *Module) relativePath(p *types.Package) string {
+ return strings.TrimPrefix(p.Path(), m.Path)
+}
+
+type differ struct {
+ old, new *types.Package
+ // Correspondences between named types.
+ // Even though it is the named types (*types.Named) that correspond, we use
+ // *types.TypeName as a map key because they are canonical.
+ // The values can be either named types or basic types.
+ correspondMap typeutil.Map
+
+ // Messages.
+ incompatibles messageSet
+ compatibles messageSet
+}
+
+func newDiffer(old, new *types.Package) *differ {
+ return &differ{
+ old: old,
+ new: new,
+ incompatibles: messageSet{},
+ compatibles: messageSet{},
+ }
+}
+
+func (d *differ) incompatible(obj objectWithSide, part, format string, args ...interface{}) {
+ addMessage(d.incompatibles, obj, part, format, args)
+}
+
+func (d *differ) compatible(obj objectWithSide, part, format string, args ...interface{}) {
+ addMessage(d.compatibles, obj, part, format, args)
+}
+
+func addMessage(ms messageSet, obj objectWithSide, part, format string, args []interface{}) {
+ ms.add(obj, part, fmt.Sprintf(format, args...))
+}
+
+func (d *differ) checkPackage(oldRootPackagePath string) {
+ // Determine what has changed between old and new.
+
+ // First, establish correspondences between types with the same name, before
+ // looking at aliases. This will avoid confusing messages like "T: changed
+ // from T to T", which can happen if a correspondence between an alias
+ // and a named type is established first.
+ // See testdata/order.go.
+ for _, name := range d.old.Scope().Names() {
+ oldobj := d.old.Scope().Lookup(name)
+ if tn, ok := oldobj.(*types.TypeName); ok {
+ if oldn, ok := tn.Type().(*types.Named); ok {
+ if !oldn.Obj().Exported() {
+ continue
+ }
+ // Does new have a named type of the same name? Look up using
+ // the old named type's name, oldn.Obj().Name(), not the
+ // TypeName tn, which may be an alias.
+ newobj := d.new.Scope().Lookup(oldn.Obj().Name())
+ if newobj != nil {
+ d.checkObjects(oldobj, newobj)
+ }
+ }
+ }
+ }
+
+ // Next, look at all exported symbols in the old world and compare them
+ // with the same-named symbols in the new world.
+ for _, name := range d.old.Scope().Names() {
+ oldobj := d.old.Scope().Lookup(name)
+ if !oldobj.Exported() {
+ continue
+ }
+ newobj := d.new.Scope().Lookup(name)
+ if newobj == nil {
+ d.incompatible(objectWithSide{oldobj, false}, "", "removed")
+ continue
+ }
+ d.checkObjects(oldobj, newobj)
+ }
+
+ // Now look at what has been added in the new package.
+ for _, name := range d.new.Scope().Names() {
+ newobj := d.new.Scope().Lookup(name)
+ if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
+ d.compatible(objectWithSide{newobj, true}, "", "added")
+ }
+ }
+
+ // Whole-package satisfaction.
+ // For every old exposed interface oIface and its corresponding new interface nIface...
+ d.correspondMap.Iterate(func(k1 types.Type, v1 any) {
+ ot1 := k1.(*types.Named)
+ otn1 := ot1.Obj()
+ nt1 := v1.(types.Type)
+ oIface, ok := otn1.Type().Underlying().(*types.Interface)
+ if !ok {
+ return
+ }
+ nIface, ok := nt1.Underlying().(*types.Interface)
+ if !ok {
+ // If nt1 isn't an interface but otn1 is, then that's an incompatibility that
+ // we've already noticed, so there's no need to do anything here.
+ return
+ }
+ // For every old type that implements oIface, its corresponding new type must implement
+ // nIface.
+ d.correspondMap.Iterate(func(k2 types.Type, v2 any) {
+ ot2 := k2.(*types.Named)
+ otn2 := ot2.Obj()
+ nt2 := v2.(types.Type)
+ if otn1 == otn2 {
+ return
+ }
+ if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) {
+ // TODO(jba): the type name is not sufficient information here; we need the type args
+ // if this is an instantiated generic type.
+ d.incompatible(objectWithSide{otn2, false}, "", "no longer implements %s", objectString(otn1, oldRootPackagePath))
+ }
+ })
+ })
+}
+
+func (d *differ) checkObjects(old, new types.Object) {
+ switch old := old.(type) {
+ case *types.Const:
+ if new, ok := new.(*types.Const); ok {
+ d.constChanges(old, new)
+ return
+ }
+ case *types.Var:
+ if new, ok := new.(*types.Var); ok {
+ d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
+ return
+ }
+ case *types.Func:
+ switch new := new.(type) {
+ case *types.Func:
+ d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
+ return
+ case *types.Var:
+ d.compatible(objectWithSide{old, false}, "", "changed from func to var")
+ d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
+ return
+
+ }
+ case *types.TypeName:
+ if new, ok := new.(*types.TypeName); ok {
+ d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
+ return
+ }
+ default:
+ panic("unexpected obj type")
+ }
+ // Here if kind of type changed.
+ d.incompatible(objectWithSide{old, false}, "", "changed from %s to %s",
+ objectKindString(old), objectKindString(new))
+}
+
+// Compare two constants.
+func (d *differ) constChanges(old, new *types.Const) {
+ ot := old.Type()
+ nt := new.Type()
+ // Check for change of type.
+ if !d.correspond(ot, nt) {
+ d.typeChanged(objectWithSide{old, false}, "", ot, nt)
+ return
+ }
+ // Check for change of value.
+ // We know the types are the same, so constant.Compare shouldn't panic.
+ if !constant.Compare(old.Val(), token.EQL, new.Val()) {
+ d.incompatible(objectWithSide{old, false}, "", "value changed from %s to %s", old.Val(), new.Val())
+ }
+}
+
+func objectKindString(obj types.Object) string {
+ switch obj.(type) {
+ case *types.Const:
+ return "const"
+ case *types.Var:
+ return "var"
+ case *types.Func:
+ return "func"
+ case *types.TypeName:
+ return "type"
+ default:
+ return "???"
+ }
+}
+
+func (d *differ) checkCorrespondence(obj objectWithSide, part string, old, new types.Type) {
+ if !d.correspond(old, new) {
+ d.typeChanged(obj, part, old, new)
+ }
+}
+
+func (d *differ) typeChanged(obj objectWithSide, part string, old, new types.Type) {
+ old = removeNamesFromSignature(old)
+ new = removeNamesFromSignature(new)
+ olds := types.TypeString(old, types.RelativeTo(d.old))
+ news := types.TypeString(new, types.RelativeTo(d.new))
+ d.incompatible(obj, part, "changed from %s to %s", olds, news)
+}
+
+// go/types always includes the argument and result names when formatting a signature.
+// Since these can change without affecting compatibility, we don't want users to
+// be distracted by them, so we remove them.
+func removeNamesFromSignature(t types.Type) types.Type {
+ sig, ok := t.(*types.Signature)
+ if !ok {
+ return t
+ }
+
+ dename := func(p *types.Tuple) *types.Tuple {
+ var vars []*types.Var
+ for i := 0; i < p.Len(); i++ {
+ v := p.At(i)
+ vars = append(vars, types.NewVar(v.Pos(), v.Pkg(), "", v.Type()))
+ }
+ return types.NewTuple(vars...)
+ }
+
+ return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic())
+}
diff --git a/apidiff/apidiff_test.go b/apidiff/apidiff_test.go
new file mode 100644
index 0000000..31ad95e
--- /dev/null
+++ b/apidiff/apidiff_test.go
@@ -0,0 +1,339 @@
+package apidiff
+
+import (
+ "bufio"
+ "fmt"
+ "go/types"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/tools/go/packages"
+ "golang.org/x/tools/go/packages/packagestest"
+)
+
+func TestModuleChanges(t *testing.T) {
+ packagestest.TestAll(t, testModuleChanges)
+}
+
+func testModuleChanges(t *testing.T, x packagestest.Exporter) {
+ e := packagestest.Export(t, x, []packagestest.Module{
+ {
+ Name: "example.com/moda",
+ Files: map[string]any{
+ "foo/foo.go": "package foo\n\nconst Version = 1",
+ "foo/baz/baz.go": "package baz",
+ },
+ },
+ {
+ Name: "example.com/modb",
+ Files: map[string]any{
+ "foo/foo.go": "package foo\n\nconst Version = 2\nconst Other = 1",
+ "bar/bar.go": "package bar",
+ },
+ },
+ })
+ defer e.Cleanup()
+
+ a, err := loadModule(t, e.Config, "example.com/moda")
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := loadModule(t, e.Config, "example.com/modb")
+ if err != nil {
+ t.Fatal(err)
+ }
+ report := ModuleChanges(a, b)
+ if len(report.Changes) == 0 {
+ t.Fatal("expected some changes, but got none")
+ }
+ wanti := []string{
+ "./foo.Version: value changed from 1 to 2",
+ "package example.com/moda/foo/baz: removed",
+ }
+ sort.Strings(wanti)
+
+ got := report.messages(false)
+ sort.Strings(got)
+
+ if diff := cmp.Diff(wanti, got); diff != "" {
+ t.Errorf("incompatibles: mismatch (-want, +got)\n%s", diff)
+ }
+
+ wantc := []string{
+ "./foo.Other: added",
+ "package example.com/modb/bar: added",
+ }
+ sort.Strings(wantc)
+
+ got = report.messages(true)
+ sort.Strings(got)
+
+ if diff := cmp.Diff(wantc, got); diff != "" {
+ t.Errorf("compatibles: mismatch (-want, +got)\n%s", diff)
+ }
+}
+
+func TestChanges(t *testing.T) {
+ testfiles, err := filepath.Glob(filepath.Join("testdata", "*.go"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, testfile := range testfiles {
+ name := strings.TrimSuffix(filepath.Base(testfile), ".go")
+ t.Run(name, func(t *testing.T) {
+ dir := filepath.Join(t.TempDir(), "go")
+ wanti, wantc := splitIntoPackages(t, testfile, dir)
+ sort.Strings(wanti)
+ sort.Strings(wantc)
+
+ oldpkg, err := loadPackage(t, "apidiff/old", dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ newpkg, err := loadPackage(t, "apidiff/new", dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ report := Changes(oldpkg.Types, newpkg.Types)
+
+ got := report.messages(false)
+ if diff := cmp.Diff(wanti, got); diff != "" {
+ t.Errorf("incompatibles: mismatch (-want, +got)\n%s", diff)
+ }
+ got = report.messages(true)
+ if diff := cmp.Diff(wantc, got); diff != "" {
+ t.Errorf("compatibles: mismatch (-want, +got)\n%s", diff)
+ }
+ })
+ }
+}
+
+func splitIntoPackages(t *testing.T, file, dir string) (incompatibles, compatibles []string) {
+ // Read the input file line by line.
+ // Write a line into the old or new package,
+ // dependent on comments.
+ // Also collect expected messages.
+ f, err := os.Open(file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ if err := os.MkdirAll(filepath.Join(dir, "src", "apidiff"), 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(dir, "src", "apidiff", "go.mod"), []byte("module apidiff\ngo 1.18\n"), 0600); err != nil {
+ t.Fatal(err)
+ }
+
+ oldd := filepath.Join(dir, "src/apidiff/old")
+ newd := filepath.Join(dir, "src/apidiff/new")
+ if err := os.MkdirAll(oldd, 0700); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Mkdir(newd, 0700); err != nil && !os.IsExist(err) {
+ t.Fatal(err)
+ }
+
+ oldf, err := os.Create(filepath.Join(oldd, "old.go"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := oldf.Close(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ newf, err := os.Create(filepath.Join(newd, "new.go"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := newf.Close(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ wl := func(f *os.File, line string) {
+ if _, err := fmt.Fprintln(f, line); err != nil {
+ t.Fatal(err)
+ }
+ }
+ writeBoth := func(line string) { wl(oldf, line); wl(newf, line) }
+ writeln := writeBoth
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ line := s.Text()
+ tl := strings.TrimSpace(line)
+ switch {
+ case tl == "// old":
+ writeln = func(line string) { wl(oldf, line) }
+ case tl == "// new":
+ writeln = func(line string) { wl(newf, line) }
+ case tl == "// both":
+ writeln = writeBoth
+ case strings.HasPrefix(tl, "// i "):
+ incompatibles = append(incompatibles, strings.TrimSpace(tl[4:]))
+ case strings.HasPrefix(tl, "// c "):
+ compatibles = append(compatibles, strings.TrimSpace(tl[4:]))
+ default:
+ writeln(line)
+ }
+ }
+ if s.Err() != nil {
+ t.Fatal(s.Err())
+ }
+ return
+}
+
+// Copied from cmd/apidiff/main.go.
+func loadModule(t *testing.T, cfg *packages.Config, modulePath string) (*Module, error) {
+ needsGoPackages(t)
+
+ cfg.Mode = cfg.Mode | packages.LoadTypes
+ loaded, err := packages.Load(cfg, fmt.Sprintf("%s/...", modulePath))
+ if err != nil {
+ return nil, err
+ }
+ if len(loaded) == 0 {
+ return nil, fmt.Errorf("found no packages for module %s", modulePath)
+ }
+ var tpkgs []*types.Package
+ for _, p := range loaded {
+ if len(p.Errors) > 0 {
+ // TODO: use errors.Join once Go 1.21 is released.
+ return nil, p.Errors[0]
+ }
+ tpkgs = append(tpkgs, p.Types)
+ }
+
+ return &Module{Path: modulePath, Packages: tpkgs}, nil
+}
+
+func loadPackage(t *testing.T, importPath, goPath string) (*packages.Package, error) {
+ needsGoPackages(t)
+
+ cfg := &packages.Config{
+ Mode: packages.LoadTypes,
+ }
+ if goPath != "" {
+ cfg.Env = append(os.Environ(), "GOPATH="+goPath)
+ cfg.Dir = filepath.Join(goPath, "src", filepath.FromSlash(importPath))
+ }
+ pkgs, err := packages.Load(cfg, importPath)
+ if err != nil {
+ return nil, err
+ }
+ if len(pkgs[0].Errors) > 0 {
+ return nil, pkgs[0].Errors[0]
+ }
+ return pkgs[0], nil
+}
+
+func TestExportedFields(t *testing.T) {
+ pkg, err := loadPackage(t, "golang.org/x/exp/apidiff/testdata/exported_fields", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ typeof := func(name string) types.Type {
+ return pkg.Types.Scope().Lookup(name).Type()
+ }
+
+ s := typeof("S")
+ su := s.(*types.Named).Underlying().(*types.Struct)
+
+ ef := exportedSelectableFields(su)
+ wants := []struct {
+ name string
+ typ types.Type
+ }{
+ {"A1", typeof("A1")},
+ {"D", types.Typ[types.Bool]},
+ {"E", types.Typ[types.Int]},
+ {"F", typeof("F")},
+ {"S", types.NewPointer(s)},
+ }
+
+ if got, want := len(ef), len(wants); got != want {
+ t.Errorf("got %d fields, want %d\n%+v", got, want, ef)
+ }
+ for _, w := range wants {
+ if got := ef[w.name]; got != nil && !types.Identical(got.Type(), w.typ) {
+ t.Errorf("%s: got %v, want %v", w.name, got.Type(), w.typ)
+ }
+ }
+}
+
+// needsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
+// the current process environment is not present in the path.
+//
+// Copied and adapted from golang.org/x/tools/internal/testenv.
+func needsGoPackages(t *testing.T) {
+ t.Helper()
+
+ tool := os.Getenv("GOPACKAGESDRIVER")
+ switch tool {
+ case "off":
+ // "off" forces go/packages to use the go command.
+ tool = "go"
+ case "":
+ if _, err := exec.LookPath("gopackagesdriver"); err == nil {
+ tool = "gopackagesdriver"
+ } else {
+ tool = "go"
+ }
+ }
+
+ needsTool(t, tool)
+}
+
+// needsTool skips t if the named tool is not present in the path.
+//
+// Copied and adapted from golang.org/x/tools/internal/testenv.
+func needsTool(t *testing.T, tool string) {
+ _, err := exec.LookPath(tool)
+ if err == nil {
+ return
+ }
+
+ t.Helper()
+ if allowMissingTool(tool) {
+ t.Skipf("skipping because %s tool not available: %v", tool, err)
+ } else {
+ t.Fatalf("%s tool not available: %v", tool, err)
+ }
+}
+
+func allowMissingTool(tool string) bool {
+ if runtime.GOOS == "android" {
+ // Android builds generally run tests on a separate machine from the build,
+ // so don't expect any external tools to be available.
+ return true
+ }
+
+ if tool == "go" && os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
+ // Work around a misconfigured builder (see https://golang.org/issue/33950).
+ return true
+ }
+
+ // If a developer is actively working on this test, we expect them to have all
+ // of its dependencies installed. However, if it's just a dependency of some
+ // other module (for example, being run via 'go test all'), we should be more
+ // tolerant of unusual environments.
+ return !packageMainIsDevel()
+}
+
+// packageMainIsDevel reports whether the module containing package main
+// is a development version (if module information is available).
+//
+// Builds in GOPATH mode and builds that lack module information are assumed to
+// be development versions.
+var packageMainIsDevel = func() bool { return true }
diff --git a/apidiff/compatibility.go b/apidiff/compatibility.go
new file mode 100644
index 0000000..3c7ff8b
--- /dev/null
+++ b/apidiff/compatibility.go
@@ -0,0 +1,364 @@
+package apidiff
+
+import (
+ "fmt"
+ "go/types"
+ "reflect"
+)
+
+func (d *differ) checkCompatible(otn *types.TypeName, old, new types.Type) {
+ switch old := old.(type) {
+ case *types.Interface:
+ if new, ok := new.(*types.Interface); ok {
+ d.checkCompatibleInterface(otn, old, new)
+ return
+ }
+
+ case *types.Struct:
+ if new, ok := new.(*types.Struct); ok {
+ d.checkCompatibleStruct(objectWithSide{otn, false}, old, new)
+ return
+ }
+
+ case *types.Chan:
+ if new, ok := new.(*types.Chan); ok {
+ d.checkCompatibleChan(otn, old, new)
+ return
+ }
+
+ case *types.Basic:
+ if new, ok := new.(*types.Basic); ok {
+ d.checkCompatibleBasic(otn, old, new)
+ return
+ }
+
+ case *types.Named:
+ panic("unreachable")
+
+ default:
+ d.checkCorrespondence(objectWithSide{otn, false}, "", old, new)
+ return
+
+ }
+ // Here if old and new are different kinds of types.
+ d.typeChanged(objectWithSide{otn, false}, "", old, new)
+}
+
+func (d *differ) checkCompatibleChan(otn *types.TypeName, old, new *types.Chan) {
+ d.checkCorrespondence(objectWithSide{otn, false}, ", element type", old.Elem(), new.Elem())
+ if old.Dir() != new.Dir() {
+ if new.Dir() == types.SendRecv {
+ d.compatible(objectWithSide{otn, false}, "", "removed direction")
+ } else {
+ d.incompatible(objectWithSide{otn, false}, "", "changed direction")
+ }
+ }
+}
+
+func (d *differ) checkCompatibleBasic(otn *types.TypeName, old, new *types.Basic) {
+ // Certain changes to numeric types are compatible. Approximately, the info must
+ // be the same, and the new values must be a superset of the old.
+ if old.Kind() == new.Kind() {
+ // old and new are identical
+ return
+ }
+ if compatibleBasics[[2]types.BasicKind{old.Kind(), new.Kind()}] {
+ d.compatible(objectWithSide{otn, false}, "", "changed from %s to %s", old, new)
+ } else {
+ d.typeChanged(objectWithSide{otn, false}, "", old, new)
+ }
+}
+
+// All pairs (old, new) of compatible basic types.
+var compatibleBasics = map[[2]types.BasicKind]bool{
+ {types.Uint8, types.Uint16}: true,
+ {types.Uint8, types.Uint32}: true,
+ {types.Uint8, types.Uint}: true,
+ {types.Uint8, types.Uint64}: true,
+ {types.Uint16, types.Uint32}: true,
+ {types.Uint16, types.Uint}: true,
+ {types.Uint16, types.Uint64}: true,
+ {types.Uint32, types.Uint}: true,
+ {types.Uint32, types.Uint64}: true,
+ {types.Uint, types.Uint64}: true,
+ {types.Int8, types.Int16}: true,
+ {types.Int8, types.Int32}: true,
+ {types.Int8, types.Int}: true,
+ {types.Int8, types.Int64}: true,
+ {types.Int16, types.Int32}: true,
+ {types.Int16, types.Int}: true,
+ {types.Int16, types.Int64}: true,
+ {types.Int32, types.Int}: true,
+ {types.Int32, types.Int64}: true,
+ {types.Int, types.Int64}: true,
+ {types.Float32, types.Float64}: true,
+ {types.Complex64, types.Complex128}: true,
+}
+
+// Interface compatibility:
+// If the old interface has an unexported method, the new interface is compatible
+// if its exported method set is a superset of the old. (Users could not implement,
+// only embed.)
+//
+// If the old interface did not have an unexported method, the new interface is
+// compatible if its exported method set is the same as the old, and it has no
+// unexported methods. (Adding an unexported method makes the interface
+// unimplementable outside the package.)
+//
+// TODO: must also check that if any methods were added or removed, every exposed
+// type in the package that implemented the interface in old still implements it in
+// new. Otherwise external assignments could fail.
+func (d *differ) checkCompatibleInterface(otn *types.TypeName, old, new *types.Interface) {
+ // Method sets are checked in checkCompatibleDefined.
+
+ // Does the old interface have an unexported method?
+ if unexportedMethod(old) != nil {
+ d.checkMethodSet(otn, old, new, additionsCompatible)
+ } else {
+ // Perform an equivalence check, but with more information.
+ d.checkMethodSet(otn, old, new, additionsIncompatible)
+ if u := unexportedMethod(new); u != nil {
+ d.incompatible(objectWithSide{otn, false}, u.Name(), "added unexported method")
+ }
+ }
+}
+
+// Return an unexported method from the method set of t, or nil if there are none.
+func unexportedMethod(t *types.Interface) *types.Func {
+ for i := 0; i < t.NumMethods(); i++ {
+ if m := t.Method(i); !m.Exported() {
+ return m
+ }
+ }
+ return nil
+}
+
+// We need to check three things for structs:
+//
+// 1. The set of exported fields must be compatible. This ensures that keyed struct
+// literals continue to compile. (There is no compatibility guarantee for unkeyed
+// struct literals.)
+//
+// 2. The set of exported *selectable* fields must be compatible. This includes the exported
+// fields of all embedded structs. This ensures that selections continue to compile.
+//
+// 3. If the old struct is comparable, so must the new one be. This ensures that equality
+// expressions and uses of struct values as map keys continue to compile.
+//
+// An unexported embedded struct can't appear in a struct literal outside the
+// package, so it doesn't have to be present, or have the same name, in the new
+// struct.
+//
+// Field tags are ignored: they have no compile-time implications.
+func (d *differ) checkCompatibleStruct(obj objectWithSide, old, new *types.Struct) {
+ d.checkCompatibleObjectSets(obj, exportedFields(old), exportedFields(new))
+ d.checkCompatibleObjectSets(obj, exportedSelectableFields(old), exportedSelectableFields(new))
+ // Removing comparability from a struct is an incompatible change.
+ if types.Comparable(old) && !types.Comparable(new) {
+ d.incompatible(obj, "", "old is comparable, new is not")
+ }
+}
+
+// exportedFields collects all the immediate fields of the struct that are exported.
+// This is also the set of exported keys for keyed struct literals.
+func exportedFields(s *types.Struct) map[string]types.Object {
+ m := map[string]types.Object{}
+ for i := 0; i < s.NumFields(); i++ {
+ f := s.Field(i)
+ if f.Exported() {
+ m[f.Name()] = f
+ }
+ }
+ return m
+}
+
+// exportedSelectableFields collects all the exported fields of the struct, including
+// exported fields of embedded structs.
+//
+// We traverse the struct breadth-first, because of the rule that a lower-depth field
+// shadows one at a higher depth.
+func exportedSelectableFields(s *types.Struct) map[string]types.Object {
+ var (
+ m = map[string]types.Object{}
+ next []*types.Struct // embedded structs at the next depth
+ seen []*types.Struct // to handle recursive embedding
+ )
+ for cur := []*types.Struct{s}; len(cur) > 0; cur, next = next, nil {
+ seen = append(seen, cur...)
+ // We only want to consider unambiguous fields. Ambiguous fields (where there
+ // is more than one field of the same name at the same level) are legal, but
+ // cannot be selected.
+ for name, f := range unambiguousFields(cur) {
+ // Record an exported field we haven't seen before. If we have seen it,
+ // it occurred a lower depth, so it shadows this field.
+ if f.Exported() && m[name] == nil {
+ m[name] = f
+ }
+ // Remember embedded structs for processing at the next depth,
+ // but only if we haven't seen the struct at this depth or above.
+ if !f.Anonymous() {
+ continue
+ }
+ t := f.Type().Underlying()
+ if p, ok := t.(*types.Pointer); ok {
+ t = p.Elem().Underlying()
+ }
+ if t, ok := t.(*types.Struct); ok && !contains(seen, t) {
+ next = append(next, t)
+ }
+ }
+ }
+ return m
+}
+
+func contains(ts []*types.Struct, t *types.Struct) bool {
+ for _, s := range ts {
+ if types.Identical(s, t) {
+ return true
+ }
+ }
+ return false
+}
+
+// Given a set of structs at the same depth, the unambiguous fields are the ones whose
+// names appear exactly once.
+func unambiguousFields(structs []*types.Struct) map[string]*types.Var {
+ fields := map[string]*types.Var{}
+ seen := map[string]bool{}
+ for _, s := range structs {
+ for i := 0; i < s.NumFields(); i++ {
+ f := s.Field(i)
+ name := f.Name()
+ if seen[name] {
+ delete(fields, name)
+ } else {
+ seen[name] = true
+ fields[name] = f
+ }
+ }
+ }
+ return fields
+}
+
+// Anything removed or change from the old set is an incompatible change.
+// Anything added to the new set is a compatible change.
+func (d *differ) checkCompatibleObjectSets(obj objectWithSide, old, new map[string]types.Object) {
+ for name, oldo := range old {
+ newo := new[name]
+ if newo == nil {
+ d.incompatible(obj, name, "removed")
+ } else {
+ d.checkCorrespondence(obj, name, oldo.Type(), newo.Type())
+ }
+ }
+ for name := range new {
+ if old[name] == nil {
+ d.compatible(obj, name, "added")
+ }
+ }
+}
+
+func (d *differ) checkCompatibleDefined(otn *types.TypeName, old *types.Named, new types.Type) {
+ // We've already checked that old and new correspond.
+ d.checkCompatible(otn, old.Underlying(), new.Underlying())
+ // If there are different kinds of types (e.g. struct and interface), don't bother checking
+ // the method sets.
+ if reflect.TypeOf(old.Underlying()) != reflect.TypeOf(new.Underlying()) {
+ return
+ }
+ // Interface method sets are checked in checkCompatibleInterface.
+ if _, ok := old.Underlying().(*types.Interface); ok {
+ return
+ }
+
+ // A new method set is compatible with an old if the new exported methods are a superset of the old.
+ d.checkMethodSet(otn, old, new, additionsCompatible)
+ d.checkMethodSet(otn, types.NewPointer(old), types.NewPointer(new), additionsCompatible)
+}
+
+const (
+ additionsCompatible = true
+ additionsIncompatible = false
+)
+
+func (d *differ) checkMethodSet(otn *types.TypeName, oldt, newt types.Type, addcompat bool) {
+ // TODO: find a way to use checkCompatibleObjectSets for this.
+ oldMethodSet := exportedMethods(oldt)
+ newMethodSet := exportedMethods(newt)
+ msname := otn.Name()
+ if _, ok := oldt.(*types.Pointer); ok {
+ msname = "*" + msname
+ }
+ for name, oldMethod := range oldMethodSet {
+ newMethod := newMethodSet[name]
+ if newMethod == nil {
+ var part string
+ // Due to embedding, it's possible that the method's receiver type is not
+ // the same as the defined type whose method set we're looking at. So for
+ // a type T with removed method M that is embedded in some other type U,
+ // we will generate two "removed" messages for T.M, one for its own type
+ // T and one for the embedded type U. We want both messages to appear,
+ // but the messageSet dedup logic will allow only one message for a given
+ // object. So use the part string to distinguish them.
+ if receiverNamedType(oldMethod).Obj() != otn {
+ part = fmt.Sprintf(", method set of %s", msname)
+ }
+ d.incompatible(objectWithSide{oldMethod, false}, part, "removed")
+ } else {
+ obj := objectWithSide{oldMethod, false}
+ // If a value method is changed to a pointer method and has a signature
+ // change, then we can get two messages for the same method definition: one
+ // for the value method set that says it's removed, and another for the
+ // pointer method set that says it changed. To keep both messages (since
+ // messageSet dedups), use newMethod for the second. (Slight hack.)
+ if !hasPointerReceiver(oldMethod) && hasPointerReceiver(newMethod) {
+ obj = objectWithSide{newMethod, true}
+ }
+ d.checkCorrespondence(obj, "", oldMethod.Type(), newMethod.Type())
+ }
+ }
+
+ // Check for added methods.
+ for name, newMethod := range newMethodSet {
+ if oldMethodSet[name] == nil {
+ if addcompat {
+ d.compatible(objectWithSide{newMethod, true}, "", "added")
+ } else {
+ d.incompatible(objectWithSide{newMethod, true}, "", "added")
+ }
+ }
+ }
+}
+
+// exportedMethods collects all the exported methods of type's method set.
+func exportedMethods(t types.Type) map[string]types.Object {
+ m := map[string]types.Object{}
+ ms := types.NewMethodSet(t)
+ for i := 0; i < ms.Len(); i++ {
+ obj := ms.At(i).Obj()
+ if obj.Exported() {
+ m[obj.Name()] = obj
+ }
+ }
+ return m
+}
+
+func receiverType(method types.Object) types.Type {
+ return method.Type().(*types.Signature).Recv().Type()
+}
+
+func receiverNamedType(method types.Object) *types.Named {
+ switch t := receiverType(method).(type) {
+ case *types.Pointer:
+ return t.Elem().(*types.Named)
+ case *types.Named:
+ return t
+ default:
+ panic("unreachable")
+ }
+}
+
+func hasPointerReceiver(method types.Object) bool {
+ _, ok := receiverType(method).(*types.Pointer)
+ return ok
+}
diff --git a/apidiff/correspondence.go b/apidiff/correspondence.go
new file mode 100644
index 0000000..ffc422a
--- /dev/null
+++ b/apidiff/correspondence.go
@@ -0,0 +1,303 @@
+package apidiff
+
+import (
+ "fmt"
+ "go/types"
+ "sort"
+)
+
+// Two types are correspond if they are identical except for defined types,
+// which must correspond.
+//
+// Two defined types correspond if they can be interchanged in the old and new APIs,
+// possibly after a renaming.
+//
+// This is not a pure function. If we come across named types while traversing,
+// we establish correspondence.
+func (d *differ) correspond(old, new types.Type) bool {
+ return d.corr(old, new, nil)
+}
+
+// corr determines whether old and new correspond. The argument p is a list of
+// known interface identities, to avoid infinite recursion.
+//
+// corr calls itself recursively as much as possible, to establish more
+// correspondences and so check more of the API. E.g. if the new function has more
+// parameters than the old, compare all the old ones before returning false.
+//
+// Compare this to the implementation of go/types.Identical.
+func (d *differ) corr(old, new types.Type, p *ifacePair) bool {
+ // Structure copied from types.Identical.
+ switch old := old.(type) {
+ case *types.Basic:
+ if new, ok := new.(*types.Basic); ok {
+ return old.Kind() == new.Kind()
+ }
+
+ case *types.Array:
+ if new, ok := new.(*types.Array); ok {
+ return d.corr(old.Elem(), new.Elem(), p) && old.Len() == new.Len()
+ }
+
+ case *types.Slice:
+ if new, ok := new.(*types.Slice); ok {
+ return d.corr(old.Elem(), new.Elem(), p)
+ }
+
+ case *types.Map:
+ if new, ok := new.(*types.Map); ok {
+ return d.corr(old.Key(), new.Key(), p) && d.corr(old.Elem(), new.Elem(), p)
+ }
+
+ case *types.Chan:
+ if new, ok := new.(*types.Chan); ok {
+ return d.corr(old.Elem(), new.Elem(), p) && old.Dir() == new.Dir()
+ }
+
+ case *types.Pointer:
+ if new, ok := new.(*types.Pointer); ok {
+ return d.corr(old.Elem(), new.Elem(), p)
+ }
+
+ case *types.Signature:
+ if new, ok := new.(*types.Signature); ok {
+ pe := d.corr(old.Params(), new.Params(), p)
+ re := d.corr(old.Results(), new.Results(), p)
+ return old.Variadic() == new.Variadic() && pe && re
+ }
+
+ case *types.Tuple:
+ if new, ok := new.(*types.Tuple); ok {
+ for i := 0; i < old.Len(); i++ {
+ if i >= new.Len() || !d.corr(old.At(i).Type(), new.At(i).Type(), p) {
+ return false
+ }
+ }
+ return old.Len() == new.Len()
+ }
+
+ case *types.Struct:
+ if new, ok := new.(*types.Struct); ok {
+ for i := 0; i < old.NumFields(); i++ {
+ if i >= new.NumFields() {
+ return false
+ }
+ of := old.Field(i)
+ nf := new.Field(i)
+ if of.Anonymous() != nf.Anonymous() ||
+ old.Tag(i) != new.Tag(i) ||
+ !d.corr(of.Type(), nf.Type(), p) ||
+ !d.corrFieldNames(of, nf) {
+ return false
+ }
+ }
+ return old.NumFields() == new.NumFields()
+ }
+
+ case *types.Interface:
+ if new, ok := new.(*types.Interface); ok {
+ // Deal with circularity. See the comment in types.Identical.
+ q := &ifacePair{old, new, p}
+ for p != nil {
+ if p.identical(q) {
+ return true // same pair was compared before
+ }
+ p = p.prev
+ }
+ oldms := d.sortedMethods(old)
+ newms := d.sortedMethods(new)
+ for i, om := range oldms {
+ if i >= len(newms) {
+ return false
+ }
+ nm := newms[i]
+ if d.methodID(om) != d.methodID(nm) || !d.corr(om.Type(), nm.Type(), q) {
+ return false
+ }
+ }
+ return old.NumMethods() == new.NumMethods()
+ }
+
+ case *types.Named:
+ return d.establishCorrespondence(old, new)
+
+ case *types.TypeParam:
+ if new, ok := new.(*types.TypeParam); ok {
+ if old.Index() == new.Index() {
+ return true
+ }
+ }
+
+ default:
+ panic(fmt.Sprintf("unknown type kind %T", old))
+ }
+ return false
+}
+
+// Compare old and new field names. We are determining correspondence across packages,
+// so just compare names, not packages. For an unexported, embedded field of named
+// type (non-named embedded fields are possible with aliases), we check that the type
+// names correspond. We check the types for correspondence before this is called, so
+// we've established correspondence.
+func (d *differ) corrFieldNames(of, nf *types.Var) bool {
+ if of.Anonymous() && nf.Anonymous() && !of.Exported() && !nf.Exported() {
+ if on, ok := of.Type().(*types.Named); ok {
+ nn := nf.Type().(*types.Named)
+ return d.establishCorrespondence(on, nn)
+ }
+ }
+ return of.Name() == nf.Name()
+}
+
+// establishCorrespondence records and validates a correspondence between
+// old and new.
+//
+// If this is the first type corresponding to old, it checks that the type
+// declaration is compatible with old and records its correspondence.
+// Otherwise, it checks that new is equivalent to the previously recorded
+// type corresponding to old.
+func (d *differ) establishCorrespondence(old *types.Named, new types.Type) bool {
+ oldname := old.Obj()
+ // If there already is a corresponding new type for old, check that they
+ // are the same.
+ if c := d.correspondMap.At(old); c != nil {
+ return types.Identical(c.(types.Type), new)
+ }
+ // Attempt to establish a correspondence.
+ // Assume the types don't correspond unless they have the same
+ // ID, or are from the old and new packages, respectively.
+ //
+ // This is too conservative. For instance,
+ // [old] type A = q.B; [new] type A q.C
+ // could be OK if in package q, B is an alias for C.
+ // Or, using p as the name of the current old/new packages:
+ // [old] type A = q.B; [new] type A int
+ // could be OK if in q,
+ // [old] type B int; [new] type B = p.A
+ // In this case, p.A and q.B name the same type in both old and new worlds.
+ // Note that this case doesn't imply circular package imports: it's possible
+ // that in the old world, p imports q, but in the new, q imports p.
+ //
+ // However, if we didn't do something here, then we'd incorrectly allow cases
+ // like the first one above in which q.B is not an alias for q.C
+ //
+ // What we should do is check that the old type, in the new world's package
+ // of the same path, doesn't correspond to something other than the new type.
+ // That is a bit hard, because there is no easy way to find a new package
+ // matching an old one.
+ switch new := new.(type) {
+ case *types.Named:
+ newn := new
+ oobj := old.Obj()
+ nobj := newn.Obj()
+ if oobj.Pkg() != d.old || nobj.Pkg() != d.new {
+ // Compare the fully qualified names of the types.
+ //
+ // TODO(jba): when comparing modules, we should only look at the
+ // paths relative to the module path, because the module paths may differ.
+ // See cmd/gorelease/testdata/internalcompat.
+ var opath, npath string
+ if oobj.Pkg() != nil {
+ opath = oobj.Pkg().Path()
+ }
+ if nobj.Pkg() != nil {
+ npath = nobj.Pkg().Path()
+ }
+ return oobj.Name() == nobj.Name() && opath == npath
+ }
+ // Two generic named types correspond if their type parameter lists correspond.
+ // Since one or the other of those lists will be empty, it doesn't hurt
+ // to check both.
+ oldOrigin := old.Origin()
+ newOrigin := newn.Origin()
+ if oldOrigin != old {
+ // old is an instantiated type.
+ if newOrigin == newn {
+ // new is not; they cannot correspond.
+ return false
+ }
+ // Two instantiated types correspond if their origins correspond and
+ // their type argument lists correspond.
+ if !d.correspond(oldOrigin, newOrigin) {
+ return false
+ }
+ if !d.typeListsCorrespond(old.TypeArgs(), newn.TypeArgs()) {
+ return false
+ }
+ } else {
+ if !d.typeParamListsCorrespond(old.TypeParams(), newn.TypeParams()) {
+ return false
+ }
+ }
+ case *types.Basic:
+ if old.Obj().Pkg() != d.old {
+ // A named type from a package other than old never corresponds to a basic type.
+ return false
+ }
+ default:
+ // Only named and basic types can correspond.
+ return false
+ }
+ // If there is no correspondence, create one.
+ d.correspondMap.Set(old, new)
+ // Check that the corresponding types are compatible.
+ d.checkCompatibleDefined(oldname, old, new)
+ return true
+}
+
+func (d *differ) typeListsCorrespond(tl1, tl2 *types.TypeList) bool {
+ if tl1.Len() != tl2.Len() {
+ return false
+ }
+ for i := 0; i < tl1.Len(); i++ {
+ if !d.correspond(tl1.At(i), tl2.At(i)) {
+ return false
+ }
+ }
+ return true
+}
+
+// Two list of type parameters correspond if they are the same length, and
+// the constraints of corresponding type parameters correspond.
+func (d *differ) typeParamListsCorrespond(tps1, tps2 *types.TypeParamList) bool {
+ if tps1.Len() != tps2.Len() {
+ return false
+ }
+ for i := 0; i < tps1.Len(); i++ {
+ if !d.correspond(tps1.At(i).Constraint(), tps2.At(i).Constraint()) {
+ return false
+ }
+ }
+ return true
+}
+
+func (d *differ) sortedMethods(iface *types.Interface) []*types.Func {
+ ms := make([]*types.Func, iface.NumMethods())
+ for i := 0; i < iface.NumMethods(); i++ {
+ ms[i] = iface.Method(i)
+ }
+ sort.Slice(ms, func(i, j int) bool { return d.methodID(ms[i]) < d.methodID(ms[j]) })
+ return ms
+}
+
+func (d *differ) methodID(m *types.Func) string {
+ // If the method belongs to one of the two packages being compared, use
+ // just its name even if it's unexported. That lets us treat unexported names
+ // from the old and new packages as equal.
+ if m.Pkg() == d.old || m.Pkg() == d.new {
+ return m.Name()
+ }
+ return m.Id()
+}
+
+// Copied from the go/types package:
+
+// An ifacePair is a node in a stack of interface type pairs compared for identity.
+type ifacePair struct {
+ x, y *types.Interface
+ prev *ifacePair
+}
+
+func (p *ifacePair) identical(q *ifacePair) bool {
+ return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x
+}
diff --git a/apidiff/messageset.go b/apidiff/messageset.go
new file mode 100644
index 0000000..633ae58
--- /dev/null
+++ b/apidiff/messageset.go
@@ -0,0 +1,111 @@
+// TODO: show that two-non-empty dotjoin can happen, by using an anon struct as a field type
+// TODO: don't report removed/changed methods for both value and pointer method sets?
+
+package apidiff
+
+import (
+ "fmt"
+ "go/types"
+ "sort"
+ "strings"
+)
+
+// objectWithSide contains an object, and information on which side (old or new)
+// of the comparison it relates to. This matters when need to express the object's
+// package path, relative to the root path of the comparison, as the old and new
+// sides can have different roots (e.g. comparing somepackage/v2 vs. somepackage/v3).
+type objectWithSide struct {
+ object types.Object
+ isNew bool
+}
+
+// There can be at most one message for each object or part thereof.
+// Parts include interface methods and struct fields.
+//
+// The part thing is necessary. Method (Func) objects have sufficient info, but field
+// Vars do not: they just have a field name and a type, without the enclosing struct.
+type messageSet map[objectWithSide]map[string]string
+
+// Add a message for obj and part, overwriting a previous message
+// (shouldn't happen).
+// obj is required but part can be empty.
+func (m messageSet) add(obj objectWithSide, part, msg string) {
+ s := m[obj]
+ if s == nil {
+ s = map[string]string{}
+ m[obj] = s
+ }
+ if f, ok := s[part]; ok && f != msg {
+ fmt.Printf("! second, different message for obj %s, isNew %v, part %q\n", obj.object, obj.isNew, part)
+ fmt.Printf(" first: %s\n", f)
+ fmt.Printf(" second: %s\n", msg)
+ }
+ s[part] = msg
+}
+
+func (m messageSet) collect(oldRootPackagePath, newRootPackagePath string) []string {
+ var s []string
+ for obj, parts := range m {
+ rootPackagePath := oldRootPackagePath
+ if obj.isNew {
+ rootPackagePath = newRootPackagePath
+ }
+
+ // Format each object name relative to its own package.
+ objstring := objectString(obj.object, rootPackagePath)
+ for part, msg := range parts {
+ var p string
+
+ if strings.HasPrefix(part, ",") {
+ p = objstring + part
+ } else {
+ p = dotjoin(objstring, part)
+ }
+ s = append(s, p+": "+msg)
+ }
+ }
+ sort.Strings(s)
+ return s
+}
+
+func objectString(obj types.Object, rootPackagePath string) string {
+ thisPackagePath := obj.Pkg().Path()
+
+ var packagePrefix string
+ if thisPackagePath == rootPackagePath {
+ // obj is in same package as the diff operation root - no prefix
+ packagePrefix = ""
+ } else if strings.HasPrefix(thisPackagePath, rootPackagePath+"/") {
+ // obj is in a child package compared to the diff operation root - use a
+ // prefix starting with "./" to emphasise the relative nature
+ packagePrefix = "./" + thisPackagePath[len(rootPackagePath)+1:] + "."
+ } else {
+ // obj is outside the diff operation root - display full path. This can
+ // happen if there is a need to report a change in a type in an unrelated
+ // package, because it has been used as the underlying type in a type
+ // definition in the package being processed, for example.
+ packagePrefix = thisPackagePath + "."
+ }
+
+ if f, ok := obj.(*types.Func); ok {
+ sig := f.Type().(*types.Signature)
+ if recv := sig.Recv(); recv != nil {
+ tn := types.TypeString(recv.Type(), types.RelativeTo(obj.Pkg()))
+ if tn[0] == '*' {
+ tn = "(" + tn + ")"
+ }
+ return fmt.Sprintf("%s%s.%s", packagePrefix, tn, obj.Name())
+ }
+ }
+ return fmt.Sprintf("%s%s", packagePrefix, obj.Name())
+}
+
+func dotjoin(s1, s2 string) string {
+ if s1 == "" {
+ return s2
+ }
+ if s2 == "" {
+ return s1
+ }
+ return s1 + "." + s2
+}
diff --git a/apidiff/report.go b/apidiff/report.go
new file mode 100644
index 0000000..ce79e27
--- /dev/null
+++ b/apidiff/report.go
@@ -0,0 +1,71 @@
+package apidiff
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+)
+
+// Report describes the changes detected by Changes.
+type Report struct {
+ Changes []Change
+}
+
+// A Change describes a single API change.
+type Change struct {
+ Message string
+ Compatible bool
+}
+
+func (r Report) messages(compatible bool) []string {
+ var msgs []string
+ for _, c := range r.Changes {
+ if c.Compatible == compatible {
+ msgs = append(msgs, c.Message)
+ }
+ }
+ return msgs
+}
+
+func (r Report) String() string {
+ var buf bytes.Buffer
+ if err := r.Text(&buf); err != nil {
+ return fmt.Sprintf("!!%v", err)
+ }
+ return buf.String()
+}
+
+func (r Report) Text(w io.Writer) error {
+ if err := r.TextIncompatible(w, true); err != nil {
+ return err
+ }
+ return r.TextCompatible(w)
+}
+
+func (r Report) TextIncompatible(w io.Writer, withHeader bool) error {
+ if withHeader {
+ return r.writeMessages(w, "Incompatible changes:", r.messages(false))
+ }
+ return r.writeMessages(w, "", r.messages(false))
+}
+
+func (r Report) TextCompatible(w io.Writer) error {
+ return r.writeMessages(w, "Compatible changes:", r.messages(true))
+}
+
+func (r Report) writeMessages(w io.Writer, header string, msgs []string) error {
+ if len(msgs) == 0 {
+ return nil
+ }
+ if header != "" {
+ if _, err := fmt.Fprintf(w, "%s\n", header); err != nil {
+ return err
+ }
+ }
+ for _, m := range msgs {
+ if _, err := fmt.Fprintf(w, "- %s\n", m); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/apidiff/testdata/README.md b/apidiff/testdata/README.md
new file mode 100644
index 0000000..8029a13
--- /dev/null
+++ b/apidiff/testdata/README.md
@@ -0,0 +1,12 @@
+The .go files in this directory are split into two packages, old and new.
+They are syntactically valid Go so that gofmt can process them.
+
+```
+If a comment begins with: Then:
+old write subsequent lines to the "old" package
+new write subsequent lines to the "new" package
+both write subsequent lines to both packages
+c expect a compatible error with the following text
+i expect an incompatible error with the following text
+
+```
diff --git a/apidiff/testdata/aliases.go b/apidiff/testdata/aliases.go
new file mode 100644
index 0000000..6681948
--- /dev/null
+++ b/apidiff/testdata/aliases.go
@@ -0,0 +1,29 @@
+package p
+
+// Here the same alias refers to different types in old and new.
+// We correctly detect the problem, but the message is poor.
+
+// both
+type t1 int
+type t2 bool
+
+// old
+type A = t1
+
+// new
+// i t1: changed from int to bool
+type A = t2
+
+// old
+type B = int
+
+// new
+// i B: changed from int to B
+type B int
+
+// old
+type C int
+
+// new
+// OK: merging types
+type C = int
diff --git a/apidiff/testdata/basics.go b/apidiff/testdata/basics.go
new file mode 100644
index 0000000..94c0940
--- /dev/null
+++ b/apidiff/testdata/basics.go
@@ -0,0 +1,110 @@
+package p
+
+// Same type in both: OK.
+// both
+type A int
+
+// Changing the type is an incompatible change.
+// old
+type B int
+
+// new
+// i B: changed from int to string
+type B string
+
+// Adding a new type, whether alias or not, is a compatible change.
+// new
+// c AA: added
+type AA = A
+
+// c B1: added
+type B1 bool
+
+// Change of type for an unexported name doesn't matter...
+// old
+type t int
+
+// new
+type t string // OK: t isn't part of the API
+
+// ...unless it is exposed.
+// both
+var V2 u
+
+// old
+type u string
+
+// new
+// i u: changed from string to int
+type u int
+
+// An exposed, unexported type can be renamed.
+// both
+type u2 int
+
+// old
+type u1 int
+
+var V5 u1
+
+// new
+var V5 u2 // OK: V5 has changed type, but old u1 corresopnds to new u2
+
+// Splitting a single type into two is an incompatible change.
+// both
+type u3 int
+
+// old
+type (
+ Split1 = u1
+ Split2 = u1
+)
+
+// new
+type (
+ Split1 = u2 // OK, since old u1 corresponds to new u2
+
+ // This tries to make u1 correspond to u3
+ // i Split2: changed from u1 to u3
+ Split2 = u3
+)
+
+// Merging two types into one is OK.
+// old
+type (
+ GoodMerge1 = u2
+ GoodMerge2 = u3
+)
+
+// new
+type (
+ GoodMerge1 = u3
+ GoodMerge2 = u3
+)
+
+// Merging isn't OK here because a method is lost.
+// both
+type u4 int
+
+func (u4) M() {}
+
+// old
+type (
+ BadMerge1 = u3
+ BadMerge2 = u4
+)
+
+// new
+type (
+ BadMerge1 = u3
+ // i u4.M: removed
+ // What's really happening here is that old u4 corresponds to new u3,
+ // and new u3's method set is not a superset of old u4's.
+ BadMerge2 = u3
+)
+
+// old
+type Rem int
+
+// new
+// i Rem: removed
diff --git a/apidiff/testdata/constants.go b/apidiff/testdata/constants.go
new file mode 100644
index 0000000..d3f40da
--- /dev/null
+++ b/apidiff/testdata/constants.go
@@ -0,0 +1,53 @@
+package p
+
+// old
+type u1 int
+
+// both
+type u2 int
+
+// type changes
+// old
+
+const (
+ C1 = 1
+ C2 int = 2
+ C3 = 3
+ C4 u1 = 4
+)
+
+var V8 int
+
+// new
+const (
+ // i C1: changed from untyped int to untyped string
+ C1 = "1"
+ // i C2: changed from int to untyped int
+ C2 = -1
+ // i C3: changed from untyped int to int
+ C3 int = 3
+ // i V8: changed from var to const
+ V8 int = 1
+ C4 u2 = 4 // OK: u1 corresponds to u2
+)
+
+// value change
+// old
+const (
+ Cr1 = 1
+ Cr2 = "2"
+ Cr3 = 3.5
+ Cr4 = complex(0, 4.1)
+)
+
+// new
+const (
+ // i Cr1: value changed from 1 to -1
+ Cr1 = -1
+ // i Cr2: value changed from "2" to "3"
+ Cr2 = "3"
+ // i Cr3: value changed from 3.5 to 3.8
+ Cr3 = 3.8
+ // i Cr4: value changed from (0 + 4.1i) to (4.1 + 0i)
+ Cr4 = complex(4.1, 0)
+)
diff --git a/apidiff/testdata/exported_fields/ef.go b/apidiff/testdata/exported_fields/ef.go
new file mode 100644
index 0000000..8e441e2
--- /dev/null
+++ b/apidiff/testdata/exported_fields/ef.go
@@ -0,0 +1,38 @@
+package exported_fields
+
+// Used for testing exportedFields.
+// Its exported fields are:
+//
+// A1 [1]int
+// D bool
+// E int
+// F F
+// S *S
+type (
+ S struct {
+ int
+ *embed2
+ embed
+ E int // shadows embed.E
+ alias
+ A1
+ *S
+ }
+
+ A1 [1]int
+
+ embed struct {
+ E string
+ }
+
+ embed2 struct {
+ embed3
+ F // shadows embed3.F
+ }
+ embed3 struct {
+ F bool
+ }
+ alias = struct{ D bool }
+
+ F int
+)
diff --git a/apidiff/testdata/functions.go b/apidiff/testdata/functions.go
new file mode 100644
index 0000000..2b1ca9f
--- /dev/null
+++ b/apidiff/testdata/functions.go
@@ -0,0 +1,71 @@
+package p
+
+// old
+type u1 int
+
+// both
+type A int
+type u2 int
+
+// new
+// c AA: added
+type AA = A
+
+// old
+func F1(a int, b string) map[u1]A { return nil }
+func F2(int) {}
+func F3(int) {}
+func F4(int) int { return 0 }
+func F5(int) int { return 0 }
+func F6(int) {}
+func F7(interface{}) {}
+
+// new
+func F1(c int, d string) map[u2]AA { return nil } //OK: same (since u1 corresponds to u2)
+
+// i F2: changed from func(int) to func(int) bool
+func F2(int) bool { return true }
+
+// i F3: changed from func(int) to func(int, int)
+func F3(int, int) {}
+
+// i F4: changed from func(int) int to func(bool) int
+func F4(bool) int { return 0 }
+
+// i F5: changed from func(int) int to func(int) string
+func F5(int) string { return "" }
+
+// i F6: changed from func(int) to func(...int)
+func F6(...int) {}
+
+// i F7: changed from func(interface{}) to func(interface{x()})
+func F7(a interface{ x() }) {}
+
+// old
+func F8(bool) {}
+
+// new
+// c F8: changed from func to var
+var F8 func(bool)
+
+// old
+var F9 func(int)
+
+// new
+// i F9: changed from var to func
+func F9(int) {}
+
+// both
+// OK, even though new S is incompatible with old S (see below)
+func F10(S) {}
+
+// old
+type S struct {
+ A int
+}
+
+// new
+type S struct {
+ // i S.A: changed from int to bool
+ A bool
+}
diff --git a/apidiff/testdata/generics.go b/apidiff/testdata/generics.go
new file mode 100644
index 0000000..12e1c78
--- /dev/null
+++ b/apidiff/testdata/generics.go
@@ -0,0 +1,84 @@
+package p
+
+//// Generics
+
+// old
+type G[T any] []T
+
+// new
+// OK: param name change
+type G[A any] []A
+
+// old
+type GM[A, B comparable] map[A]B
+
+// new
+// i GM: changed from map[A]B to map[B]A
+type GM[A, B comparable] map[B]A
+
+// old
+type GT[V any] struct {
+}
+
+func (GT[V]) M(*GT[V]) {}
+
+// new
+// OK
+type GT[V any] struct {
+}
+
+func (GT[V]) M(*GT[V]) {}
+
+// old
+type GT2[V any] struct {
+}
+
+func (GT2[V]) M(*GT2[V]) {}
+
+// new
+// i GT2: changed from GT2[V any] to GT2[V comparable]
+type GT2[V comparable] struct {
+}
+
+func (GT2[V]) M(*GT2[V]) {}
+
+// both
+type custom interface {
+ int
+}
+
+type GT3[E custom] map[E]int
+
+// Instantiated types:
+// Two instantiations of generic types
+// with different type parameters are different.
+
+// both
+type H[T any] []T
+
+// old
+var V1 H[int]
+
+type T int
+
+var V2 H[T]
+
+// new
+// i V1: changed from H[int] to H[bool]
+var V1 H[bool]
+
+// i T: changed from int to bool
+type T bool
+
+// OK: we reported on T, so we don't need to here.
+var V2 H[T]
+
+// old
+type t int
+
+// new
+// i t: changed from int to byte
+type t byte
+
+// both
+var V3 H[t]
diff --git a/apidiff/testdata/method_sets.go b/apidiff/testdata/method_sets.go
new file mode 100644
index 0000000..5d0dbb5
--- /dev/null
+++ b/apidiff/testdata/method_sets.go
@@ -0,0 +1,118 @@
+package p
+
+// old
+type SM struct {
+ embedm
+ Embedm
+}
+
+func (SM) V1() {}
+func (SM) V2() {}
+func (SM) V3() {}
+func (SM) V4() {}
+func (SM) v() {}
+
+func (*SM) P1() {}
+func (*SM) P2() {}
+func (*SM) P3() {}
+func (*SM) P4() {}
+func (*SM) p() {}
+
+type embedm int
+
+func (embedm) EV1() {}
+func (embedm) EV2() {}
+func (embedm) EV3() {}
+func (*embedm) EP1() {}
+func (*embedm) EP2() {}
+func (*embedm) EP3() {}
+
+type Embedm struct {
+ A int
+}
+
+func (Embedm) FV() {}
+func (*Embedm) FP() {}
+
+type RepeatEmbedm struct {
+ Embedm
+}
+
+// new
+type SM struct {
+ embedm2
+ embedm3
+ Embedm
+ // i SM.A: changed from int to bool
+}
+
+// c SMa: added
+type SMa = SM
+
+func (SM) V1() {} // OK: same
+
+// func (SM) V2() {}
+// i SM.V2: removed
+
+// i SM.V3: changed from func() to func(int)
+func (SM) V3(int) {}
+
+// c SM.V5: added
+func (SM) V5() {}
+
+func (SM) v(int) {} // OK: unexported method change
+func (SM) v2() {} // OK: unexported method added
+
+func (*SM) P1() {} // OK: same
+//func (*SM) P2() {}
+// i (*SM).P2: removed
+
+// i (*SM).P3: changed from func() to func(int)
+func (*SMa) P3(int) {}
+
+// c (*SM).P5: added
+func (*SM) P5() {}
+
+// func (*SM) p() {} // OK: unexported method removed
+
+// Changing from a value to a pointer receiver or vice versa
+// just looks like adding and removing a method.
+
+// i SM.V4: removed
+// i (*SM).V4: changed from func() to func(int)
+func (*SM) V4(int) {}
+
+// c SM.P4: added
+// P4 is not removed from (*SM) because value methods
+// are in the pointer method set.
+func (SM) P4() {}
+
+type embedm2 int
+
+// i embedm.EV1: changed from func() to func(int)
+func (embedm2) EV1(int) {}
+
+// i embedm.EV2, method set of SM: removed
+// i embedm.EV2, method set of *SM: removed
+
+// i (*embedm).EP2, method set of *SM: removed
+func (*embedm2) EP1() {}
+
+type embedm3 int
+
+func (embedm3) EV3() {} // OK: compatible with old embedm.EV3
+func (*embedm3) EP3() {} // OK: compatible with old (*embedm).EP3
+
+type Embedm struct {
+ // i Embedm.A: changed from int to bool
+ A bool
+}
+
+// i Embedm.FV: changed from func() to func(int)
+func (Embedm) FV(int) {}
+func (*Embedm) FP() {}
+
+type RepeatEmbedm struct {
+ // i RepeatEmbedm.A: changed from int to bool
+ Embedm
+}
diff --git a/apidiff/testdata/multi_levels.go b/apidiff/testdata/multi_levels.go
new file mode 100644
index 0000000..9d763cc
--- /dev/null
+++ b/apidiff/testdata/multi_levels.go
@@ -0,0 +1,31 @@
+package p
+
+// This verifies that the code works even through
+// multiple levels of unexported types.
+
+// old
+var Z w
+
+type w []x
+type x []z
+type z int
+
+// new
+var Z w
+
+type w []x
+type x []z
+
+// i z: changed from int to bool
+type z bool
+
+// old
+type H struct{}
+
+func (H) M() {}
+
+// new
+// i H: changed from struct{} to interface{M()}
+type H interface {
+ M()
+}
diff --git a/apidiff/testdata/order.go b/apidiff/testdata/order.go
new file mode 100644
index 0000000..f5b2adf
--- /dev/null
+++ b/apidiff/testdata/order.go
@@ -0,0 +1,37 @@
+package p
+
+// apidiff's output (but not its correctness) could depend on the order
+// in which declarations were visited.
+
+// For example, this always correctly reported that U changed from T to U:
+
+// old
+type U = T
+
+// new
+// i U: changed from T to U
+type U T
+
+// both
+type T int
+
+// But the test below, which is just a renaming of the one above, would report
+// that B was changed from B to B! apidiff was not wrong--there is an
+// incompatibility here--but it expressed itself poorly.
+//
+// This happened because old.A was processed first (Scope.Names returns names
+// sorted), resulting in old.B corresponding with new.A. Later, when old.B
+// was processed, it was matched with new.B. But since there was already a
+// correspondence for old.B, the blame for the incompatibility was put on new.B.
+
+// The fix is to establish the correspondence between same-named types first.
+
+// old
+type A = B
+
+// new
+// i A: changed from B to A
+type A B
+
+// both
+type B int
diff --git a/apidiff/testdata/other_packages.go b/apidiff/testdata/other_packages.go
new file mode 100644
index 0000000..f4d1878
--- /dev/null
+++ b/apidiff/testdata/other_packages.go
@@ -0,0 +1,44 @@
+// These tests demonstrate the correct handling of symbols
+// in packages other than two being compared.
+// See the lines in establishCorrespondence beginning
+//
+// if newn, ok := new.(*types.Named); ok
+package p
+
+// both
+
+// gofmt insists on grouping imports, so old and new
+// must both have both imports.
+import (
+ "io"
+ "text/tabwriter"
+)
+
+// Here we have two named types in different packages.
+// They have the same package-relative name, but we compare
+// the package-qualified names.
+
+// old
+var V io.Writer
+var _ tabwriter.Writer
+
+// new
+// i V: changed from io.Writer to text/tabwriter.Writer
+var V tabwriter.Writer
+var _ io.Writer
+
+// Here one type is a basic type.
+// Example from https://go.dev/issue/61385.
+// apidiff would previously report
+// F2: changed from func(io.ReadCloser) to func(io.ReadCloser)
+// io.ReadCloser: changed from interface{io.Reader; io.Closer} to int
+
+// old
+func F1(io.ReadCloser) {}
+
+// new
+// i F1: changed from func(io.ReadCloser) to func(int)
+func F1(int) {}
+
+// both
+func F2(io.ReadCloser) {}
diff --git a/apidiff/testdata/splitting.go b/apidiff/testdata/splitting.go
new file mode 100644
index 0000000..2ef6b06
--- /dev/null
+++ b/apidiff/testdata/splitting.go
@@ -0,0 +1,77 @@
+package p
+
+// Splitting types
+//
+// In the old world, there is one type with two names, one of which is an alias.
+// In the new world, there are two distinct types.
+//
+// That is an incompatible change, because client code like
+//
+// var v *T1 = new(T2)
+//
+// will succeed in the old world, where T1 and T2 name the same type,
+// but fail in the new world.
+
+// OK: in both old and new, A, B, and C all name the same type.
+// old
+type (
+ A = B
+ B = C
+ C int
+)
+
+// new
+type (
+ A = B
+ B int
+ C = A
+)
+
+// An example of splitting:
+
+// Old has one type, D; new has E and D.
+// both
+type D int
+
+// old
+type E = D
+
+// new
+// i E: changed from D to E
+type E D // old D corresponds with new E
+// old D also corresponds with new D: problem
+
+// Here we have a benign split.
+// f and g are the same type in old and different types in new.
+// But clients have no way of constructing an expression of type f,
+// so they cannot write code that breaks.
+
+// both
+type f int
+
+var Vg g // expose g
+
+// old
+type g = f
+
+// new
+// OK: f isn't exposed
+type g f
+
+// Here we have another incompatible split, even
+// though the type names are unexported. The problem
+// is that both names are exposed via exported variables.
+
+// both
+type h int
+
+var Vj j // expose j
+var Vh h // expose h
+
+// old
+type j = h
+
+// new
+// i Vj: changed from h to j
+// e.g. p.Vj = p.Vh
+type j h
diff --git a/apidiff/testdata/structs.go b/apidiff/testdata/structs.go
new file mode 100644
index 0000000..5bd871b
--- /dev/null
+++ b/apidiff/testdata/structs.go
@@ -0,0 +1,143 @@
+package p
+
+// old
+type S1 struct {
+ A int
+ B string
+ C bool
+ d float32
+}
+
+// new
+type S1 = s1
+
+type s1 struct {
+ C chan int
+ // i S1.C: changed from bool to chan int
+ A int
+ // i S1.B: removed
+ // i S1: old is comparable, new is not
+ x []int
+ d float32
+ E bool
+ // c S1.E: added
+}
+
+// old
+type embed struct {
+ E string
+}
+
+type S2 struct {
+ A int
+ embed
+}
+
+// new
+type embedx struct {
+ E string
+}
+
+type S2 struct {
+ embedx // OK: the unexported embedded field changed names, but the exported field didn't
+ A int
+}
+
+// both
+type F int
+
+// old
+type S3 struct {
+ A int
+ embed
+}
+
+// new
+type embed struct{ F int }
+
+type S3 struct {
+ // i S3.E: removed
+ embed
+ // c S3.F: added
+ A int
+}
+
+// both
+type A1 [1]int
+
+// old
+type embed2 struct {
+ embed3
+ F // shadows embed3.F
+}
+
+type embed3 struct {
+ F bool
+}
+
+type alias = struct{ D bool }
+
+type S4 struct {
+ int
+ *embed2
+ embed
+ E int // shadows embed.E
+ alias
+ A1
+ *S4
+}
+
+// new
+type S4 struct {
+ // OK: removed unexported fields
+ // D and F marked as added because they are now part of the immediate fields
+ D bool
+ // c S4.D: added
+ E int // OK: same as in old
+ F F
+ // c S4.F: added
+ A1 // OK: same
+ *S4 // OK: same (recursive embedding)
+}
+
+// Difference between exported selectable fields and exported immediate fields.
+// both
+type S5 struct{ A int }
+
+// old
+// Exported immediate fields: A, S5
+// Exported selectable fields: A int, S5 S5
+type S6 struct {
+ S5 S5
+ A int
+}
+
+// new
+// Exported immediate fields: S5
+// Exported selectable fields: A int, S5 S5.
+
+// i S6.A: removed
+type S6 struct {
+ S5
+}
+
+// Ambiguous fields can exist; they just can't be selected.
+// both
+type (
+ embed7a struct{ E int }
+ embed7b struct{ E bool }
+)
+
+// old
+type S7 struct { // legal, but no selectable fields
+ embed7a
+ embed7b
+}
+
+// new
+type S7 struct {
+ embed7a
+ embed7b
+ // c S7.E: added
+ E string
+}
diff --git a/apidiff/testdata/types.go b/apidiff/testdata/types.go
new file mode 100644
index 0000000..dca94d0
--- /dev/null
+++ b/apidiff/testdata/types.go
@@ -0,0 +1,253 @@
+package p
+
+// both
+import "io"
+
+// old
+type u1 int
+
+// both
+type u2 int
+
+// old
+const C5 = 3
+
+type (
+ A1 [1]int
+ A2 [2]int
+ A3 [C5]int
+)
+
+// new
+// i C5: value changed from 3 to 4
+const C5 = 4
+
+type (
+ A1 [1]int
+ // i A2: changed from [2]int to [2]bool
+ A2 [2]bool
+ // i A3: changed from [3]int to [4]int
+ A3 [C5]int
+)
+
+// old
+type (
+ Sl []int
+ P1 *int
+ P2 *u1
+)
+
+// new
+type (
+ // i Sl: changed from []int to []string
+ Sl []string
+ // i P1: changed from *int to **bool
+ P1 **bool
+ P2 *u2 // OK: u1 corresponds to u2
+)
+
+// old
+type Bc1 int32
+type Bc2 uint
+type Bc3 float32
+type Bc4 complex64
+
+// new
+// c Bc1: changed from int32 to int
+type Bc1 int
+
+// c Bc2: changed from uint to uint64
+type Bc2 uint64
+
+// c Bc3: changed from float32 to float64
+type Bc3 float64
+
+// c Bc4: changed from complex64 to complex128
+type Bc4 complex128
+
+// old
+type Bi1 int32
+type Bi2 uint
+type Bi3 float64
+type Bi4 complex128
+
+// new
+// i Bi1: changed from int32 to int16
+type Bi1 int16
+
+// i Bi2: changed from uint to uint32
+type Bi2 uint32
+
+// i Bi3: changed from float64 to float32
+type Bi3 float32
+
+// i Bi4: changed from complex128 to complex64
+type Bi4 complex64
+
+// old
+type (
+ M1 map[string]int
+ M2 map[string]int
+ M3 map[string]int
+)
+
+// new
+type (
+ M1 map[string]int
+ // i M2: changed from map[string]int to map[int]int
+ M2 map[int]int
+ // i M3: changed from map[string]int to map[string]string
+ M3 map[string]string
+)
+
+// old
+type (
+ Ch1 chan int
+ Ch2 <-chan int
+ Ch3 chan int
+ Ch4 <-chan int
+)
+
+// new
+type (
+ // i Ch1, element type: changed from int to bool
+ Ch1 chan bool
+ // i Ch2: changed direction
+ Ch2 chan<- int
+ // i Ch3: changed direction
+ Ch3 <-chan int
+ // c Ch4: removed direction
+ Ch4 chan int
+)
+
+// old
+type I1 interface {
+ M1()
+ M2()
+}
+
+// new
+type I1 interface {
+ // M1()
+ // i I1.M1: removed
+ M2(int)
+ // i I1.M2: changed from func() to func(int)
+ M3()
+ // i I1.M3: added
+ m()
+ // i I1.m: added unexported method
+}
+
+// old
+type I2 interface {
+ M1()
+ m()
+}
+
+// new
+type I2 interface {
+ M1()
+ // m() Removing an unexported method is OK.
+ m2() // OK, because old already had an unexported method
+ // c I2.M2: added
+ M2()
+}
+
+// old
+type I3 interface {
+ io.Reader
+ M()
+}
+
+// new
+// OK: what matters is the method set; the name of the embedded
+// interface isn't important.
+type I3 interface {
+ M()
+ Read([]byte) (int, error)
+}
+
+// old
+type I4 io.Writer
+
+// new
+// OK: in both, I4 is a distinct type from io.Writer, and
+// the old and new I4s have the same method set.
+type I4 interface {
+ Write([]byte) (int, error)
+}
+
+// old
+type I5 = io.Writer
+
+// new
+// i I5: changed from io.Writer to I5
+// In old, I5 and io.Writer are the same type; in new,
+// they are different. That can break something like:
+//
+// var _ func(io.Writer) = func(pkg.I6) {}
+type I5 io.Writer
+
+// old
+type I6 interface{ Write([]byte) (int, error) }
+
+// new
+// i I6: changed from I6 to io.Writer
+// Similar to the above.
+type I6 = io.Writer
+
+//// correspondence with a basic type
+// Basic types are technically defined types, but they aren't
+// represented that way in go/types, so the cases below are special.
+
+// both
+type T1 int
+
+// old
+var VT1 T1
+
+// new
+// i VT1: changed from T1 to int
+// This fails because old T1 corresponds to both int and new T1.
+var VT1 int
+
+// old
+type t2 int
+
+var VT2 t2
+
+// new
+// OK: t2 corresponds to int. It's fine that old t2
+// doesn't exist in new.
+var VT2 int
+
+// both
+type t3 int
+
+func (t3) M() {}
+
+// old
+var VT3 t3
+
+// new
+// i t3.M: removed
+// Here the change from t3 to int is incompatible
+// because old t3 has an exported method.
+var VT3 int
+
+// old
+var VT4 int
+
+// new
+type t4 int
+
+// i VT4: changed from int to t4
+// This is incompatible because of code like
+//
+// VT4 + int(1)
+//
+// which works in old but fails in new.
+// The difference from the above cases is that
+// in those, we were merging two types into one;
+// here, we are splitting int into t4 and int.
+var VT4 t4
diff --git a/apidiff/testdata/variables.go b/apidiff/testdata/variables.go
new file mode 100644
index 0000000..6c9e1fd
--- /dev/null
+++ b/apidiff/testdata/variables.go
@@ -0,0 +1,72 @@
+package p
+
+// old
+type u1 int
+
+// both
+type A int
+type u2 int
+
+// simple type changes
+// old
+var (
+ V1 string
+ V3 A
+ V7 <-chan int
+)
+
+// new
+var (
+ // i V1: changed from string to []string
+ V1 []string
+ V3 A // OK: same
+ // i V7: changed from <-chan int to chan int
+ V7 chan int
+)
+
+// interface type changes
+// old
+var (
+ V9 interface{ M() }
+ V10 interface{ M() }
+ V11 interface{ M() }
+)
+
+// new
+var (
+ // i V9: changed from interface{M()} to interface{}
+ V9 interface{}
+ // i V10: changed from interface{M()} to interface{M(); M2()}
+ V10 interface {
+ M2()
+ M()
+ }
+ // i V11: changed from interface{M()} to interface{M(int)}
+ V11 interface{ M(int) }
+)
+
+// struct type changes
+// old
+var (
+ VS1 struct{ A, B int }
+ VS2 struct{ A, B int }
+ VS3 struct{ A, B int }
+ VS4 struct {
+ A int
+ u1
+ }
+)
+
+// new
+var (
+ // i VS1: changed from struct{A int; B int} to struct{B int; A int}
+ VS1 struct{ B, A int }
+ // i VS2: changed from struct{A int; B int} to struct{A int}
+ VS2 struct{ A int }
+ // i VS3: changed from struct{A int; B int} to struct{A int; B int; C int}
+ VS3 struct{ A, B, C int }
+ VS4 struct {
+ A int
+ u2
+ }
+)
diff --git a/apidiff/testdata/whole.go b/apidiff/testdata/whole.go
new file mode 100644
index 0000000..eefba21
--- /dev/null
+++ b/apidiff/testdata/whole.go
@@ -0,0 +1,49 @@
+package p
+
+// Whole-package interface satisfaction
+
+// old
+type WI1 interface {
+ M1()
+ m1()
+}
+
+type WI2 interface {
+ M2()
+ m2()
+}
+
+type WS1 int
+
+func (WS1) M1() {}
+func (WS1) m1() {}
+
+type WS2 int
+
+func (WS2) M2() {}
+func (WS2) m2() {}
+
+// new
+type WI1 interface {
+ M1()
+ m()
+}
+
+type WS1 int
+
+func (WS1) M1() {}
+
+// i WS1: no longer implements WI1
+//func (WS1) m1() {}
+
+type WI2 interface {
+ M2()
+ m2()
+ // i WS2: no longer implements WI2
+ m3()
+}
+
+type WS2 int
+
+func (WS2) M2() {}
+func (WS2) m2() {}
diff --git a/cmd/apidiff/main.go b/cmd/apidiff/main.go
new file mode 100644
index 0000000..67261a1
--- /dev/null
+++ b/cmd/apidiff/main.go
@@ -0,0 +1,293 @@
+// Command apidiff determines whether two versions of a package are compatible
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "go/token"
+ "go/types"
+ "os"
+ "strings"
+
+ "golang.org/x/exp/apidiff"
+ "golang.org/x/tools/go/gcexportdata"
+ "golang.org/x/tools/go/packages"
+)
+
+var (
+ exportDataOutfile = flag.String("w", "", "file for export data")
+ incompatibleOnly = flag.Bool("incompatible", false, "display only incompatible changes")
+ allowInternal = flag.Bool("allow-internal", false, "allow apidiff to compare internal packages")
+ moduleMode = flag.Bool("m", false, "compare modules instead of packages")
+)
+
+func main() {
+ flag.Usage = func() {
+ w := flag.CommandLine.Output()
+ fmt.Fprintf(w, "usage:\n")
+ fmt.Fprintf(w, "apidiff OLD NEW\n")
+ fmt.Fprintf(w, " compares OLD and NEW package APIs\n")
+ fmt.Fprintf(w, " where OLD and NEW are either import paths or files of export data\n")
+ fmt.Fprintf(w, "apidiff -m OLD NEW\n")
+ fmt.Fprintf(w, " compares OLD and NEW module APIs\n")
+ fmt.Fprintf(w, " where OLD and NEW are module paths\n")
+ fmt.Fprintf(w, "apidiff -w FILE IMPORT_PATH\n")
+ fmt.Fprintf(w, " writes export data of the package at IMPORT_PATH to FILE\n")
+ fmt.Fprintf(w, " NOTE: In a GOPATH-less environment, this option consults the\n")
+ fmt.Fprintf(w, " module cache by default, unless used in the directory that\n")
+ fmt.Fprintf(w, " contains the go.mod module definition that IMPORT_PATH belongs\n")
+ fmt.Fprintf(w, " to. In most cases users want the latter behavior, so be sure\n")
+ fmt.Fprintf(w, " to cd to the exact directory which contains the module\n")
+ fmt.Fprintf(w, " definition of IMPORT_PATH.\n")
+ fmt.Fprintf(w, "apidiff -m -w FILE MODULE_PATH\n")
+ fmt.Fprintf(w, " writes export data of the module at MODULE_PATH to FILE\n")
+ fmt.Fprintf(w, " Same NOTE for packages applies to modules.\n")
+ flag.PrintDefaults()
+ }
+
+ flag.Parse()
+ if *exportDataOutfile != "" {
+ if len(flag.Args()) != 1 {
+ flag.Usage()
+ os.Exit(2)
+ }
+ if err := loadAndWrite(flag.Arg(0)); err != nil {
+ die("writing export data: %v", err)
+ }
+ os.Exit(0)
+ }
+
+ if len(flag.Args()) != 2 {
+ flag.Usage()
+ os.Exit(2)
+ }
+
+ var report apidiff.Report
+ if *moduleMode {
+ oldmod := mustLoadOrReadModule(flag.Arg(0))
+ newmod := mustLoadOrReadModule(flag.Arg(1))
+
+ report = apidiff.ModuleChanges(oldmod, newmod)
+ } else {
+ oldpkg := mustLoadOrReadPackage(flag.Arg(0))
+ newpkg := mustLoadOrReadPackage(flag.Arg(1))
+ if !*allowInternal {
+ if isInternalPackage(oldpkg.Path(), "") && isInternalPackage(newpkg.Path(), "") {
+ fmt.Fprintf(os.Stderr, "Ignoring internal package %s\n", oldpkg.Path())
+ os.Exit(0)
+ }
+ }
+ report = apidiff.Changes(oldpkg, newpkg)
+ }
+
+ var err error
+ if *incompatibleOnly {
+ err = report.TextIncompatible(os.Stdout, false)
+ } else {
+ err = report.Text(os.Stdout)
+ }
+ if err != nil {
+ die("writing report: %v", err)
+ }
+}
+
+func loadAndWrite(path string) error {
+ if *moduleMode {
+ module := mustLoadModule(path)
+ return writeModuleExportData(module, *exportDataOutfile)
+ }
+
+ // Loading and writing data for only a single package.
+ pkg := mustLoadPackage(path)
+ return writePackageExportData(pkg, *exportDataOutfile)
+}
+
+func mustLoadOrReadPackage(importPathOrFile string) *types.Package {
+ fileInfo, err := os.Stat(importPathOrFile)
+ if err == nil && fileInfo.Mode().IsRegular() {
+ pkg, err := readPackageExportData(importPathOrFile)
+ if err != nil {
+ die("reading export data from %s: %v", importPathOrFile, err)
+ }
+ return pkg
+ } else {
+ return mustLoadPackage(importPathOrFile).Types
+ }
+}
+
+func mustLoadPackage(importPath string) *packages.Package {
+ pkg, err := loadPackage(importPath)
+ if err != nil {
+ die("loading %s: %v", importPath, err)
+ }
+ return pkg
+}
+
+func loadPackage(importPath string) (*packages.Package, error) {
+ cfg := &packages.Config{Mode: packages.LoadTypes |
+ packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
+ }
+ pkgs, err := packages.Load(cfg, importPath)
+ if err != nil {
+ return nil, err
+ }
+ if len(pkgs) == 0 {
+ return nil, fmt.Errorf("found no packages for import %s", importPath)
+ }
+ if len(pkgs[0].Errors) > 0 {
+ // TODO: use errors.Join once Go 1.21 is released.
+ return nil, pkgs[0].Errors[0]
+ }
+ return pkgs[0], nil
+}
+
+func mustLoadOrReadModule(modulePathOrFile string) *apidiff.Module {
+ var module *apidiff.Module
+ fileInfo, err := os.Stat(modulePathOrFile)
+ if err == nil && fileInfo.Mode().IsRegular() {
+ module, err = readModuleExportData(modulePathOrFile)
+ if err != nil {
+ die("reading export data from %s: %v", modulePathOrFile, err)
+ }
+ } else {
+ module = mustLoadModule(modulePathOrFile)
+ }
+
+ filterInternal(module, *allowInternal)
+
+ return module
+}
+
+func mustLoadModule(modulepath string) *apidiff.Module {
+ module, err := loadModule(modulepath)
+ if err != nil {
+ die("loading %s: %v", modulepath, err)
+ }
+ return module
+}
+
+func loadModule(modulepath string) (*apidiff.Module, error) {
+ cfg := &packages.Config{Mode: packages.LoadTypes |
+ packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps | packages.NeedModule,
+ }
+ loaded, err := packages.Load(cfg, fmt.Sprintf("%s/...", modulepath))
+ if err != nil {
+ return nil, err
+ }
+ if len(loaded) == 0 {
+ return nil, fmt.Errorf("found no packages for module %s", modulepath)
+ }
+ var tpkgs []*types.Package
+ for _, p := range loaded {
+ if len(p.Errors) > 0 {
+ // TODO: use errors.Join once Go 1.21 is released.
+ return nil, p.Errors[0]
+ }
+ tpkgs = append(tpkgs, p.Types)
+ }
+
+ return &apidiff.Module{Path: loaded[0].Module.Path, Packages: tpkgs}, nil
+}
+
+func readModuleExportData(filename string) (*apidiff.Module, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ r := bufio.NewReader(f)
+ modPath, err := r.ReadString('\n')
+ if err != nil {
+ return nil, err
+ }
+ modPath = modPath[:len(modPath)-1] // remove delimiter
+ m := map[string]*types.Package{}
+ pkgs, err := gcexportdata.ReadBundle(r, token.NewFileSet(), m)
+ if err != nil {
+ return nil, err
+ }
+
+ return &apidiff.Module{Path: modPath, Packages: pkgs}, nil
+}
+
+func writeModuleExportData(module *apidiff.Module, filename string) error {
+ f, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintln(f, module.Path)
+ // TODO: Determine if token.NewFileSet is appropriate here.
+ if err := gcexportdata.WriteBundle(f, token.NewFileSet(), module.Packages); err != nil {
+ return err
+ }
+ return f.Close()
+}
+
+func readPackageExportData(filename string) (*types.Package, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ r := bufio.NewReader(f)
+ m := map[string]*types.Package{}
+ pkgPath, err := r.ReadString('\n')
+ if err != nil {
+ return nil, err
+ }
+ pkgPath = pkgPath[:len(pkgPath)-1] // remove delimiter
+ return gcexportdata.Read(r, token.NewFileSet(), m, pkgPath)
+}
+
+func writePackageExportData(pkg *packages.Package, filename string) error {
+ f, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ // Include the package path in the file. The exportdata format does
+ // not record the path of the package being written.
+ fmt.Fprintln(f, pkg.PkgPath)
+ err1 := gcexportdata.Write(f, pkg.Fset, pkg.Types)
+ err2 := f.Close()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
+
+func die(format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, format+"\n", args...)
+ os.Exit(1)
+}
+
+func filterInternal(m *apidiff.Module, allow bool) {
+ if allow {
+ return
+ }
+
+ var nonInternal []*types.Package
+ for _, p := range m.Packages {
+ if !isInternalPackage(p.Path(), m.Path) {
+ nonInternal = append(nonInternal, p)
+ } else {
+ fmt.Fprintf(os.Stderr, "Ignoring internal package %s\n", p.Path())
+ }
+ }
+ m.Packages = nonInternal
+}
+
+func isInternalPackage(pkgPath, modulePath string) bool {
+ pkgPath = strings.TrimPrefix(pkgPath, modulePath)
+ switch {
+ case strings.HasSuffix(pkgPath, "/internal"):
+ return true
+ case strings.Contains(pkgPath, "/internal/"):
+ return true
+ case pkgPath == "internal":
+ return true
+ case strings.HasPrefix(pkgPath, "internal/"):
+ return true
+ }
+ return false
+}
diff --git a/cmd/apidiff/main_test.go b/cmd/apidiff/main_test.go
new file mode 100644
index 0000000..7cf8bf3
--- /dev/null
+++ b/cmd/apidiff/main_test.go
@@ -0,0 +1,144 @@
+package main
+
+import (
+ "go/types"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/exp/apidiff"
+)
+
+func TestIsInternalPackage(t *testing.T) {
+ for _, tst := range []struct {
+ name, pkg, mod string
+ want bool
+ }{
+ {
+ name: "not internal no module",
+ pkg: "foo",
+ want: false,
+ },
+ {
+ name: "not internal with module",
+ pkg: "example.com/bar/foo",
+ mod: "example.com/bar",
+ want: false,
+ },
+ {
+ name: "internal no module",
+ pkg: "internal",
+ want: true,
+ },
+ {
+ name: "leading internal no module",
+ pkg: "internal/foo",
+ want: true,
+ },
+ {
+ name: "middle internal no module",
+ pkg: "foo/internal/bar",
+ want: true,
+ },
+ {
+ name: "ending internal no module",
+ pkg: "foo/internal",
+ want: true,
+ },
+
+ {
+ name: "leading internal with module",
+ pkg: "example.com/baz/internal/foo",
+ mod: "example.com/baz",
+ want: true,
+ },
+ {
+ name: "middle internal with module",
+ pkg: "example.com/baz/foo/internal/bar",
+ mod: "example.com/baz",
+ want: true,
+ },
+ {
+ name: "ending internal with module",
+ pkg: "example.com/baz/foo/internal",
+ mod: "example.com/baz",
+ want: true,
+ },
+ {
+ name: "not package internal with internal module",
+ pkg: "example.com/internal/foo",
+ mod: "example.com/internal",
+ want: false,
+ },
+ } {
+ t.Run(tst.name, func(t *testing.T) {
+ if got := isInternalPackage(tst.pkg, tst.mod); got != tst.want {
+ t.Errorf("expected %v, got %v for %s/%s", tst.want, got, tst.mod, tst.pkg)
+ }
+ })
+ }
+}
+
+func TestFilterInternal(t *testing.T) {
+ for _, tst := range []struct {
+ name string
+ mod *apidiff.Module
+ allow bool
+ want []*types.Package
+ }{
+ {
+ name: "allow internal",
+ mod: &apidiff.Module{
+ Path: "example.com/foo",
+ Packages: []*types.Package{
+ types.NewPackage("example.com/foo/bar", "bar"),
+ types.NewPackage("example.com/foo/internal", "internal"),
+ types.NewPackage("example.com/foo/internal/buz", "buz"),
+ types.NewPackage("example.com/foo/bar/internal", "internal"),
+ },
+ },
+ allow: true,
+ want: []*types.Package{
+ types.NewPackage("example.com/foo/bar", "bar"),
+ types.NewPackage("example.com/foo/internal", "internal"),
+ types.NewPackage("example.com/foo/internal/buz", "buz"),
+ types.NewPackage("example.com/foo/bar/internal", "internal"),
+ },
+ },
+ {
+ name: "filter internal",
+ mod: &apidiff.Module{
+ Path: "example.com/foo",
+ Packages: []*types.Package{
+ types.NewPackage("example.com/foo/bar", "bar"),
+ types.NewPackage("example.com/foo/internal", "internal"),
+ types.NewPackage("example.com/foo/internal/buz", "buz"),
+ types.NewPackage("example.com/foo/bar/internal", "internal"),
+ },
+ },
+ want: []*types.Package{
+ types.NewPackage("example.com/foo/bar", "bar"),
+ },
+ },
+ {
+ name: "filter internal nothing left",
+ mod: &apidiff.Module{
+ Path: "example.com/foo",
+ Packages: []*types.Package{
+ types.NewPackage("example.com/foo/internal", "internal"),
+ },
+ },
+ want: nil,
+ },
+ } {
+ t.Run(tst.name, func(t *testing.T) {
+ filterInternal(tst.mod, tst.allow)
+ if diff := cmp.Diff(tst.mod.Packages, tst.want, cmp.Comparer(comparePath)); diff != "" {
+ t.Errorf("got(-),want(+):\n%s", diff)
+ }
+ })
+ }
+}
+
+func comparePath(x, y *types.Package) bool {
+ return x.Path() == y.Path()
+}
diff --git a/cmd/gorelease/errors.go b/cmd/gorelease/errors.go
new file mode 100644
index 0000000..d99f840
--- /dev/null
+++ b/cmd/gorelease/errors.go
@@ -0,0 +1,81 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "os/exec"
+ "strings"
+
+ "golang.org/x/mod/module"
+)
+
+type usageError struct {
+ err error
+}
+
+func usageErrorf(format string, args ...interface{}) error {
+ return &usageError{err: fmt.Errorf(format, args...)}
+}
+
+const usageText = `usage: gorelease [-base=version] [-version=version]`
+
+func (e *usageError) Error() string {
+ msg := ""
+ if !errors.Is(e.err, flag.ErrHelp) {
+ msg = e.err.Error()
+ }
+ return usageText + "\n" + msg + "\nFor more information, run go doc golang.org/x/exp/cmd/gorelease"
+}
+
+type baseVersionError struct {
+ err error
+ modPath string
+}
+
+func (e *baseVersionError) Error() string {
+ firstVersion := "v0.1.0"
+ _, major, _ := module.SplitPathVersion(e.modPath)
+ if major != "" {
+ firstVersion = major[1:] + ".0.0"
+ }
+
+ return fmt.Sprintf("could not find base version. Consider setting -version=%s if this is a first release, or explicitly set -base=none: %v", firstVersion, e.err)
+}
+
+func (e *baseVersionError) Unwrap() error {
+ return e.err
+}
+
+type downloadError struct {
+ m module.Version
+ err error
+}
+
+func (e *downloadError) Error() string {
+ msg := e.err.Error()
+ sep := " "
+ if strings.Contains(msg, "\n") {
+ sep = "\n"
+ }
+ return fmt.Sprintf("error downloading module %s@%s:%s%s", e.m.Path, e.m.Version, sep, msg)
+}
+
+// cleanCmdError simplifies error messages from os/exec.Cmd.Run.
+// For ExitErrors, it trims and returns stderr. This is useful for go commands
+// that print well-formatted errors. By default, ExitError prints the exit
+// status but not stderr.
+//
+// cleanCmdError returns other errors unmodified.
+func cleanCmdError(err error) error {
+ if xerr, ok := err.(*exec.ExitError); ok {
+ if stderr := strings.TrimSpace(string(xerr.Stderr)); stderr != "" {
+ return errors.New(stderr)
+ }
+ }
+ return err
+}
diff --git a/cmd/gorelease/gorelease.go b/cmd/gorelease/gorelease.go
new file mode 100644
index 0000000..e155323
--- /dev/null
+++ b/cmd/gorelease/gorelease.go
@@ -0,0 +1,1517 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// gorelease is an experimental tool that helps module authors avoid common
+// problems before releasing a new version of a module.
+//
+// Usage:
+//
+// gorelease [-base={version|none}] [-version=version]
+//
+// Examples:
+//
+// # Compare with the latest version and suggest a new version.
+// gorelease
+//
+// # Compare with a specific version and suggest a new version.
+// gorelease -base=v1.2.3
+//
+// # Compare with the latest version and check a specific new version for compatibility.
+// gorelease -version=v1.3.0
+//
+// # Compare with a specific version and check a specific new version for compatibility.
+// gorelease -base=v1.2.3 -version=v1.3.0
+//
+// gorelease analyzes changes in the public API and dependencies of the main
+// module. It compares a base version (set with -base) with the currently
+// checked out revision. Given a proposed version to release (set with
+// -version), gorelease reports whether the changes are consistent with
+// semantic versioning. If no version is proposed with -version, gorelease
+// suggests the lowest version consistent with semantic versioning.
+//
+// If there are no visible changes in the module's public API, gorelease
+// accepts versions that increment the minor or patch version numbers. For
+// example, if the base version is "v2.3.1", gorelease would accept "v2.3.2" or
+// "v2.4.0" or any prerelease of those versions, like "v2.4.0-beta". If no
+// version is proposed, gorelease would suggest "v2.3.2".
+//
+// If there are only backward compatible differences in the module's public
+// API, gorelease only accepts versions that increment the minor version. For
+// example, if the base version is "v2.3.1", gorelease would accept "v2.4.0"
+// but not "v2.3.2".
+//
+// If there are incompatible API differences for a proposed version with
+// major version 1 or higher, gorelease will exit with a non-zero status.
+// Incompatible differences may only be released in a new major version, which
+// requires creating a module with a different path. For example, if
+// incompatible changes are made in the module "example.com/mod", a
+// new major version must be released as a new module, "example.com/mod/v2".
+// For a proposed version with major version 0, which allows incompatible
+// changes, gorelease will describe all changes, but incompatible changes
+// will not affect its exit status.
+//
+// For more information on semantic versioning, see https://semver.org.
+//
+// Note: gorelease does not accept build metadata in releases (like
+// v1.0.0+debug). Although it is valid semver, the Go tool and other tools in
+// the ecosystem do not support it, so its use is not recommended.
+//
+// gorelease accepts the following flags:
+//
+// -base=version: The version that the current version of the module will be
+// compared against. This may be a version like "v1.5.2", a version query like
+// "latest", or "none". If the version is "none", gorelease will not compare the
+// current version against any previous version; it will only validate the
+// current version. This is useful for checking the first release of a new major
+// version. The version may be preceded by a different module path and an '@',
+// like -base=example.com/mod/v2@v2.5.2. This is useful to compare against
+// an earlier major version or a fork. If -base is not specified, gorelease will
+// attempt to infer a base version from the -version flag and available released
+// versions.
+//
+// -version=version: The proposed version to be released. If specified,
+// gorelease will confirm whether this version is consistent with changes made
+// to the module's public API. gorelease will exit with a non-zero status if the
+// version is not valid.
+//
+// gorelease is eventually intended to be merged into the go command
+// as "go release". See golang.org/issues/26420.
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "go/build"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+ "unicode"
+
+ "golang.org/x/exp/apidiff"
+ "golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
+ "golang.org/x/mod/semver"
+ "golang.org/x/mod/zip"
+ "golang.org/x/tools/go/packages"
+)
+
+// IDEAS:
+// * Should we suggest versions at all or should -version be mandatory?
+// * Verify downstream modules have licenses. May need an API or library
+// for this. Be clear that we can't provide legal advice.
+// * Internal packages may be relevant to submodules (for example,
+// golang.org/x/tools/internal/lsp is imported by golang.org/x/tools).
+// gorelease should detect whether this is the case and include internal
+// directories in comparison. It should be possible to opt out or specify
+// a different list of submodules.
+// * Decide what to do about build constraints, particularly GOOS and GOARCH.
+// The API may be different on some platforms (e.g., x/sys).
+// Should gorelease load packages in multiple configurations in the same run?
+// Is it a compatible change if the same API is available for more platforms?
+// Is it an incompatible change for fewer?
+// How about cgo? Is adding a new cgo dependency an incompatible change?
+// * Support splits and joins of nested modules. For example, if we are
+// proposing to tag a particular commit as both cloud.google.com/go v0.46.2
+// and cloud.google.com/go/storage v1.0.0, we should ensure that the sets of
+// packages provided by those modules are disjoint, and we should not report
+// the packages moved from one to the other as an incompatible change (since
+// the APIs are still compatible, just with a different module split).
+
+// TODO(jayconrod):
+// * Clean up overuse of fmt.Errorf.
+// * Support migration to modules after v2.x.y+incompatible. Requires comparing
+// packages with different module paths.
+// * Error when packages import from earlier major version of same module.
+// (this may be intentional; look for real examples first).
+// * Mechanism to suppress error messages.
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("gorelease: ")
+ wd, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ ctx := context.WithValue(context.Background(), "env", append(os.Environ(), "GO111MODULE=on"))
+ success, err := runRelease(ctx, os.Stdout, wd, os.Args[1:])
+ if err != nil {
+ if _, ok := err.(*usageError); ok {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ } else {
+ log.Fatal(err)
+ }
+ }
+ if !success {
+ os.Exit(1)
+ }
+}
+
+// runRelease is the main function of gorelease. It's called by tests, so
+// it writes to w instead of os.Stdout and returns an error instead of
+// exiting.
+func runRelease(ctx context.Context, w io.Writer, dir string, args []string) (success bool, err error) {
+ // Validate arguments and flags. We'll print our own errors, since we want to
+ // test without printing to stderr.
+ fs := flag.NewFlagSet("gorelease", flag.ContinueOnError)
+ fs.Usage = func() {}
+ fs.SetOutput(io.Discard)
+ var baseOpt, releaseVersion string
+ fs.StringVar(&baseOpt, "base", "", "previous version to compare against")
+ fs.StringVar(&releaseVersion, "version", "", "proposed version to be released")
+ if err := fs.Parse(args); err != nil {
+ return false, &usageError{err: err}
+ }
+
+ if len(fs.Args()) > 0 {
+ return false, usageErrorf("no arguments allowed")
+ }
+
+ if releaseVersion != "" {
+ if semver.Build(releaseVersion) != "" {
+ return false, usageErrorf("release version %q is not a canonical semantic version: build metadata is not supported", releaseVersion)
+ }
+ if c := semver.Canonical(releaseVersion); c != releaseVersion {
+ return false, usageErrorf("release version %q is not a canonical semantic version", releaseVersion)
+ }
+ }
+
+ var baseModPath, baseVersion string
+ if at := strings.Index(baseOpt, "@"); at >= 0 {
+ baseModPath = baseOpt[:at]
+ baseVersion = baseOpt[at+1:]
+ } else if dot, slash := strings.Index(baseOpt, "."), strings.Index(baseOpt, "/"); dot >= 0 && slash >= 0 && dot < slash {
+ baseModPath = baseOpt
+ } else {
+ baseVersion = baseOpt
+ }
+ if baseModPath == "" {
+ if baseVersion != "" && semver.Canonical(baseVersion) == baseVersion && releaseVersion != "" {
+ if cmp := semver.Compare(baseOpt, releaseVersion); cmp == 0 {
+ return false, usageErrorf("-base and -version must be different")
+ } else if cmp > 0 {
+ return false, usageErrorf("base version (%q) must be lower than release version (%q)", baseVersion, releaseVersion)
+ }
+ }
+ } else if baseModPath != "" && baseVersion == "none" {
+ return false, usageErrorf(`base version (%q) cannot have version "none" with explicit module path`, baseOpt)
+ }
+
+ // Find the local module and repository root directories.
+ modRoot, err := findModuleRoot(dir)
+ if err != nil {
+ return false, err
+ }
+ repoRoot := findRepoRoot(modRoot)
+
+ // Load packages for the version to be released from the local directory.
+ release, err := loadLocalModule(ctx, modRoot, repoRoot, releaseVersion)
+ if err != nil {
+ return false, err
+ }
+
+ // Find the base version if there is one, download it, and load packages from
+ // the module cache.
+ var max string
+ if baseModPath == "" {
+ if baseVersion != "" && semver.Canonical(baseVersion) == baseVersion && module.Check(release.modPath, baseVersion) != nil {
+ // Base version was specified, but it's not consistent with the release
+ // module path, for example, the module path is example.com/m/v2, but
+ // the user said -base=v1.0.0. Instead of making the user explicitly
+ // specify the base module path, we'll adjust the major version suffix.
+ prefix, _, _ := module.SplitPathVersion(release.modPath)
+ major := semver.Major(baseVersion)
+ if strings.HasPrefix(prefix, "gopkg.in/") {
+ baseModPath = prefix + "." + semver.Major(baseVersion)
+ } else if major >= "v2" {
+ baseModPath = prefix + "/" + major
+ } else {
+ baseModPath = prefix
+ }
+ } else {
+ baseModPath = release.modPath
+ max = releaseVersion
+ }
+ }
+ base, err := loadDownloadedModule(ctx, baseModPath, baseVersion, max)
+ if err != nil {
+ return false, err
+ }
+
+ // Compare packages and check for other issues.
+ report, err := makeReleaseReport(ctx, base, release)
+ if err != nil {
+ return false, err
+ }
+ if _, err := fmt.Fprint(w, report.String()); err != nil {
+ return false, err
+ }
+ return report.isSuccessful(), nil
+}
+
+type moduleInfo struct {
+ modRoot string // module root directory
+ repoRoot string // repository root directory (may be "")
+ modPath string // module path in go.mod
+ version string // resolved version or "none"
+ versionQuery string // a query like "latest" or "dev-branch", if specified
+ versionInferred bool // true if the version was unspecified and inferred
+ highestTransitiveVersion string // version of the highest transitive self-dependency (cycle)
+ modPathMajor string // major version suffix like "/v3" or ".v2"
+ tagPrefix string // prefix for version tags if module not in repo root
+
+ goModPath string // file path to go.mod
+ goModData []byte // content of go.mod
+ goSumData []byte // content of go.sum
+ goModFile *modfile.File // parsed go.mod file
+
+ diagnostics []string // problems not related to loading specific packages
+ pkgs []*packages.Package // loaded packages with type information
+
+ // Versions of this module which already exist. Only loaded for release
+ // (not base).
+ existingVersions []string
+}
+
+// loadLocalModule loads information about a module and its packages from a
+// local directory.
+//
+// modRoot is the directory containing the module's go.mod file.
+//
+// repoRoot is the root directory of the repository containing the module or "".
+//
+// version is a proposed version for the module or "".
+func loadLocalModule(ctx context.Context, modRoot, repoRoot, version string) (m moduleInfo, err error) {
+ if repoRoot != "" && !hasFilePathPrefix(modRoot, repoRoot) {
+ return moduleInfo{}, fmt.Errorf("module root %q is not in repository root %q", modRoot, repoRoot)
+ }
+
+ // Load the go.mod file and check the module path and go version.
+ m = moduleInfo{
+ modRoot: modRoot,
+ repoRoot: repoRoot,
+ version: version,
+ goModPath: filepath.Join(modRoot, "go.mod"),
+ }
+
+ if version != "" && semver.Compare(version, "v0.0.0-99999999999999-zzzzzzzzzzzz") < 0 {
+ m.diagnostics = append(m.diagnostics, fmt.Sprintf("Version %s is lower than most pseudo-versions. Consider releasing v0.1.0-0 instead.", version))
+ }
+
+ m.goModData, err = os.ReadFile(m.goModPath)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ m.goModFile, err = modfile.ParseLax(m.goModPath, m.goModData, nil)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ if m.goModFile.Module == nil {
+ return moduleInfo{}, fmt.Errorf("%s: module directive is missing", m.goModPath)
+ }
+ m.modPath = m.goModFile.Module.Mod.Path
+ if err := checkModPath(m.modPath); err != nil {
+ return moduleInfo{}, err
+ }
+ var ok bool
+ _, m.modPathMajor, ok = module.SplitPathVersion(m.modPath)
+ if !ok {
+ // we just validated the path above.
+ panic(fmt.Sprintf("could not find version suffix in module path %q", m.modPath))
+ }
+ if m.goModFile.Go == nil {
+ m.diagnostics = append(m.diagnostics, "go.mod: go directive is missing")
+ }
+
+ // Determine the version tag prefix for the module within the repository.
+ if repoRoot != "" && modRoot != repoRoot {
+ if strings.HasPrefix(m.modPathMajor, ".") {
+ m.diagnostics = append(m.diagnostics, fmt.Sprintf("%s: module path starts with gopkg.in and must be declared in the root directory of the repository", m.modPath))
+ } else {
+ codeDir := filepath.ToSlash(modRoot[len(repoRoot)+1:])
+ var altGoModPath string
+ if m.modPathMajor == "" {
+ // module has no major version suffix.
+ // codeDir must be a suffix of modPath.
+ // tagPrefix is codeDir with a trailing slash.
+ if strings.HasSuffix(m.modPath, "/"+codeDir) {
+ m.tagPrefix = codeDir + "/"
+ } else {
+ m.diagnostics = append(m.diagnostics, fmt.Sprintf("%s: module path must end with %[2]q, since it is in subdirectory %[2]q", m.modPath, codeDir))
+ }
+ } else {
+ if strings.HasSuffix(m.modPath, "/"+codeDir) {
+ // module has a major version suffix and is in a major version subdirectory.
+ // codeDir must be a suffix of modPath.
+ // tagPrefix must not include the major version.
+ m.tagPrefix = codeDir[:len(codeDir)-len(m.modPathMajor)+1]
+ altGoModPath = modRoot[:len(modRoot)-len(m.modPathMajor)+1] + "go.mod"
+ } else if strings.HasSuffix(m.modPath, "/"+codeDir+m.modPathMajor) {
+ // module has a major version suffix and is not in a major version subdirectory.
+ // codeDir + modPathMajor is a suffix of modPath.
+ // tagPrefix is codeDir with a trailing slash.
+ m.tagPrefix = codeDir + "/"
+ altGoModPath = filepath.Join(modRoot, m.modPathMajor[1:], "go.mod")
+ } else {
+ m.diagnostics = append(m.diagnostics, fmt.Sprintf("%s: module path must end with %[2]q or %q, since it is in subdirectory %[2]q", m.modPath, codeDir, codeDir+m.modPathMajor))
+ }
+ }
+
+ // Modules with major version suffixes can be defined in two places
+ // (e.g., sub/go.mod and sub/v2/go.mod). They must not be defined in both.
+ if altGoModPath != "" {
+ if data, err := os.ReadFile(altGoModPath); err == nil {
+ if altModPath := modfile.ModulePath(data); m.modPath == altModPath {
+ goModRel, _ := filepath.Rel(repoRoot, m.goModPath)
+ altGoModRel, _ := filepath.Rel(repoRoot, altGoModPath)
+ m.diagnostics = append(m.diagnostics, fmt.Sprintf("module is defined in two locations:\n\t%s\n\t%s", goModRel, altGoModRel))
+ }
+ }
+ }
+ }
+ }
+
+ // Load the module's packages.
+ // We pack the module into a zip file and extract it to a temporary directory
+ // as if it were published and downloaded. We'll detect any errors that would
+ // occur (for example, invalid file names). We avoid loading it as the
+ // main module.
+ tmpModRoot, err := copyModuleToTempDir(repoRoot, m.modPath, m.modRoot)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ defer func() {
+ if rerr := os.RemoveAll(tmpModRoot); err == nil && rerr != nil {
+ err = fmt.Errorf("removing temporary module directory: %v", rerr)
+ }
+ }()
+ tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths, prepareDiagnostics, err := prepareLoadDir(ctx, m.goModFile, m.modPath, tmpModRoot, version, false)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ defer func() {
+ if rerr := os.RemoveAll(tmpLoadDir); err == nil && rerr != nil {
+ err = fmt.Errorf("removing temporary load directory: %v", rerr)
+ }
+ }()
+
+ var loadDiagnostics []string
+ m.pkgs, loadDiagnostics, err = loadPackages(ctx, m.modPath, tmpModRoot, tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+
+ m.diagnostics = append(m.diagnostics, prepareDiagnostics...)
+ m.diagnostics = append(m.diagnostics, loadDiagnostics...)
+
+ highestVersion, err := findSelectedVersion(ctx, tmpLoadDir, m.modPath)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+
+ if highestVersion != "" {
+ // A version of the module is included in the transitive dependencies.
+ // Add it to the moduleInfo so that the release report stage can use it
+ // in verifying the version or suggestion a new version, depending on
+ // whether the user provided a version already.
+ m.highestTransitiveVersion = highestVersion
+ }
+
+ retracted, err := loadRetractions(ctx, tmpLoadDir)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ m.diagnostics = append(m.diagnostics, retracted...)
+
+ return m, nil
+}
+
+// loadDownloadedModule downloads a module and loads information about it and
+// its packages from the module cache.
+//
+// modPath is the module path used to fetch the module. The module's path in
+// go.mod (m.modPath) may be different, for example in a soft fork intended as
+// a replacement.
+//
+// version is the version to load. It may be "none" (indicating nothing should
+// be loaded), "" (the highest available version below max should be used), a
+// version query (to be resolved with 'go list'), or a canonical version.
+//
+// If version is "" and max is not "", available versions greater than or equal
+// to max will not be considered. Typically, loadDownloadedModule is used to
+// load the base version, and max is the release version.
+func loadDownloadedModule(ctx context.Context, modPath, version, max string) (m moduleInfo, err error) {
+ // Check the module path and version.
+ // If the version is a query, resolve it to a canonical version.
+ m = moduleInfo{modPath: modPath}
+ if err := checkModPath(modPath); err != nil {
+ return moduleInfo{}, err
+ }
+
+ var ok bool
+ _, m.modPathMajor, ok = module.SplitPathVersion(modPath)
+ if !ok {
+ // we just validated the path above.
+ panic(fmt.Sprintf("could not find version suffix in module path %q", modPath))
+ }
+
+ if version == "none" {
+ // We don't have a base version to compare against.
+ m.version = "none"
+ return m, nil
+ }
+ if version == "" {
+ // Unspecified version: use the highest version below max.
+ m.versionInferred = true
+ if m.version, err = inferBaseVersion(ctx, modPath, max); err != nil {
+ return moduleInfo{}, err
+ }
+ if m.version == "none" {
+ return m, nil
+ }
+ } else if version != module.CanonicalVersion(version) {
+ // Version query: find the real version.
+ m.versionQuery = version
+ if m.version, err = queryVersion(ctx, modPath, version); err != nil {
+ return moduleInfo{}, err
+ }
+ if m.version != "none" && max != "" && semver.Compare(m.version, max) >= 0 {
+ // TODO(jayconrod): reconsider this comparison for pseudo-versions in
+ // general. A query might match different pseudo-versions over time,
+ // depending on ancestor versions, so this might start failing with
+ // no local change.
+ return moduleInfo{}, fmt.Errorf("base version %s (%s) must be lower than release version %s", m.version, m.versionQuery, max)
+ }
+ } else {
+ // Canonical version: make sure it matches the module path.
+ if err := module.CheckPathMajor(version, m.modPathMajor); err != nil {
+ // TODO(golang.org/issue/39666): don't assume this is the base version
+ // or that we're comparing across major versions.
+ return moduleInfo{}, fmt.Errorf("can't compare major versions: base version %s does not belong to module %s", version, modPath)
+ }
+ m.version = version
+ }
+
+ // Download the module into the cache and load the mod file.
+ // Note that goModPath is $GOMODCACHE/cache/download/$modPath/@v/$version.mod,
+ // which is not inside modRoot. This is what the go command uses. Even if
+ // the module didn't have a go.mod file, one will be synthesized there.
+ v := module.Version{Path: modPath, Version: m.version}
+ if m.modRoot, m.goModPath, err = downloadModule(ctx, v); err != nil {
+ return moduleInfo{}, err
+ }
+ if m.goModData, err = os.ReadFile(m.goModPath); err != nil {
+ return moduleInfo{}, err
+ }
+ if m.goModFile, err = modfile.ParseLax(m.goModPath, m.goModData, nil); err != nil {
+ return moduleInfo{}, err
+ }
+ if m.goModFile.Module == nil {
+ return moduleInfo{}, fmt.Errorf("%s: missing module directive", m.goModPath)
+ }
+ m.modPath = m.goModFile.Module.Mod.Path
+
+ // Load packages.
+ tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths, _, err := prepareLoadDir(ctx, nil, m.modPath, m.modRoot, m.version, true)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ defer func() {
+ if rerr := os.RemoveAll(tmpLoadDir); err == nil && rerr != nil {
+ err = fmt.Errorf("removing temporary load directory: %v", err)
+ }
+ }()
+
+ if m.pkgs, _, err = loadPackages(ctx, m.modPath, m.modRoot, tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths); err != nil {
+ return moduleInfo{}, err
+ }
+
+ // Calculate the existing versions.
+ ev, err := existingVersions(ctx, m.modPath, tmpLoadDir)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ m.existingVersions = ev
+
+ return m, nil
+}
+
+// makeReleaseReport returns a report comparing the current version of a
+// module with a previously released version. The report notes any backward
+// compatible and incompatible changes in the module's public API. It also
+// diagnoses common problems, such as go.mod or go.sum being incomplete.
+// The report recommends or validates a release version and indicates a
+// version control tag to use (with an appropriate prefix, for modules not
+// in the repository root directory).
+func makeReleaseReport(ctx context.Context, base, release moduleInfo) (report, error) {
+ // TODO: use apidiff.ModuleChanges.
+ // Compare each pair of packages.
+ // Ignore internal packages.
+ // If we don't have a base version to compare against just check the new
+ // packages for errors.
+ shouldCompare := base.version != "none"
+ isInternal := func(modPath, pkgPath string) bool {
+ if !hasPathPrefix(pkgPath, modPath) {
+ panic(fmt.Sprintf("package %s not in module %s", pkgPath, modPath))
+ }
+ for pkgPath != modPath {
+ if path.Base(pkgPath) == "internal" {
+ return true
+ }
+ pkgPath = path.Dir(pkgPath)
+ }
+ return false
+ }
+ r := report{
+ base: base,
+ release: release,
+ }
+ for _, pair := range zipPackages(base.modPath, base.pkgs, release.modPath, release.pkgs) {
+ basePkg, releasePkg := pair.base, pair.release
+ switch {
+ case releasePkg == nil:
+ // Package removed
+ if internal := isInternal(base.modPath, basePkg.PkgPath); !internal || len(basePkg.Errors) > 0 {
+ pr := packageReport{
+ path: basePkg.PkgPath,
+ baseErrors: basePkg.Errors,
+ }
+ if !internal {
+ pr.Report = apidiff.Report{
+ Changes: []apidiff.Change{{
+ Message: "package removed",
+ Compatible: false,
+ }},
+ }
+ }
+ r.addPackage(pr)
+ }
+
+ case basePkg == nil:
+ // Package added
+ if internal := isInternal(release.modPath, releasePkg.PkgPath); !internal && shouldCompare || len(releasePkg.Errors) > 0 {
+ pr := packageReport{
+ path: releasePkg.PkgPath,
+ releaseErrors: releasePkg.Errors,
+ }
+ if !internal && shouldCompare {
+ // If we aren't comparing against a base version, don't say
+ // "package added". Only report packages with errors.
+ pr.Report = apidiff.Report{
+ Changes: []apidiff.Change{{
+ Message: "package added",
+ Compatible: true,
+ }},
+ }
+ }
+ r.addPackage(pr)
+ }
+
+ default:
+ // Matched packages
+ // Both packages are internal or neither; we only consider path components
+ // after the module path.
+ internal := isInternal(release.modPath, releasePkg.PkgPath)
+ if !internal && basePkg.Name != "main" && releasePkg.Name != "main" {
+ pr := packageReport{
+ path: basePkg.PkgPath,
+ baseErrors: basePkg.Errors,
+ releaseErrors: releasePkg.Errors,
+ Report: apidiff.Changes(basePkg.Types, releasePkg.Types),
+ }
+ r.addPackage(pr)
+ }
+ }
+ }
+
+ if r.canVerifyReleaseVersion() {
+ if release.version == "" {
+ r.suggestReleaseVersion()
+ } else {
+ r.validateReleaseVersion()
+ }
+ }
+
+ return r, nil
+}
+
+// existingVersions returns the versions that already exist for the given
+// modPath.
+func existingVersions(ctx context.Context, modPath, modRoot string) (versions []string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("listing versions of %s: %w", modPath, err)
+ }
+ }()
+
+ type listVersions struct {
+ Versions []string
+ }
+ cmd := exec.CommandContext(ctx, "go", "list", "-json", "-m", "-versions", modPath)
+ cmd.Env = copyEnv(ctx, cmd.Env)
+ cmd.Dir = modRoot
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, cleanCmdError(err)
+ }
+ if len(out) == 0 {
+ return nil, nil
+ }
+
+ var lv listVersions
+ if err := json.Unmarshal(out, &lv); err != nil {
+ return nil, err
+ }
+ return lv.Versions, nil
+}
+
+// findRepoRoot finds the root directory of the repository that contains dir.
+// findRepoRoot returns "" if it can't find the repository root.
+func findRepoRoot(dir string) string {
+ vcsDirs := []string{".git", ".hg", ".svn", ".bzr"}
+ d := filepath.Clean(dir)
+ for {
+ for _, vcsDir := range vcsDirs {
+ if _, err := os.Stat(filepath.Join(d, vcsDir)); err == nil {
+ return d
+ }
+ }
+ parent := filepath.Dir(d)
+ if parent == d {
+ return ""
+ }
+ d = parent
+ }
+}
+
+// findModuleRoot finds the root directory of the module that contains dir.
+func findModuleRoot(dir string) (string, error) {
+ d := filepath.Clean(dir)
+ for {
+ if fi, err := os.Stat(filepath.Join(d, "go.mod")); err == nil && !fi.IsDir() {
+ return dir, nil
+ }
+ parent := filepath.Dir(d)
+ if parent == d {
+ break
+ }
+ d = parent
+ }
+ return "", fmt.Errorf("%s: cannot find go.mod file", dir)
+}
+
+// checkModPath is like golang.org/x/mod/module.CheckPath, but it returns
+// friendlier error messages for common mistakes.
+//
+// TODO(jayconrod): update module.CheckPath and delete this function.
+func checkModPath(modPath string) error {
+ if path.IsAbs(modPath) || filepath.IsAbs(modPath) {
+ // TODO(jayconrod): improve error message in x/mod instead of checking here.
+ return fmt.Errorf("module path %q must not be an absolute path.\nIt must be an address where your module may be found.", modPath)
+ }
+ if suffix := dirMajorSuffix(modPath); suffix == "v0" || suffix == "v1" {
+ return fmt.Errorf("module path %q has major version suffix %q.\nA major version suffix is only allowed for v2 or later.", modPath, suffix)
+ } else if strings.HasPrefix(suffix, "v0") {
+ return fmt.Errorf("module path %q has major version suffix %q.\nA major version may not have a leading zero.", modPath, suffix)
+ } else if strings.ContainsRune(suffix, '.') {
+ return fmt.Errorf("module path %q has major version suffix %q.\nA major version may not contain dots.", modPath, suffix)
+ }
+ return module.CheckPath(modPath)
+}
+
+// inferBaseVersion returns an appropriate base version if one was not specified
+// explicitly.
+//
+// If max is not "", inferBaseVersion returns the highest available release
+// version of the module lower than max. Otherwise, inferBaseVersion returns the
+// highest available release version. Pre-release versions are not considered.
+// If there is no available version, and max appears to be the first release
+// version (for example, "v0.1.0", "v2.0.0"), "none" is returned.
+func inferBaseVersion(ctx context.Context, modPath, max string) (baseVersion string, err error) {
+ defer func() {
+ if err != nil {
+ err = &baseVersionError{err: err, modPath: modPath}
+ }
+ }()
+
+ versions, err := loadVersions(ctx, modPath)
+ if err != nil {
+ return "", err
+ }
+
+ for i := len(versions) - 1; i >= 0; i-- {
+ v := versions[i]
+ if semver.Prerelease(v) == "" &&
+ (max == "" || semver.Compare(v, max) < 0) {
+ return v, nil
+ }
+ }
+
+ if max == "" || maybeFirstVersion(max) {
+ return "none", nil
+ }
+ return "", fmt.Errorf("no versions found lower than %s", max)
+}
+
+// queryVersion returns the canonical version for a given module version query.
+func queryVersion(ctx context.Context, modPath, query string) (resolved string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not resolve version %s@%s: %w", modPath, query, err)
+ }
+ }()
+ if query == "upgrade" || query == "patch" {
+ return "", errors.New("query is based on requirements in main go.mod file")
+ }
+
+ tmpDir, err := os.MkdirTemp("", "")
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ if rerr := os.Remove(tmpDir); rerr != nil && err == nil {
+ err = rerr
+ }
+ }()
+ arg := modPath + "@" + query
+ cmd := exec.CommandContext(ctx, "go", "list", "-m", "-f", "{{.Version}}", "--", arg)
+ cmd.Env = copyEnv(ctx, cmd.Env)
+ cmd.Dir = tmpDir
+ cmd.Env = append(cmd.Env, "GO111MODULE=on")
+ out, err := cmd.Output()
+ if err != nil {
+ return "", cleanCmdError(err)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+// loadVersions loads the list of versions for the given module using
+// 'go list -m -versions'. The returned versions are sorted in ascending
+// semver order.
+func loadVersions(ctx context.Context, modPath string) (versions []string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not load versions for %s: %v", modPath, err)
+ }
+ }()
+
+ tmpDir, err := os.MkdirTemp("", "")
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if rerr := os.Remove(tmpDir); rerr != nil && err == nil {
+ err = rerr
+ }
+ }()
+ cmd := exec.CommandContext(ctx, "go", "list", "-m", "-versions", "--", modPath)
+ cmd.Env = copyEnv(ctx, cmd.Env)
+ cmd.Dir = tmpDir
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, cleanCmdError(err)
+ }
+ versions = strings.Fields(string(out))
+ if len(versions) > 0 {
+ versions = versions[1:] // skip module path
+ }
+
+ // Sort versions defensively. 'go list -m -versions' should always returns
+ // a sorted list of versions, but it's fast and easy to sort them here, too.
+ sort.Slice(versions, func(i, j int) bool {
+ return semver.Compare(versions[i], versions[j]) < 0
+ })
+ return versions, nil
+}
+
+// maybeFirstVersion returns whether v appears to be the first version
+// of a module.
+func maybeFirstVersion(v string) bool {
+ major, minor, patch, _, _, err := parseVersion(v)
+ if err != nil {
+ return false
+ }
+ if major == "0" {
+ return minor == "0" && patch == "0" ||
+ minor == "0" && patch == "1" ||
+ minor == "1" && patch == "0"
+ }
+ return minor == "0" && patch == "0"
+}
+
+// dirMajorSuffix returns a major version suffix for a slash-separated path.
+// For example, for the path "foo/bar/v2", dirMajorSuffix would return "v2".
+// If no major version suffix is found, "" is returned.
+//
+// dirMajorSuffix is less strict than module.SplitPathVersion so that incorrect
+// suffixes like "v0", "v02", "v1.2" can be detected. It doesn't handle
+// special cases for gopkg.in paths.
+func dirMajorSuffix(path string) string {
+ i := len(path)
+ for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') || path[i-1] == '.' {
+ i--
+ }
+ if i <= 1 || i == len(path) || path[i-1] != 'v' || (i > 1 && path[i-2] != '/') {
+ return ""
+ }
+ return path[i-1:]
+}
+
+// copyModuleToTempDir copies module files from modRoot to a subdirectory of
+// scratchDir. Submodules, vendor directories, and irregular files are excluded.
+// An error is returned if the module contains any files or directories that
+// can't be included in a module zip file (due to special characters,
+// excessive sizes, etc.).
+func copyModuleToTempDir(repoRoot, modPath, modRoot string) (dir string, err error) {
+ // Generate a fake version consistent with modPath. We need a canonical
+ // version to create a zip file.
+ version := "v0.0.0-gorelease"
+ _, majorPathSuffix, _ := module.SplitPathVersion(modPath)
+ if majorPathSuffix != "" {
+ version = majorPathSuffix[1:] + ".0.0-gorelease"
+ }
+ m := module.Version{Path: modPath, Version: version}
+
+ zipFile, err := os.CreateTemp("", "gorelease-*.zip")
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ zipFile.Close()
+ os.Remove(zipFile.Name())
+ }()
+
+ dir, err = os.MkdirTemp("", "gorelease")
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ if err != nil {
+ os.RemoveAll(dir)
+ dir = ""
+ }
+ }()
+
+ var fallbackToDir bool
+ if repoRoot != "" {
+ var err error
+ fallbackToDir, err = tryCreateFromVCS(zipFile, m, modRoot, repoRoot)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ if repoRoot == "" || fallbackToDir {
+ // Not a recognised repo: fall back to creating from dir.
+ if err := zip.CreateFromDir(zipFile, m, modRoot); err != nil {
+ var e zip.FileErrorList
+ if errors.As(err, &e) {
+ return "", e
+ }
+ return "", err
+ }
+ }
+
+ if err := zipFile.Close(); err != nil {
+ return "", err
+ }
+ if err := zip.Unzip(dir, m, zipFile.Name()); err != nil {
+ return "", err
+ }
+ return dir, nil
+}
+
+// tryCreateFromVCS tries to create a module zip file from VCS. If it succeeds,
+// it returns fallBackToDir false and a nil err. If it fails in a recoverable
+// way, it returns fallBackToDir true and a nil err. If it fails in an
+// unrecoverable way, it returns a non-nil err.
+func tryCreateFromVCS(zipFile io.Writer, m module.Version, modRoot, repoRoot string) (fallbackToDir bool, _ error) {
+ // We recognised a repo: create from VCS.
+ if !hasFilePathPrefix(modRoot, repoRoot) {
+ panic(fmt.Sprintf("repo root %q is not a prefix of mod root %q", repoRoot, modRoot))
+ }
+ hasUncommitted, err := hasGitUncommittedChanges(repoRoot)
+ if err != nil {
+ // Fallback to CreateFromDir.
+ return true, nil
+ }
+ if hasUncommitted {
+ return false, fmt.Errorf("repo %s has uncommitted changes", repoRoot)
+ }
+ modRel := filepath.ToSlash(trimFilePathPrefix(modRoot, repoRoot))
+ if err := zip.CreateFromVCS(zipFile, m, repoRoot, "HEAD", modRel); err != nil {
+ var fel zip.FileErrorList
+ if errors.As(err, &fel) {
+ return false, fel
+ }
+ var uve *zip.UnrecognizedVCSError
+ if errors.As(err, &uve) {
+ // Fallback to CreateFromDir.
+ return true, nil
+ }
+ return false, err
+ }
+ // Success!
+ return false, nil
+}
+
+// downloadModule downloads a specific version of a module to the
+// module cache using 'go mod download'.
+func downloadModule(ctx context.Context, m module.Version) (modRoot, goModPath string, err error) {
+ defer func() {
+ if err != nil {
+ err = &downloadError{m: m, err: cleanCmdError(err)}
+ }
+ }()
+
+ // Run 'go mod download' from a temporary directory to avoid needing to load
+ // go.mod from gorelease's working directory (or a parent).
+ // go.mod may be broken, and we don't need it.
+ // TODO(golang.org/issue/36812): 'go mod download' reads go.mod even though
+ // we don't need information about the main module or the build list.
+ // If it didn't read go.mod in this case, we wouldn't need a temp directory.
+ tmpDir, err := os.MkdirTemp("", "gorelease-download")
+ if err != nil {
+ return "", "", err
+ }
+ defer os.Remove(tmpDir)
+ cmd := exec.CommandContext(ctx, "go", "mod", "download", "-json", "--", m.Path+"@"+m.Version)
+ cmd.Env = copyEnv(ctx, cmd.Env)
+ cmd.Dir = tmpDir
+ out, err := cmd.Output()
+ var xerr *exec.ExitError
+ if err != nil {
+ var ok bool
+ if xerr, ok = err.(*exec.ExitError); !ok {
+ return "", "", err
+ }
+ }
+
+ // If 'go mod download' exited unsuccessfully but printed well-formed JSON
+ // with an error, return that error.
+ parsed := struct{ Dir, GoMod, Error string }{}
+ if jsonErr := json.Unmarshal(out, &parsed); jsonErr != nil {
+ if xerr != nil {
+ return "", "", cleanCmdError(xerr)
+ }
+ return "", "", jsonErr
+ }
+ if parsed.Error != "" {
+ return "", "", errors.New(parsed.Error)
+ }
+ if xerr != nil {
+ return "", "", cleanCmdError(xerr)
+ }
+ return parsed.Dir, parsed.GoMod, nil
+}
+
+// prepareLoadDir creates a temporary directory and a go.mod file that requires
+// the module being loaded. go.sum is copied if present. It also creates a .go
+// file that imports every package in the given modPath. This temporary module
+// is useful for two reasons. First, replace and exclude directives from the
+// target module aren't applied, so we have the same view as a dependent module.
+// Second, we can run commands like 'go get' without modifying the original
+// go.mod and go.sum files.
+//
+// modFile is the pre-parsed go.mod file. If non-nil, its requirements and
+// go version will be copied so that incomplete and out-of-date requirements
+// may be reported later.
+//
+// modPath is the module's path.
+//
+// modRoot is the module's root directory.
+//
+// version is the version of the module being loaded. If must be canonical
+// for modules loaded from the cache. Otherwise, it may be empty (for example,
+// when no release version is proposed).
+//
+// cached indicates whether the module is being loaded from the module cache.
+// If cached is true, then the module lives in the cache at
+// $GOMODCACHE/$modPath@$version/. Its go.mod file is at
+// $GOMODCACHE/cache/download/$modPath/@v/$version.mod. It must be referenced
+// with a simple require. A replace directive won't work because it may not have
+// a go.mod file in modRoot.
+// If cached is false, then modRoot is somewhere outside the module cache
+// (ex /tmp). We'll reference it with a local replace directive. It must have a
+// go.mod file in modRoot.
+//
+// dir is the location of the temporary directory.
+//
+// goModData and goSumData are the contents of the go.mod and go.sum files,
+// respectively.
+//
+// pkgPaths are the import paths of the module being loaded, including the path
+// to any main packages (as if they were importable).
+func prepareLoadDir(ctx context.Context, modFile *modfile.File, modPath, modRoot, version string, cached bool) (dir string, goModData, goSumData []byte, pkgPaths []string, diagnostics []string, err error) {
+ defer func() {
+ if err != nil {
+ if cached {
+ err = fmt.Errorf("preparing to load packages for %s@%s: %w", modPath, version, err)
+ } else {
+ err = fmt.Errorf("preparing to load packages for %s: %w", modPath, err)
+ }
+ }
+ }()
+
+ if module.Check(modPath, version) != nil {
+ // If no version is proposed or if the version isn't valid, use a fake
+ // version that matches the module's major version suffix. If the version
+ // is invalid, that will be reported elsewhere.
+ version = "v0.0.0-gorelease"
+ if _, pathMajor, _ := module.SplitPathVersion(modPath); pathMajor != "" {
+ version = pathMajor[1:] + ".0.0-gorelease"
+ }
+ }
+
+ dir, err = os.MkdirTemp("", "gorelease-load")
+ if err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+
+ f := &modfile.File{}
+ f.AddModuleStmt("gorelease-load-module")
+ f.AddRequire(modPath, version)
+ if !cached {
+ f.AddReplace(modPath, version, modRoot, "")
+ }
+ if modFile != nil {
+ if modFile.Go != nil {
+ f.AddGoStmt(modFile.Go.Version)
+ }
+ for _, r := range modFile.Require {
+ f.AddRequire(r.Mod.Path, r.Mod.Version)
+ }
+ }
+ goModData, err = f.Format()
+ if err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+ if err := os.WriteFile(filepath.Join(dir, "go.mod"), goModData, 0666); err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+
+ goSumData, err = os.ReadFile(filepath.Join(modRoot, "go.sum"))
+ if err != nil && !os.IsNotExist(err) {
+ return "", nil, nil, nil, nil, err
+ }
+ if err := os.WriteFile(filepath.Join(dir, "go.sum"), goSumData, 0666); err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+
+ // Add a .go file with requirements, so that `go get` won't blat
+ // requirements.
+ fakeImports := &strings.Builder{}
+ fmt.Fprint(fakeImports, "package tmp\n")
+ imps, err := collectImportPaths(modPath, modRoot)
+ if err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+ for _, imp := range imps {
+ fmt.Fprintf(fakeImports, "import _ %q\n", imp)
+ }
+ if err := os.WriteFile(filepath.Join(dir, "tmp.go"), []byte(fakeImports.String()), 0666); err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+
+ // Add missing requirements.
+ cmd := exec.CommandContext(ctx, "go", "get", "-d", ".")
+ cmd.Env = copyEnv(ctx, cmd.Env)
+ cmd.Dir = dir
+ if _, err := cmd.Output(); err != nil {
+ return "", nil, nil, nil, nil, fmt.Errorf("looking for missing dependencies: %w", cleanCmdError(err))
+ }
+
+ // Report new requirements in go.mod.
+ goModPath := filepath.Join(dir, "go.mod")
+ loadReqs := func(data []byte) (reqs []module.Version, err error) {
+ modFile, err := modfile.ParseLax(goModPath, data, nil)
+ if err != nil {
+ return nil, err
+ }
+ for _, r := range modFile.Require {
+ reqs = append(reqs, r.Mod)
+ }
+ return reqs, nil
+ }
+
+ oldReqs, err := loadReqs(goModData)
+ if err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+ newGoModData, err := os.ReadFile(goModPath)
+ if err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+ newReqs, err := loadReqs(newGoModData)
+ if err != nil {
+ return "", nil, nil, nil, nil, err
+ }
+
+ oldMap := make(map[module.Version]bool)
+ for _, req := range oldReqs {
+ oldMap[req] = true
+ }
+ var missing []module.Version
+ for _, req := range newReqs {
+ // Ignore cyclic imports, since a module never needs to require itself.
+ if req.Path == modPath {
+ continue
+ }
+ if !oldMap[req] {
+ missing = append(missing, req)
+ }
+ }
+
+ if len(missing) > 0 {
+ var missingReqs []string
+ for _, m := range missing {
+ missingReqs = append(missingReqs, m.String())
+ }
+ diagnostics = append(diagnostics, fmt.Sprintf("go.mod: the following requirements are needed\n\t%s\nRun 'go mod tidy' to add missing requirements.", strings.Join(missingReqs, "\n\t")))
+ return dir, goModData, goSumData, imps, diagnostics, nil
+ }
+
+ // Cached modules may have no go.sum.
+ // We skip comparison because a downloaded module is outside the user's
+ // control.
+ if !cached {
+ // Check if 'go get' added new hashes to go.sum.
+ goSumPath := filepath.Join(dir, "go.sum")
+ newGoSumData, err := os.ReadFile(goSumPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return "", nil, nil, nil, nil, err
+ }
+ // If the sum doesn't exist, that's ok: we'll treat "no go.sum" like
+ // "empty go.sum".
+ }
+
+ if !sumsMatchIgnoringPath(string(goSumData), string(newGoSumData), modPath) {
+ diagnostics = append(diagnostics, "go.sum: one or more sums are missing. Run 'go mod tidy' to add missing sums.")
+ }
+ }
+
+ return dir, goModData, goSumData, imps, diagnostics, nil
+}
+
+// sumsMatchIgnoringPath checks whether the two sums match. It ignores any lines
+// which contains the given modPath.
+func sumsMatchIgnoringPath(sum1, sum2, modPathToIgnore string) bool {
+ lines1 := make(map[string]bool)
+ for _, line := range strings.Split(string(sum1), "\n") {
+ if line == "" {
+ continue
+ }
+ lines1[line] = true
+ }
+ for _, line := range strings.Split(string(sum2), "\n") {
+ if line == "" {
+ continue
+ }
+ parts := strings.Fields(line)
+ if len(parts) < 1 {
+ panic(fmt.Sprintf("go.sum malformed: unexpected line %s", line))
+ }
+ if parts[0] == modPathToIgnore {
+ continue
+ }
+
+ if !lines1[line] {
+ return false
+ }
+ }
+
+ lines2 := make(map[string]bool)
+ for _, line := range strings.Split(string(sum2), "\n") {
+ if line == "" {
+ continue
+ }
+ lines2[line] = true
+ }
+ for _, line := range strings.Split(string(sum1), "\n") {
+ if line == "" {
+ continue
+ }
+ parts := strings.Fields(line)
+ if len(parts) < 1 {
+ panic(fmt.Sprintf("go.sum malformed: unexpected line %s", line))
+ }
+ if parts[0] == modPathToIgnore {
+ continue
+ }
+
+ if !lines2[line] {
+ return false
+ }
+ }
+
+ return true
+}
+
+// collectImportPaths visits the given root and traverses its directories
+// recursively, collecting the import paths of all importable packages in each
+// directory along the way.
+//
+// modPath is the module path.
+// root is the root directory of the module to collect imports for (the root
+// of the modPath module).
+//
+// Note: the returned importPaths will include main if it exists in root.
+func collectImportPaths(modPath, root string) (importPaths []string, _ error) {
+ err := filepath.Walk(root, func(walkPath string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // Avoid .foo, _foo, and testdata subdirectory trees.
+ if !fi.IsDir() {
+ return nil
+ }
+ base := filepath.Base(walkPath)
+ if strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") || base == "testdata" || base == "internal" {
+ return filepath.SkipDir
+ }
+
+ p, err := build.Default.ImportDir(walkPath, 0)
+ if err != nil {
+ if nogoErr := (*build.NoGoError)(nil); errors.As(err, &nogoErr) {
+ // No .go files found in directory. That's ok, we'll keep
+ // searching.
+ return nil
+ }
+ return err
+ }
+
+ // Construct the import path.
+ importPath := path.Join(modPath, filepath.ToSlash(trimFilePathPrefix(p.Dir, root)))
+ importPaths = append(importPaths, importPath)
+
+ return nil
+ })
+ if err != nil {
+ return nil, fmt.Errorf("listing packages in %s: %v", root, err)
+ }
+
+ return importPaths, nil
+}
+
+// loadPackages returns a list of all packages in the module modPath, sorted by
+// package path. modRoot is the module root directory, but packages are loaded
+// from loadDir, which must contain go.mod and go.sum containing goModData and
+// goSumData.
+//
+// We load packages from a temporary external module so that replace and exclude
+// directives are not applied. The loading process may also modify go.mod and
+// go.sum, and we want to detect and report differences.
+//
+// Package loading errors will be returned in the Errors field of each package.
+// Other diagnostics (such as the go.sum file being incomplete) will be
+// returned through diagnostics.
+// err will be non-nil in case of a fatal error that prevented packages
+// from being loaded.
+func loadPackages(ctx context.Context, modPath, modRoot, loadDir string, goModData, goSumData []byte, pkgPaths []string) (pkgs []*packages.Package, diagnostics []string, err error) {
+ // Load packages.
+ // TODO(jayconrod): if there are errors loading packages in the release
+ // version, try loading in the release directory. Errors there would imply
+ // that packages don't load without replace / exclude directives.
+ cfg := &packages.Config{
+ Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
+ Dir: loadDir,
+ Context: ctx,
+ }
+ cfg.Env = copyEnv(ctx, cfg.Env)
+ if len(pkgPaths) > 0 {
+ pkgs, err = packages.Load(cfg, pkgPaths...)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Sort the returned packages by path.
+ // packages.Load makes no guarantee about the order of returned packages.
+ sort.Slice(pkgs, func(i, j int) bool {
+ return pkgs[i].PkgPath < pkgs[j].PkgPath
+ })
+
+ // Trim modRoot from file paths in errors.
+ prefix := modRoot + string(os.PathSeparator)
+ for _, pkg := range pkgs {
+ for i := range pkg.Errors {
+ pkg.Errors[i].Pos = strings.TrimPrefix(pkg.Errors[i].Pos, prefix)
+ }
+ }
+
+ return pkgs, diagnostics, nil
+}
+
+type packagePair struct {
+ base, release *packages.Package
+}
+
+// zipPackages combines two lists of packages, sorted by package path,
+// and returns a sorted list of pairs of packages with matching paths.
+// If a package is in one list but not the other (because it was added or
+// removed between releases), a pair will be returned with a nil
+// base or release field.
+func zipPackages(baseModPath string, basePkgs []*packages.Package, releaseModPath string, releasePkgs []*packages.Package) []packagePair {
+ baseIndex, releaseIndex := 0, 0
+ var pairs []packagePair
+ for baseIndex < len(basePkgs) || releaseIndex < len(releasePkgs) {
+ var basePkg, releasePkg *packages.Package
+ var baseSuffix, releaseSuffix string
+ if baseIndex < len(basePkgs) {
+ basePkg = basePkgs[baseIndex]
+ baseSuffix = trimPathPrefix(basePkg.PkgPath, baseModPath)
+ }
+ if releaseIndex < len(releasePkgs) {
+ releasePkg = releasePkgs[releaseIndex]
+ releaseSuffix = trimPathPrefix(releasePkg.PkgPath, releaseModPath)
+ }
+
+ var pair packagePair
+ if basePkg != nil && (releasePkg == nil || baseSuffix < releaseSuffix) {
+ // Package removed
+ pair = packagePair{basePkg, nil}
+ baseIndex++
+ } else if releasePkg != nil && (basePkg == nil || releaseSuffix < baseSuffix) {
+ // Package added
+ pair = packagePair{nil, releasePkg}
+ releaseIndex++
+ } else {
+ // Matched packages.
+ pair = packagePair{basePkg, releasePkg}
+ baseIndex++
+ releaseIndex++
+ }
+ pairs = append(pairs, pair)
+ }
+ return pairs
+}
+
+// findSelectedVersion returns the highest version of the given modPath at
+// modDir, if a module cycle exists. modDir should be a writable directory
+// containing the go.mod for modPath.
+//
+// If no module cycle exists, it returns empty string.
+func findSelectedVersion(ctx context.Context, modDir, modPath string) (latestVersion string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not find selected version for %s: %v", modPath, err)
+ }
+ }()
+
+ cmd := exec.CommandContext(ctx, "go", "list", "-m", "-f", "{{.Version}}", "--", modPath)
+ cmd.Env = copyEnv(ctx, cmd.Env)
+ cmd.Dir = modDir
+ out, err := cmd.Output()
+ if err != nil {
+ return "", cleanCmdError(err)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+func copyEnv(ctx context.Context, current []string) []string {
+ env, ok := ctx.Value("env").([]string)
+ if !ok {
+ return current
+ }
+ clone := make([]string, len(env))
+ copy(clone, env)
+ return clone
+}
+
+// loadRetractions lists all retracted deps found at the modRoot.
+func loadRetractions(ctx context.Context, modRoot string) ([]string, error) {
+ cmd := exec.CommandContext(ctx, "go", "list", "-json", "-m", "-u", "all")
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
+ cmd.Dir = modRoot
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, cleanCmdError(err)
+ }
+
+ var retracted []string
+ type message struct {
+ Path string
+ Version string
+ Retracted []string
+ }
+
+ dec := json.NewDecoder(bytes.NewBuffer(out))
+ for {
+ var m message
+ if err := dec.Decode(&m); err == io.EOF {
+ break
+ } else if err != nil {
+ return nil, err
+ }
+ if len(m.Retracted) == 0 {
+ continue
+ }
+ rationale, ok := shortRetractionRationale(m.Retracted)
+ if ok {
+ retracted = append(retracted, fmt.Sprintf("required module %s@%s retracted by module author: %s", m.Path, m.Version, rationale))
+ } else {
+ retracted = append(retracted, fmt.Sprintf("required module %s@%s retracted by module author", m.Path, m.Version))
+ }
+ }
+
+ return retracted, nil
+}
+
+// shortRetractionRationale returns a retraction rationale string that is safe
+// to print in a terminal. It returns hard-coded strings if the rationale
+// is empty, too long, or contains non-printable characters.
+//
+// It returns true if the rationale was printable, and false if it was not (too
+// long, contains graphics, etc).
+func shortRetractionRationale(rationales []string) (string, bool) {
+ if len(rationales) == 0 {
+ return "", false
+ }
+ rationale := rationales[0]
+
+ const maxRationaleBytes = 500
+ if i := strings.Index(rationale, "\n"); i >= 0 {
+ rationale = rationale[:i]
+ }
+ rationale = strings.TrimSpace(rationale)
+ if rationale == "" || rationale == "retracted by module author" {
+ return "", false
+ }
+ if len(rationale) > maxRationaleBytes {
+ return "", false
+ }
+ for _, r := range rationale {
+ if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
+ return "", false
+ }
+ }
+ // NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here.
+ return rationale, true
+}
+
+// hasGitUncommittedChanges checks if the given directory has uncommitteed git
+// changes.
+func hasGitUncommittedChanges(dir string) (bool, error) {
+ stdout := &bytes.Buffer{}
+ cmd := exec.Command("git", "status", "--porcelain")
+ cmd.Dir = dir
+ cmd.Stdout = stdout
+ if err := cmd.Run(); err != nil {
+ return false, cleanCmdError(err)
+ }
+ return stdout.Len() != 0, nil
+}
diff --git a/cmd/gorelease/gorelease_test.go b/cmd/gorelease/gorelease_test.go
new file mode 100644
index 0000000..47d0892
--- /dev/null
+++ b/cmd/gorelease/gorelease_test.go
@@ -0,0 +1,503 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+
+ "golang.org/x/mod/module"
+ "golang.org/x/tools/txtar"
+)
+
+var (
+ testwork = flag.Bool("testwork", false, "preserve work directory")
+ updateGolden = flag.Bool("u", false, "update expected text in test files instead of failing")
+)
+
+var hasGitCache struct {
+ once sync.Once
+ found bool
+}
+
+// hasGit reports whether the git executable exists on the PATH.
+func hasGit() bool {
+ hasGitCache.once.Do(func() {
+ if _, err := exec.LookPath("git"); err != nil {
+ return
+ }
+ hasGitCache.found = true
+ })
+ return hasGitCache.found
+}
+
+// prepareProxy creates a proxy dir and returns an associated ctx.
+//
+// proxyVersions must be a map of module version to true. If proxyVersions is
+// empty, all modules in mod/ will be included in the proxy list. If proxy
+// versions is non-empty, only those modules in mod/ that match an entry in
+// proxyVersions will be included.
+//
+// ctx must be used in runRelease.
+// cleanup must be called when the relevant tests are finished.
+func prepareProxy(proxyVersions map[module.Version]bool, tests []*test) (ctx context.Context, cleanup func(), _ error) {
+ env := append(os.Environ(), "GO111MODULE=on", "GOSUMDB=off")
+
+ proxyDir, proxyURL, err := buildProxyDir(proxyVersions, tests)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error building proxy dir: %v", err)
+ }
+ env = append(env, fmt.Sprintf("GOPROXY=%s", proxyURL))
+
+ cacheDir, err := os.MkdirTemp("", "gorelease_test-gocache")
+ if err != nil {
+ return nil, nil, err
+ }
+ env = append(env, fmt.Sprintf("GOPATH=%s", cacheDir))
+
+ return context.WithValue(context.Background(), "env", env), func() {
+ if *testwork {
+ fmt.Fprintf(os.Stderr, "test cache dir: %s\n", cacheDir)
+ fmt.Fprintf(os.Stderr, "test proxy dir: %s\ntest proxy URL: %s\n", proxyDir, proxyURL)
+ } else {
+ cmd := exec.Command("go", "clean", "-modcache")
+ cmd.Env = env
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintln(os.Stderr, fmt.Errorf("error running go clean: %v", err))
+ }
+
+ if err := os.RemoveAll(cacheDir); err != nil {
+ fmt.Fprintln(os.Stderr, fmt.Errorf("error removing cache dir %s: %v", cacheDir, err))
+ }
+ if err := os.RemoveAll(proxyDir); err != nil {
+ fmt.Fprintln(os.Stderr, fmt.Errorf("error removing proxy dir %s: %v", proxyDir, err))
+ }
+ }
+ }, nil
+}
+
+// test describes an individual test case, written as a .test file in the
+// testdata directory.
+//
+// Each test is a txtar archive (see golang.org/x/tools/txtar). The comment
+// section (before the first file) contains a sequence of key=value pairs
+// (one per line) that configure the test.
+//
+// Most tests include a file named "want". The output of gorelease is compared
+// against this file. If the -u flag is set, this file is replaced with the
+// actual output of gorelease, and the test is written back to disk. This is
+// useful for updating tests after cosmetic changes.
+type test struct {
+ txtar.Archive
+
+ // testPath is the name of the .test file describing the test.
+ testPath string
+
+ // modPath (set with mod=...) is the path of the module being tested. Used
+ // to retrieve files from the test proxy.
+ modPath string
+
+ // version (set with version=...) is the name of a version to check out
+ // from the test proxy into the working directory. Some tests use this
+ // instead of specifying files they need in the txtar archive.
+ version string
+
+ // baseVersion (set with base=...) is the value of the -base flag to pass
+ // to gorelease.
+ baseVersion string
+
+ // releaseVersion (set with release=...) is the value of the -version flag
+ // to pass to gorelease.
+ releaseVersion string
+
+ // dir (set with dir=...) is the directory where gorelease should be invoked.
+ // If unset, gorelease is invoked in the directory where the txtar archive
+ // is unpacked. This is useful for invoking gorelease in a subdirectory.
+ dir string
+
+ // wantError (set with error=...) is true if the test expects a hard error
+ // (returned by runRelease).
+ wantError bool
+
+ // wantSuccess (set with success=...) is true if the test expects a report
+ // to be returned without errors or diagnostics. True by default.
+ wantSuccess bool
+
+ // skip (set with skip=...) is non-empty if the test should be skipped.
+ skip string
+
+ // want is set to the contents of the file named "want" in the txtar archive.
+ want []byte
+
+ // proxyVersions is used to set the exact contents of the GOPROXY.
+ //
+ // If empty, all of testadata/mod/ will be included in the proxy.
+ // If it is not empty, each entry must be of the form <modpath>@v<version>
+ // and exist in testdata/mod/.
+ proxyVersions map[module.Version]bool
+
+ // vcs is used to set the VCS that the root of the test should
+ // emulate. Allowed values are git, and hg.
+ vcs string
+}
+
+// readTest reads and parses a .test file with the given name.
+func readTest(testPath string) (*test, error) {
+ arc, err := txtar.ParseFile(testPath)
+ if err != nil {
+ return nil, err
+ }
+ t := &test{
+ Archive: *arc,
+ testPath: testPath,
+ wantSuccess: true,
+ }
+
+ for n, line := range bytes.Split(t.Comment, []byte("\n")) {
+ lineNum := n + 1
+ if i := bytes.IndexByte(line, '#'); i >= 0 {
+ line = line[:i]
+ }
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 {
+ continue
+ }
+
+ var key, value string
+ if i := bytes.IndexByte(line, '='); i < 0 {
+ return nil, fmt.Errorf("%s:%d: no '=' found", testPath, lineNum)
+ } else {
+ key = strings.TrimSpace(string(line[:i]))
+ value = strings.TrimSpace(string(line[i+1:]))
+ }
+ switch key {
+ case "mod":
+ t.modPath = value
+ case "version":
+ t.version = value
+ case "base":
+ t.baseVersion = value
+ case "release":
+ t.releaseVersion = value
+ case "dir":
+ t.dir = value
+ case "skip":
+ t.skip = value
+ case "success":
+ t.wantSuccess, err = strconv.ParseBool(value)
+ if err != nil {
+ return nil, fmt.Errorf("%s:%d: %v", testPath, lineNum, err)
+ }
+ case "error":
+ t.wantError, err = strconv.ParseBool(value)
+ if err != nil {
+ return nil, fmt.Errorf("%s:%d: %v", testPath, lineNum, err)
+ }
+ case "proxyVersions":
+ if len(value) == 0 {
+ break
+ }
+ proxyVersions := make(map[module.Version]bool)
+ parts := strings.Split(value, ",")
+ for _, modpathWithVersion := range parts {
+ vParts := strings.Split(modpathWithVersion, "@")
+ if len(vParts) != 2 {
+ return nil, fmt.Errorf("proxyVersions entry %s is invalid: it should be of the format <modpath>@v<semver> (ex: github.com/foo/bar@v1.2.3)", modpathWithVersion)
+ }
+ modPath, version := vParts[0], vParts[1]
+ mv := module.Version{
+ Path: modPath,
+ Version: version,
+ }
+ proxyVersions[mv] = true
+ }
+ t.proxyVersions = proxyVersions
+ case "vcs":
+ t.vcs = value
+ default:
+ return nil, fmt.Errorf("%s:%d: unknown key: %q", testPath, lineNum, key)
+ }
+ }
+ if t.modPath == "" && (t.version != "" || (t.baseVersion != "" && t.baseVersion != "none")) {
+ return nil, fmt.Errorf("%s: version or base was set but mod was not set", testPath)
+ }
+
+ haveFiles := false
+ for _, f := range t.Files {
+ if f.Name == "want" {
+ t.want = bytes.TrimSpace(f.Data)
+ continue
+ }
+ haveFiles = true
+ }
+
+ if haveFiles && t.version != "" {
+ return nil, fmt.Errorf("%s: version is set but files are present", testPath)
+ }
+
+ return t, nil
+}
+
+// updateTest replaces the contents of the file named "want" within a test's
+// txtar archive, then formats and writes the test file.
+func updateTest(t *test, want []byte) error {
+ var wantFile *txtar.File
+ for i := range t.Files {
+ if t.Files[i].Name == "want" {
+ wantFile = &t.Files[i]
+ break
+ }
+ }
+ if wantFile == nil {
+ t.Files = append(t.Files, txtar.File{Name: "want"})
+ wantFile = &t.Files[len(t.Files)-1]
+ }
+
+ wantFile.Data = want
+ data := txtar.Format(&t.Archive)
+ return os.WriteFile(t.testPath, data, 0666)
+}
+
+func TestRelease(t *testing.T) {
+ testPaths, err := filepath.Glob(filepath.FromSlash("testdata/*/*.test"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(testPaths) == 0 {
+ t.Fatal("no .test files found in testdata directory")
+ }
+
+ var tests []*test
+ for _, testPath := range testPaths {
+ test, err := readTest(testPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tests = append(tests, test)
+ }
+
+ defaultContext, cleanup, err := prepareProxy(nil, tests)
+ if err != nil {
+ t.Fatalf("preparing test proxy: %v", err)
+ }
+ t.Cleanup(cleanup)
+
+ for _, test := range tests {
+ testName := strings.TrimSuffix(strings.TrimPrefix(filepath.ToSlash(test.testPath), "testdata/"), ".test")
+ t.Run(testName, testRelease(defaultContext, tests, test))
+ }
+}
+
+func TestRelease_gitRepo_uncommittedChanges(t *testing.T) {
+ ctx := context.Background()
+ buf := &bytes.Buffer{}
+ releaseDir, err := os.MkdirTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ goModInit(t, releaseDir)
+ gitInit(t, releaseDir)
+
+ // Create an uncommitted change.
+ bContents := `package b
+const B = "b"`
+ if err := os.WriteFile(filepath.Join(releaseDir, "b.go"), []byte(bContents), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ success, err := runRelease(ctx, buf, releaseDir, nil)
+ if got, want := err.Error(), fmt.Sprintf("repo %s has uncommitted changes", releaseDir); got != want {
+ t.Errorf("runRelease:\ngot error:\n%q\nwant error\n%q", got, want)
+ }
+ if success {
+ t.Errorf("runRelease: expected failure, got success")
+ }
+}
+
+func testRelease(ctx context.Context, tests []*test, test *test) func(t *testing.T) {
+ return func(t *testing.T) {
+ if test.skip != "" {
+ t.Skip(test.skip)
+ }
+
+ t.Parallel()
+
+ if len(test.proxyVersions) > 0 {
+ var cleanup func()
+ var err error
+ ctx, cleanup, err = prepareProxy(test.proxyVersions, tests)
+ if err != nil {
+ t.Fatalf("preparing test proxy: %v", err)
+ }
+ t.Cleanup(cleanup)
+ }
+
+ // Extract the files in the release version. They may be part of the
+ // test archive or in testdata/mod.
+ testDir, err := os.MkdirTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if *testwork {
+ fmt.Fprintf(os.Stderr, "test dir: %s\n", testDir)
+ } else {
+ t.Cleanup(func() {
+ os.RemoveAll(testDir)
+ })
+ }
+
+ var arc *txtar.Archive
+ if test.version != "" {
+ arcBase := fmt.Sprintf("%s_%s.txt", strings.ReplaceAll(test.modPath, "/", "_"), test.version)
+ arcPath := filepath.Join("testdata/mod", arcBase)
+ var err error
+ arc, err = txtar.ParseFile(arcPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ arc = &test.Archive
+ }
+ if err := extractTxtar(testDir, arc); err != nil {
+ t.Fatal(err)
+ }
+
+ switch test.vcs {
+ case "git":
+ // Convert testDir to a git repository with a single commit, to
+ // simulate a real user's module-in-a-git-repo.
+ gitInit(t, testDir)
+ case "hg":
+ // Convert testDir to a mercurial repository to simulate a real
+ // user's module-in-a-hg-repo.
+ hgInit(t, testDir)
+ case "":
+ // No VCS.
+ default:
+ t.Fatalf("unknown vcs %q", test.vcs)
+ }
+
+ // Generate the report and compare it against the expected text.
+ var args []string
+ if test.baseVersion != "" {
+ args = append(args, "-base="+test.baseVersion)
+ }
+ if test.releaseVersion != "" {
+ args = append(args, "-version="+test.releaseVersion)
+ }
+ buf := &bytes.Buffer{}
+ releaseDir := filepath.Join(testDir, test.dir)
+ success, err := runRelease(ctx, buf, releaseDir, args)
+ if err != nil {
+ if !test.wantError {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if errMsg := []byte(err.Error()); !bytes.Equal(errMsg, bytes.TrimSpace(test.want)) {
+ if *updateGolden {
+ if err := updateTest(test, errMsg); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ t.Fatalf("got error: %s; want error: %s", errMsg, test.want)
+ }
+ }
+ return
+ }
+ if test.wantError {
+ t.Fatalf("got success; want error %s", test.want)
+ }
+
+ got := bytes.TrimSpace(buf.Bytes())
+ if filepath.Separator != '/' {
+ got = bytes.ReplaceAll(got, []byte{filepath.Separator}, []byte{'/'})
+ }
+ if !bytes.Equal(got, test.want) {
+ if *updateGolden {
+ if err := updateTest(test, got); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ t.Fatalf("got:\n%s\n\nwant:\n%s", got, test.want)
+ }
+ }
+ if success != test.wantSuccess {
+ t.Fatalf("got success: %v; want success %v", success, test.wantSuccess)
+ }
+ }
+}
+
+// hgInit initialises a directory as a mercurial repo.
+func hgInit(t *testing.T, dir string) {
+ t.Helper()
+
+ if err := os.Mkdir(filepath.Join(dir, ".hg"), 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := os.WriteFile(filepath.Join(dir, ".hg", "branch"), []byte("default"), 0777); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// gitInit initialises a directory as a git repo, and adds a simple commit.
+func gitInit(t *testing.T, dir string) {
+ t.Helper()
+
+ if !hasGit() {
+ t.Skip("PATH does not contain git")
+ }
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ for _, args := range [][]string{
+ {"git", "init"},
+ {"git", "config", "user.name", "Gopher"},
+ {"git", "config", "user.email", "gopher@golang.org"},
+ {"git", "checkout", "-b", "test"},
+ {"git", "add", "-A"},
+ {"git", "commit", "-m", "test"},
+ } {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ cmd.Stdout = stdout
+ cmd.Stderr = stderr
+ if err := cmd.Run(); err != nil {
+ cmdArgs := strings.Join(args, " ")
+ t.Fatalf("%s\n%s\nerror running %q on dir %s: %v", stdout.String(), stderr.String(), cmdArgs, dir, err)
+ }
+ }
+}
+
+// goModInit runs `go mod init` in the given directory.
+func goModInit(t *testing.T, dir string) {
+ t.Helper()
+
+ aContents := `package a
+const A = "a"`
+ if err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(aContents), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+ cmd := exec.Command("go", "mod", "init", "example.com/uncommitted")
+ cmd.Stdout = stdout
+ cmd.Stderr = stderr
+ cmd.Dir = dir
+ if err := cmd.Run(); err != nil {
+ t.Fatalf("error running `go mod init`: %s, %v", stderr.String(), err)
+ }
+}
diff --git a/cmd/gorelease/path.go b/cmd/gorelease/path.go
new file mode 100644
index 0000000..bfe1f20
--- /dev/null
+++ b/cmd/gorelease/path.go
@@ -0,0 +1,87 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "path/filepath"
+ "strings"
+)
+
+// hasPathPrefix reports whether the slash-separated path s
+// begins with the elements in prefix.
+// Copied from cmd/go/internal/str.HasPathPrefix.
+func hasPathPrefix(s, prefix string) bool {
+ if len(s) == len(prefix) {
+ return s == prefix
+ }
+ if prefix == "" {
+ return true
+ }
+ if len(s) > len(prefix) {
+ if prefix[len(prefix)-1] == '/' || s[len(prefix)] == '/' {
+ return s[:len(prefix)] == prefix
+ }
+ }
+ return false
+}
+
+// hasFilePathPrefix reports whether the filesystem path s
+// begins with the elements in prefix.
+// Copied from cmd/go/internal/str.HasFilePathPrefix.
+func hasFilePathPrefix(s, prefix string) bool {
+ sv := strings.ToUpper(filepath.VolumeName(s))
+ pv := strings.ToUpper(filepath.VolumeName(prefix))
+ s = s[len(sv):]
+ prefix = prefix[len(pv):]
+ switch {
+ default:
+ return false
+ case pv != "" && sv != pv:
+ return false
+ case len(s) == len(prefix):
+ return s == prefix
+ case prefix == "":
+ return true
+ case len(s) > len(prefix):
+ if prefix[len(prefix)-1] == filepath.Separator {
+ return strings.HasPrefix(s, prefix)
+ }
+ return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
+ }
+}
+
+// trimFilePathPrefix returns the given filesystem path s without the leading
+// prefix.
+func trimFilePathPrefix(s, prefix string) string {
+ sv := strings.ToUpper(filepath.VolumeName(s))
+ pv := strings.ToUpper(filepath.VolumeName(prefix))
+ s = s[len(sv):]
+ prefix = prefix[len(pv):]
+
+ if !hasFilePathPrefix(s, prefix) || len(prefix) == 0 {
+ return s
+ }
+ if len(s) == len(prefix) {
+ return ""
+ }
+ if prefix[len(prefix)-1] == filepath.Separator {
+ return strings.TrimPrefix(s, prefix)
+ }
+ return s[len(prefix)+1:]
+}
+
+// trimPathPrefix returns p without the leading prefix. Unlike
+// strings.TrimPrefix, the prefix will only match on slash-separted component
+// boundaries, so trimPathPrefix("aa/b", "aa") returns "b", but
+// trimPathPrefix("aa/b", "a") returns "aa/b".
+func trimPathPrefix(p, prefix string) string {
+ if prefix == "" {
+ return p
+ }
+ if prefix == p {
+ return ""
+ }
+ return strings.TrimPrefix(p, prefix+"/")
+}
diff --git a/cmd/gorelease/path_test.go b/cmd/gorelease/path_test.go
new file mode 100644
index 0000000..57dfd98
--- /dev/null
+++ b/cmd/gorelease/path_test.go
@@ -0,0 +1,227 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "runtime"
+ "testing"
+)
+
+func TestHasPathPrefix(t *testing.T) {
+ for _, test := range []struct {
+ desc, path, prefix string
+ want bool
+ }{
+ {
+ desc: "empty_prefix",
+ path: "a/b",
+ prefix: "",
+ want: true,
+ }, {
+ desc: "partial_prefix",
+ path: "a/b",
+ prefix: "a",
+ want: true,
+ }, {
+ desc: "full_prefix",
+ path: "a/b",
+ prefix: "a/b",
+ want: true,
+ }, {
+ desc: "partial_component",
+ path: "aa/b",
+ prefix: "a",
+ want: false,
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ if got := hasPathPrefix(test.path, test.prefix); got != test.want {
+ t.Errorf("hasPathPrefix(%q, %q): got %v, want %v", test.path, test.prefix, got, test.want)
+ }
+ })
+ }
+}
+
+func TestHasFilePathPrefix(t *testing.T) {
+ type test struct {
+ desc, path, prefix string
+ want bool
+ }
+ var tests []test
+ if runtime.GOOS == "windows" {
+ tests = []test{
+ {
+ desc: "empty_prefix",
+ path: `c:\a\b`,
+ prefix: "",
+ want: true,
+ }, {
+ desc: "drive_prefix",
+ path: `c:\a\b`,
+ prefix: `c:\`,
+ want: true,
+ }, {
+ desc: "partial_prefix",
+ path: `c:\a\b`,
+ prefix: `c:\a`,
+ want: true,
+ }, {
+ desc: "full_prefix",
+ path: `c:\a\b`,
+ prefix: `c:\a\b`,
+ want: true,
+ }, {
+ desc: "partial_component",
+ path: `c:\aa\b`,
+ prefix: `c:\a`,
+ want: false,
+ },
+ }
+ } else {
+ tests = []test{
+ {
+ desc: "empty_prefix",
+ path: "/a/b",
+ prefix: "",
+ want: true,
+ }, {
+ desc: "partial_prefix",
+ path: "/a/b",
+ prefix: "/a",
+ want: true,
+ }, {
+ desc: "full_prefix",
+ path: "/a/b",
+ prefix: "/a/b",
+ want: true,
+ }, {
+ desc: "partial_component",
+ path: "/aa/b",
+ prefix: "/a",
+ want: false,
+ },
+ }
+ }
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ if got := hasFilePathPrefix(test.path, test.prefix); got != test.want {
+ t.Errorf("hasFilePathPrefix(%q, %q): got %v, want %v", test.path, test.prefix, got, test.want)
+ }
+ })
+ }
+}
+
+func TestTrimFilePathPrefix(t *testing.T) {
+ type test struct {
+ desc, path, prefix, want string
+ }
+ var tests []test
+ if runtime.GOOS == "windows" {
+ tests = []test{
+ // Note: these two cases in which the result preserves the leading \
+ // don't come up in reality in gorelease. That's because prefix is
+ // always far to the right of the path parts (ex github.com/foo/bar
+ // in C:\Users\foo\AppData\Local\Temp\...\github.com\foo\bar).
+ {
+ desc: "empty_prefix",
+ path: `c:\a\b`,
+ prefix: "",
+ want: `\a\b`,
+ }, {
+ desc: "partial_component",
+ path: `c:\aa\b`,
+ prefix: `c:\a`,
+ want: `\aa\b`,
+ },
+
+ {
+ desc: "drive_prefix",
+ path: `c:\a\b`,
+ prefix: `c:\`,
+ want: `a\b`,
+ }, {
+ desc: "partial_prefix",
+ path: `c:\a\b`,
+ prefix: `c:\a`,
+ want: `b`,
+ }, {
+ desc: "full_prefix",
+ path: `c:\a\b`,
+ prefix: `c:\a\b`,
+ want: "",
+ },
+ }
+ } else {
+ tests = []test{
+ {
+ desc: "empty_prefix",
+ path: "/a/b",
+ prefix: "",
+ want: "/a/b",
+ }, {
+ desc: "partial_prefix",
+ path: "/a/b",
+ prefix: "/a",
+ want: "b",
+ }, {
+ desc: "full_prefix",
+ path: "/a/b",
+ prefix: "/a/b",
+ want: "",
+ }, {
+ desc: "partial_component",
+ path: "/aa/b",
+ prefix: "/a",
+ want: "/aa/b",
+ },
+ }
+ }
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ if got := trimFilePathPrefix(test.path, test.prefix); got != test.want {
+ t.Errorf("hasFilePathPrefix(%q, %q): got %v, want %v", test.path, test.prefix, got, test.want)
+ }
+ })
+ }
+}
+
+func TestTrimPathPrefix(t *testing.T) {
+ for _, test := range []struct {
+ desc, path, prefix, want string
+ }{
+ {
+ desc: "empty_prefix",
+ path: "a/b",
+ prefix: "",
+ want: "a/b",
+ }, {
+ desc: "abs_empty_prefix",
+ path: "/a/b",
+ prefix: "",
+ want: "/a/b",
+ }, {
+ desc: "partial_prefix",
+ path: "a/b",
+ prefix: "a",
+ want: "b",
+ }, {
+ desc: "full_prefix",
+ path: "a/b",
+ prefix: "a/b",
+ want: "",
+ }, {
+ desc: "partial_component",
+ path: "aa/b",
+ prefix: "a",
+ want: "aa/b",
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ if got := trimPathPrefix(test.path, test.prefix); got != test.want {
+ t.Errorf("trimPathPrefix(%q, %q): got %q, want %q", test.path, test.prefix, got, test.want)
+ }
+ })
+ }
+}
diff --git a/cmd/gorelease/proxy_test.go b/cmd/gorelease/proxy_test.go
new file mode 100644
index 0000000..d5fefd9
--- /dev/null
+++ b/cmd/gorelease/proxy_test.go
@@ -0,0 +1,206 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ "golang.org/x/mod/module"
+ "golang.org/x/mod/semver"
+ "golang.org/x/mod/zip"
+ "golang.org/x/tools/txtar"
+)
+
+// buildProxyDir constructs a temporary directory suitable for use as a
+// module proxy with a file:// URL. The caller is responsible for deleting
+// the directory when it's no longer needed.
+//
+// proxyVersions must be a map of module version true. If proxyVersions is
+// empty, all modules in mod/ will be included in the proxy list. If proxy
+// versions is non-empty, only those modules in mod/ that match an entry in
+// proxyVersions will be included.
+func buildProxyDir(proxyVersions map[module.Version]bool, tests []*test) (proxyDir, proxyURL string, err error) {
+ proxyDir, err = os.MkdirTemp("", "gorelease-proxy")
+ if err != nil {
+ return "", "", err
+ }
+
+ txtarPaths, err := filepath.Glob(filepath.FromSlash("testdata/mod/*.txt"))
+ if err != nil {
+ return "", "", err
+ }
+
+ // Map of modPath to versions for that modPath.
+ versionLists := make(map[string][]string)
+
+ for _, t := range tests {
+ versionLists[t.modPath] = []string{}
+ modDir := filepath.Join(proxyDir, t.modPath, "@v")
+ if err := os.MkdirAll(modDir, 0777); err != nil {
+ return "", "", err
+ }
+ }
+
+ for _, txtarPath := range txtarPaths {
+ base := filepath.Base(txtarPath)
+ stem := base[:len(base)-len(".txt")]
+ i := strings.LastIndexByte(base, '_')
+ if i < 0 {
+ return "", "", fmt.Errorf("invalid module archive: %s", base)
+ }
+ modPath := strings.ReplaceAll(stem[:i], "_", "/")
+ version := stem[i+1:]
+ mv := module.Version{
+ Path: modPath,
+ Version: version,
+ }
+
+ // User has supplied proxyVersions. Honor proxy versions by only
+ // accepting those versions supplied in proxyVersions.
+ if len(proxyVersions) > 0 {
+ if !proxyVersions[mv] {
+ // modPath@version is not in proxyVersions: skip.
+ continue
+ }
+ }
+
+ versionLists[modPath] = append(versionLists[modPath], version)
+
+ modDir := filepath.Join(proxyDir, modPath, "@v")
+ if err := os.MkdirAll(modDir, 0777); err != nil {
+ return "", "", err
+ }
+
+ arc, err := txtar.ParseFile(txtarPath)
+ if err != nil {
+ return "", "", err
+ }
+
+ isCanonical := version == module.CanonicalVersion(version)
+ var zipContents []zip.File
+ var haveInfo, haveMod bool
+ var goMod txtar.File
+ for _, af := range arc.Files {
+ if !isCanonical && af.Name != ".info" {
+ return "", "", fmt.Errorf("%s: version is non-canonical but contains files other than .info", txtarPath)
+ }
+ if af.Name == ".info" || af.Name == ".mod" {
+ if af.Name == ".info" {
+ haveInfo = true
+ } else {
+ haveMod = true
+ }
+ outPath := filepath.Join(modDir, version+af.Name)
+ if err := os.WriteFile(outPath, af.Data, 0666); err != nil {
+ return "", "", err
+ }
+ continue
+ }
+ if af.Name == "go.mod" {
+ goMod = af
+ }
+
+ zipContents = append(zipContents, txtarFile{af})
+ }
+ if !isCanonical && !haveInfo {
+ return "", "", fmt.Errorf("%s: version is non-canonical but does not have .info", txtarPath)
+ }
+
+ if !haveInfo {
+ outPath := filepath.Join(modDir, version+".info")
+ outContent := fmt.Sprintf(`{"Version":"%s"}`, version)
+ if err := os.WriteFile(outPath, []byte(outContent), 0666); err != nil {
+ return "", "", err
+ }
+ }
+ if !haveMod && goMod.Name != "" {
+ outPath := filepath.Join(modDir, version+".mod")
+ if err := os.WriteFile(outPath, goMod.Data, 0666); err != nil {
+ return "", "", err
+ }
+ }
+
+ if len(zipContents) > 0 {
+ zipPath := filepath.Join(modDir, version+".zip")
+ zipFile, err := os.Create(zipPath)
+ if err != nil {
+ return "", "", err
+ }
+ defer zipFile.Close()
+ if err := zip.Create(zipFile, module.Version{Path: modPath, Version: version}, zipContents); err != nil {
+ return "", "", err
+ }
+ if err := zipFile.Close(); err != nil {
+ return "", "", err
+ }
+ }
+ }
+
+ buf := &bytes.Buffer{}
+ for modPath, versions := range versionLists {
+ outPath := filepath.Join(proxyDir, modPath, "@v", "list")
+ sort.Slice(versions, func(i, j int) bool {
+ return semver.Compare(versions[i], versions[j]) < 0
+ })
+ for _, v := range versions {
+ fmt.Fprintln(buf, v)
+ }
+ if err := os.WriteFile(outPath, buf.Bytes(), 0666); err != nil {
+ return "", "", err
+ }
+ buf.Reset()
+ }
+
+ // Make sure the URL path starts with a slash on Windows. Absolute paths
+ // normally start with a drive letter.
+ // TODO(golang.org/issue/32456): use url.FromFilePath when implemented.
+ if strings.HasPrefix(proxyDir, "/") {
+ proxyURL = "file://" + proxyDir
+ } else {
+ proxyURL = "file:///" + filepath.FromSlash(proxyDir)
+ }
+ return proxyDir, proxyURL, nil
+}
+
+type txtarFile struct {
+ f txtar.File
+}
+
+func (f txtarFile) Path() string { return f.f.Name }
+func (f txtarFile) Lstat() (os.FileInfo, error) { return txtarFileInfo{f.f}, nil }
+func (f txtarFile) Open() (io.ReadCloser, error) {
+ return io.NopCloser(bytes.NewReader(f.f.Data)), nil
+}
+
+type txtarFileInfo struct {
+ f txtar.File
+}
+
+func (f txtarFileInfo) Name() string { return f.f.Name }
+func (f txtarFileInfo) Size() int64 { return int64(len(f.f.Data)) }
+func (f txtarFileInfo) Mode() os.FileMode { return 0444 }
+func (f txtarFileInfo) ModTime() time.Time { return time.Time{} }
+func (f txtarFileInfo) IsDir() bool { return false }
+func (f txtarFileInfo) Sys() interface{} { return nil }
+
+func extractTxtar(destDir string, arc *txtar.Archive) error {
+ for _, f := range arc.Files {
+ outPath := filepath.Join(destDir, f.Name)
+ if err := os.MkdirAll(filepath.Dir(outPath), 0777); err != nil {
+ return err
+ }
+ if err := os.WriteFile(outPath, f.Data, 0666); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/cmd/gorelease/report.go b/cmd/gorelease/report.go
new file mode 100644
index 0000000..3895c98
--- /dev/null
+++ b/cmd/gorelease/report.go
@@ -0,0 +1,463 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "strings"
+
+ "golang.org/x/exp/apidiff"
+ "golang.org/x/mod/module"
+ "golang.org/x/mod/semver"
+ "golang.org/x/tools/go/packages"
+)
+
+// report describes the differences in the public API between two versions
+// of a module.
+type report struct {
+ // base contains information about the "old" module version being compared
+ // against. base.version may be "none", indicating there is no base version
+ // (for example, if this is the first release). base.version may not be "".
+ base moduleInfo
+
+ // release contains information about the version of the module to release.
+ // The version may be set explicitly with -version or suggested using
+ // suggestVersion, in which case release.versionInferred is true.
+ release moduleInfo
+
+ // packages is a list of package reports, describing the differences
+ // for individual packages, sorted by package path.
+ packages []packageReport
+
+ // versionInvalid explains why the proposed or suggested version is not valid.
+ versionInvalid *versionMessage
+
+ // haveCompatibleChanges is true if there are any backward-compatible
+ // changes in non-internal packages.
+ haveCompatibleChanges bool
+
+ // haveIncompatibleChanges is true if there are any backward-incompatible
+ // changes in non-internal packages.
+ haveIncompatibleChanges bool
+
+ // haveBaseErrors is true if there were errors loading packages
+ // in the base version.
+ haveBaseErrors bool
+
+ // haveReleaseErrors is true if there were errors loading packages
+ // in the release version.
+ haveReleaseErrors bool
+}
+
+// String returns a human-readable report that lists errors, compatible changes,
+// and incompatible changes in each package. If releaseVersion is set, the
+// report states whether releaseVersion is valid (and why). If releaseVersion is
+// not set, it suggests a new version.
+func (r *report) String() string {
+ buf := &strings.Builder{}
+ for _, p := range r.packages {
+ buf.WriteString(p.String())
+ }
+
+ if !r.canVerifyReleaseVersion() {
+ return buf.String()
+ }
+
+ if len(r.release.diagnostics) > 0 {
+ buf.WriteString("# diagnostics\n")
+ for _, d := range r.release.diagnostics {
+ fmt.Fprintln(buf, d)
+ }
+ buf.WriteByte('\n')
+ }
+
+ buf.WriteString("# summary\n")
+ baseVersion := r.base.version
+ if r.base.modPath != r.release.modPath {
+ baseVersion = r.base.modPath + "@" + baseVersion
+ }
+ if r.base.versionInferred {
+ fmt.Fprintf(buf, "Inferred base version: %s\n", baseVersion)
+ } else if r.base.versionQuery != "" {
+ fmt.Fprintf(buf, "Base version: %s (%s)\n", baseVersion, r.base.versionQuery)
+ }
+
+ if r.versionInvalid != nil {
+ fmt.Fprintln(buf, r.versionInvalid)
+ } else if r.release.versionInferred {
+ if r.release.tagPrefix == "" {
+ fmt.Fprintf(buf, "Suggested version: %s\n", r.release.version)
+ } else {
+ fmt.Fprintf(buf, "Suggested version: %[1]s (with tag %[2]s%[1]s)\n", r.release.version, r.release.tagPrefix)
+ }
+ } else if r.release.version != "" {
+ if r.release.tagPrefix == "" {
+ fmt.Fprintf(buf, "%s is a valid semantic version for this release.\n", r.release.version)
+
+ if semver.Compare(r.release.version, "v0.0.0-99999999999999-zzzzzzzzzzzz") < 0 {
+ fmt.Fprintf(buf, `Note: %s sorts lower in MVS than pseudo-versions, which may be
+unexpected for users. So, it may be better to choose a different suffix.`, r.release.version)
+ }
+ } else {
+ fmt.Fprintf(buf, "%[1]s (with tag %[2]s%[1]s) is a valid semantic version for this release\n", r.release.version, r.release.tagPrefix)
+ }
+ }
+
+ if r.versionInvalid == nil && r.haveBaseErrors {
+ fmt.Fprintln(buf, "Errors were found in the base version. Some API changes may be omitted.")
+ }
+
+ return buf.String()
+}
+
+func (r *report) addPackage(p packageReport) {
+ r.packages = append(r.packages, p)
+ if len(p.baseErrors) == 0 && len(p.releaseErrors) == 0 {
+ // Only count compatible and incompatible changes if there were no errors.
+ // When there are errors, definitions may be missing, and fixes may appear
+ // incompatible when they are not. Changes will still be reported, but
+ // they won't affect version validation or suggestions.
+ for _, c := range p.Changes {
+ if !c.Compatible && len(p.releaseErrors) == 0 {
+ r.haveIncompatibleChanges = true
+ } else if c.Compatible && len(p.baseErrors) == 0 && len(p.releaseErrors) == 0 {
+ r.haveCompatibleChanges = true
+ }
+ }
+ }
+ if len(p.baseErrors) > 0 {
+ r.haveBaseErrors = true
+ }
+ if len(p.releaseErrors) > 0 {
+ r.haveReleaseErrors = true
+ }
+}
+
+// validateReleaseVersion checks whether r.release.version is valid.
+// If r.release.version is not valid, an error is returned explaining why.
+// r.release.version must be set.
+func (r *report) validateReleaseVersion() {
+ if r.release.version == "" {
+ panic("validateVersion called without version")
+ }
+ setNotValid := func(format string, args ...interface{}) {
+ r.versionInvalid = &versionMessage{
+ message: fmt.Sprintf("%s is not a valid semantic version for this release.", r.release.version),
+ reason: fmt.Sprintf(format, args...),
+ }
+ }
+
+ if r.haveReleaseErrors {
+ if r.haveReleaseErrors {
+ setNotValid("Errors were found in one or more packages.")
+ return
+ }
+ }
+
+ // TODO(jayconrod): link to documentation for all of these errors.
+
+ // Check that the major version matches the module path.
+ _, suffix, ok := module.SplitPathVersion(r.release.modPath)
+ if !ok {
+ setNotValid("%s: could not find version suffix in module path", r.release.modPath)
+ return
+ }
+ if suffix != "" {
+ if suffix[0] != '/' && suffix[0] != '.' {
+ setNotValid("%s: unknown module path version suffix: %q", r.release.modPath, suffix)
+ return
+ }
+ pathMajor := suffix[1:]
+ major := semver.Major(r.release.version)
+ if pathMajor != major {
+ setNotValid(`The major version %s does not match the major version suffix
+in the module path: %s`, major, r.release.modPath)
+ return
+ }
+ } else if major := semver.Major(r.release.version); major != "v0" && major != "v1" {
+ setNotValid(`The module path does not end with the major version suffix /%s,
+which is required for major versions v2 or greater.`, major)
+ return
+ }
+
+ for _, v := range r.base.existingVersions {
+ if semver.Compare(v, r.release.version) == 0 {
+ setNotValid("version %s already exists", v)
+ }
+ }
+
+ // Check that compatible / incompatible changes are consistent.
+ if semver.Major(r.base.version) == "v0" || r.base.modPath != r.release.modPath {
+ return
+ }
+ if r.haveIncompatibleChanges {
+ setNotValid("There are incompatible changes.")
+ return
+ }
+ if r.haveCompatibleChanges && semver.MajorMinor(r.base.version) == semver.MajorMinor(r.release.version) {
+ setNotValid(`There are compatible changes, but the minor version is not incremented
+over the base version (%s).`, r.base.version)
+ return
+ }
+
+ if r.release.highestTransitiveVersion != "" && semver.Compare(r.release.highestTransitiveVersion, r.release.version) > 0 {
+ setNotValid(`Module indirectly depends on a higher version of itself (%s).
+ `, r.release.highestTransitiveVersion)
+ }
+}
+
+// suggestReleaseVersion suggests a new version consistent with observed
+// changes.
+func (r *report) suggestReleaseVersion() {
+ setNotValid := func(format string, args ...interface{}) {
+ r.versionInvalid = &versionMessage{
+ message: "Cannot suggest a release version.",
+ reason: fmt.Sprintf(format, args...),
+ }
+ }
+ setVersion := func(v string) {
+ r.release.version = v
+ r.release.versionInferred = true
+ }
+
+ if r.base.modPath != r.release.modPath {
+ setNotValid("Base module path is different from release.")
+ return
+ }
+
+ if r.haveReleaseErrors || r.haveBaseErrors {
+ setNotValid("Errors were found.")
+ return
+ }
+
+ var major, minor, patch, pre string
+ if r.base.version != "none" {
+ minVersion := r.base.version
+ if r.release.highestTransitiveVersion != "" && semver.Compare(r.release.highestTransitiveVersion, minVersion) > 0 {
+ setNotValid("Module indirectly depends on a higher version of itself (%s) than the base version (%s).", r.release.highestTransitiveVersion, r.base.version)
+ return
+ }
+
+ var err error
+ major, minor, patch, pre, _, err = parseVersion(minVersion)
+ if err != nil {
+ panic(fmt.Sprintf("could not parse base version: %v", err))
+ }
+ }
+
+ if r.haveIncompatibleChanges && r.base.version != "none" && pre == "" && major != "0" {
+ setNotValid("Incompatible changes were detected.")
+ return
+ // TODO(jayconrod): briefly explain how to prepare major version releases
+ // and link to documentation.
+ }
+
+ // Check whether we're comparing to the latest version of base.
+ //
+ // This could happen further up, but we want the more pressing errors above
+ // to take precedence.
+ var latestForBaseMajor string
+ for _, v := range r.base.existingVersions {
+ if semver.Major(v) != semver.Major(r.base.version) {
+ continue
+ }
+ if latestForBaseMajor == "" || semver.Compare(latestForBaseMajor, v) < 0 {
+ latestForBaseMajor = v
+ }
+ }
+ if latestForBaseMajor != "" && latestForBaseMajor != r.base.version {
+ setNotValid(fmt.Sprintf("Can only suggest a release version when compared against the most recent version of this major: %s.", latestForBaseMajor))
+ return
+ }
+
+ if r.base.version == "none" {
+ if _, pathMajor, ok := module.SplitPathVersion(r.release.modPath); !ok {
+ panic(fmt.Sprintf("could not parse module path %q", r.release.modPath))
+ } else if pathMajor == "" {
+ setVersion("v0.1.0")
+ } else {
+ setVersion(pathMajor[1:] + ".0.0")
+ }
+ return
+ }
+
+ if pre != "" {
+ // suggest non-prerelease version
+ } else if r.haveCompatibleChanges || (r.haveIncompatibleChanges && major == "0") || r.requirementsChanged() {
+ minor = incDecimal(minor)
+ patch = "0"
+ } else {
+ patch = incDecimal(patch)
+ }
+ setVersion(fmt.Sprintf("v%s.%s.%s", major, minor, patch))
+ return
+}
+
+// canVerifyReleaseVersion returns true if we can safely suggest a new version
+// or if we can verify the version passed in with -version is safe to tag.
+func (r *report) canVerifyReleaseVersion() bool {
+ // For now, return true if the base and release module paths are the same,
+ // ignoring the major version suffix.
+ // TODO(#37562, #39192, #39666, #40267): there are many more situations when
+ // we can't verify a new version.
+ basePath := strings.TrimSuffix(r.base.modPath, r.base.modPathMajor)
+ releasePath := strings.TrimSuffix(r.release.modPath, r.release.modPathMajor)
+ return basePath == releasePath
+}
+
+// requirementsChanged reports whether requirements have changed from base to
+// version.
+//
+// requirementsChanged reports true for,
+// - A requirement was upgraded to a higher minor version.
+// - A requirement was added.
+// - The version of Go was incremented.
+//
+// It does not report true when, for example, a requirement was downgraded or
+// remove. We care more about the former since that might force dependent
+// modules that have the same dependency to upgrade.
+func (r *report) requirementsChanged() bool {
+ if r.base.goModFile == nil {
+ // There wasn't a modfile before, and now there is.
+ return true
+ }
+
+ // baseReqs is a map of module path to MajorMinor of the base module
+ // requirements.
+ baseReqs := make(map[string]string)
+ for _, r := range r.base.goModFile.Require {
+ baseReqs[r.Mod.Path] = r.Mod.Version
+ }
+
+ for _, r := range r.release.goModFile.Require {
+ if _, ok := baseReqs[r.Mod.Path]; !ok {
+ // A module@version was added to the "require" block between base
+ // and release.
+ return true
+ }
+ if semver.Compare(semver.MajorMinor(r.Mod.Version), semver.MajorMinor(baseReqs[r.Mod.Path])) > 0 {
+ // The version of r.Mod.Path increased from base to release.
+ return true
+ }
+ }
+
+ if r.release.goModFile.Go != nil && r.base.goModFile.Go != nil {
+ if r.release.goModFile.Go.Version > r.base.goModFile.Go.Version {
+ // The Go version increased from base to release.
+ return true
+ }
+ }
+
+ return false
+}
+
+// isSuccessful returns true the module appears to be safe to release at the
+// proposed or suggested version.
+func (r *report) isSuccessful() bool {
+ return len(r.release.diagnostics) == 0 && r.versionInvalid == nil
+}
+
+type versionMessage struct {
+ message, reason string
+}
+
+func (m versionMessage) String() string {
+ return m.message + "\n" + m.reason + "\n"
+}
+
+// incDecimal returns the decimal string incremented by 1.
+func incDecimal(decimal string) string {
+ // Scan right to left turning 9s to 0s until you find a digit to increment.
+ digits := []byte(decimal)
+ i := len(digits) - 1
+ for ; i >= 0 && digits[i] == '9'; i-- {
+ digits[i] = '0'
+ }
+ if i >= 0 {
+ digits[i]++
+ } else {
+ // digits is all zeros
+ digits[0] = '1'
+ digits = append(digits, '0')
+ }
+ return string(digits)
+}
+
+type packageReport struct {
+ apidiff.Report
+ path string
+ baseErrors, releaseErrors []packages.Error
+}
+
+func (p *packageReport) String() string {
+ if len(p.Changes) == 0 && len(p.baseErrors) == 0 && len(p.releaseErrors) == 0 {
+ return ""
+ }
+ buf := &strings.Builder{}
+ fmt.Fprintf(buf, "# %s\n", p.path)
+ if len(p.baseErrors) > 0 {
+ fmt.Fprintf(buf, "## errors in base version:\n")
+ for _, e := range p.baseErrors {
+ fmt.Fprintln(buf, e)
+ }
+ buf.WriteByte('\n')
+ }
+ if len(p.releaseErrors) > 0 {
+ fmt.Fprintf(buf, "## errors in release version:\n")
+ for _, e := range p.releaseErrors {
+ fmt.Fprintln(buf, e)
+ }
+ buf.WriteByte('\n')
+ }
+ if len(p.Changes) > 0 {
+ var compatible, incompatible []apidiff.Change
+ for _, c := range p.Changes {
+ if c.Compatible {
+ compatible = append(compatible, c)
+ } else {
+ incompatible = append(incompatible, c)
+ }
+ }
+ if len(incompatible) > 0 {
+ fmt.Fprintf(buf, "## incompatible changes\n")
+ for _, c := range incompatible {
+ fmt.Fprintln(buf, c.Message)
+ }
+ }
+ if len(compatible) > 0 {
+ fmt.Fprintf(buf, "## compatible changes\n")
+ for _, c := range compatible {
+ fmt.Fprintln(buf, c.Message)
+ }
+ }
+ buf.WriteByte('\n')
+ }
+ return buf.String()
+}
+
+// parseVersion returns the major, minor, and patch numbers, prerelease text,
+// and metadata for a given version.
+//
+// TODO(jayconrod): extend semver to do this and delete this function.
+func parseVersion(vers string) (major, minor, patch, pre, meta string, err error) {
+ if !strings.HasPrefix(vers, "v") {
+ return "", "", "", "", "", fmt.Errorf("version %q does not start with 'v'", vers)
+ }
+ base := vers[1:]
+ if i := strings.IndexByte(base, '+'); i >= 0 {
+ meta = base[i+1:]
+ base = base[:i]
+ }
+ if i := strings.IndexByte(base, '-'); i >= 0 {
+ pre = base[i+1:]
+ base = base[:i]
+ }
+ parts := strings.Split(base, ".")
+ if len(parts) != 3 {
+ return "", "", "", "", "", fmt.Errorf("version %q should have three numbers", vers)
+ }
+ major, minor, patch = parts[0], parts[1], parts[2]
+ return major, minor, patch, pre, meta, nil
+}
diff --git a/cmd/gorelease/testdata/README.md b/cmd/gorelease/testdata/README.md
new file mode 100644
index 0000000..3c4880a
--- /dev/null
+++ b/cmd/gorelease/testdata/README.md
@@ -0,0 +1,95 @@
+This directory contains most tests for gorelease. Each test runs gorelease (the
+`runRelease` function) with a given set of flags in a temporary directory
+populated with files specified in the test itself or files from the module test
+proxy. The output is compared against a golden `want` file specified in the
+test.
+
+## Test flags
+
+A specific test may be run with a command like:
+
+ go test -run=TestRelease/basic/v0_patch_suggest
+
+where `basic/v0_patch_suggest` matches the file
+`testdata/basic/v0_patch_suggest.test`.
+
+The `-u` flag adds or updates the `want` file in each test to match the output.
+This is useful for fixing tests after an intended change in behavior.
+
+ go test -run=TestRelease/basic/v0_patch_suggest -u
+
+The `-testwork` flag instructs the test framework to leave the test's temporary
+directory and module proxy in place after running the test. This is useful
+for debugging.
+
+## Test format
+
+Tests are written in `.test` files in `testdata` subdirectories. Each `.test`
+file is a valid txtar file (see `golang.org/x/tools/txtar`). The comment section
+contains the test parameters, which are a series of `key=value` pairs. Blank
+lines and comments starting with `#` are allowed in this section. Valid keys
+are:
+
+* `mod`: sets the module path. Must be specified together with `version`. Copies
+ the content of a module out of the test proxy into a temporary directory
+ where `gorelease` is run.
+* `version`: specified together with `mod`, it sets the version to retrieve from
+ the test proxy. See more information below.
+* `base`: the value of the `-base` flag passed to `gorelease`.
+* `release`: the value of the `-version` flag passed to `gorelease`.
+* `dir`: the directory where `gorelease` should be invoked. Useful when the test
+ describes a whole repository, and `gorelease` should be invoked in a
+ subdirectory.
+* `error`: true if the test expects a hard error. False by default.
+* `success`: true if the test expects a report to be printed with no errors
+ or diagnostics. True by default.
+* `skip`: non-empty if the test should be skipped. The value is a string passed
+ to `t.Skip`.
+* `proxyVersions`: empty if the test should include all `mod/` entries in the
+ proxy, or else a comma-separated list of the modpath@version's it should
+ include.
+
+Test archives have a file named `want`, containing the expected output of the
+test. A test will fail if the actual output differs from `want`.
+
+If the `mod` and `version` parameters are not set, other files will be extracted
+to the temporary directory where `gorelease` runs.
+
+## Populating module contents
+
+When building a test in `testdata/`, there are two ways to populate the module
+directory being tested:
+
+### Option 1: inline
+
+You can inline files in the `.test` folder as described in
+https://pkg.go.dev/golang.org/x/tools/txtar#hdr-Txtar_format. For example,
+
+```
+-- some.file --
+the contents
+of the file
+```
+
+### Option 2: specify an existing file
+
+Often, multiple tests want to share the same setup - the same files. So, users
+can write these common files in `testdata/mod/`, and use one of these files as
+the module directory contents.
+
+To specify a file in `testdata/mod/` to use as the module contents.
+
+## Module format
+
+Tests run with `GOPROXY` set to a local URL that points to a test proxy. The
+test proxy serves modules described by `.txt` files in the `testdata/mod/`
+subdirectory.
+
+Each module is a txtar archive named `$modpath_$version.txt` where `$modpath`
+is the module path (with slashes replaced with underscores) and `$version` is
+the version. If the archive contains a file named `.mod`, that will be used to
+respond to `.mod` requests; otherwise, `go.mod` will be used (`.mod` is only
+necessary for modules that lack `go.mod` files). If the archive contains a
+file named `.info`, that will be used to respond to `.info` requests; otherwise,
+`.info` is synthesized from the version. All other files in the archive are
+packed into a `.zip` file to satisfy `.zip` requests.
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test
new file mode 100644
index 0000000..bea8be6
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test
@@ -0,0 +1,24 @@
+mod=example.com/basic
+base=v1.0.1
+success=false
+# A() was removed, which is a breaking change: it shouldn't try to suggest a
+# higher version.
+-- want --
+# example.com/basic/a
+## incompatible changes
+A: removed
+## compatible changes
+B: added
+
+# summary
+Cannot suggest a release version.
+Incompatible changes were detected.
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func B() int { return 0 }
+
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test
new file mode 100644
index 0000000..40eda64
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test
@@ -0,0 +1,23 @@
+mod=example.com/basic
+base=v0.0.1
+success=false
+# B() was added, so now it should suggest a new minor version. But, there's a
+# later version that already exists: so it should not try to suggest anything at
+# all.
+-- want --
+# example.com/basic/a
+## compatible changes
+B: added
+
+# summary
+Cannot suggest a release version.
+Can only suggest a release version when compared against the most recent version of this major: v0.1.2.
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
+func B() int { return 0 }
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test
new file mode 100644
index 0000000..209d9a9
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test
@@ -0,0 +1,23 @@
+mod=example.com/basic
+base=v0.1.0
+success=false
+# A() was changed in a small way, so now it should suggest a new patch version.
+# But, there's a later version that already exists: so it should not try to
+# suggest anything at all.
+-- want --
+# summary
+Cannot suggest a release version.
+Can only suggest a release version when compared against the most recent version of this major: v0.1.2.
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test
new file mode 100644
index 0000000..8ba7cfc
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test
@@ -0,0 +1,18 @@
+mod=example.com/basic
+base=v0.0.1
+release=v0.1.0
+success=false
+# The contents below are a copy of the v0.0.1 contents - nothing has changed.
+# But v0.1.0 already exists, so it should present a diagnostic.
+-- want --
+# summary
+v0.1.0 is not a valid semantic version for this release.
+version v0.1.0 already exists
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
diff --git a/cmd/gorelease/testdata/basic/README.txt b/cmd/gorelease/testdata/basic/README.txt
new file mode 100644
index 0000000..708a0c1
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/README.txt
@@ -0,0 +1,11 @@
+Module example.com/basic tests basic functionality of gorelease.
+It verifies that versions are correctly suggested or verified after
+various changes.
+
+All revisions are stored in the mod directory. The same series of
+changes is made across three major versions, v0, v1, and v2:
+
+vX.0.1 - simple package
+vX.1.0 - compatible change: add a function and a package
+vX.1.1 - internal change: function returns different value
+vX.1.2 - incompatible change: delete a function and a package
diff --git a/cmd/gorelease/testdata/basic/v0_compatible_suggest.test b/cmd/gorelease/testdata/basic/v0_compatible_suggest.test
new file mode 100644
index 0000000..6560f55
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_compatible_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic
+version=v0.1.0
+base=v0.0.1
+proxyVersions=example.com/basic@v0.0.1
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/b
+## compatible changes
+package added
+
+# summary
+Suggested version: v0.1.0
diff --git a/cmd/gorelease/testdata/basic/v0_compatible_suggest_git.test b/cmd/gorelease/testdata/basic/v0_compatible_suggest_git.test
new file mode 100644
index 0000000..080cafd
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_compatible_suggest_git.test
@@ -0,0 +1,16 @@
+mod=example.com/basic
+version=v0.1.0
+base=v0.0.1
+proxyVersions=example.com/basic@v0.0.1
+vcs=git
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/b
+## compatible changes
+package added
+
+# summary
+Suggested version: v0.1.0
diff --git a/cmd/gorelease/testdata/basic/v0_compatible_suggest_hg.test b/cmd/gorelease/testdata/basic/v0_compatible_suggest_hg.test
new file mode 100644
index 0000000..329ea88
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_compatible_suggest_hg.test
@@ -0,0 +1,16 @@
+mod=example.com/basic
+version=v0.1.0
+base=v0.0.1
+proxyVersions=example.com/basic@v0.0.1
+vcs=hg
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/b
+## compatible changes
+package added
+
+# summary
+Suggested version: v0.1.0
diff --git a/cmd/gorelease/testdata/basic/v0_compatible_verify.test b/cmd/gorelease/testdata/basic/v0_compatible_verify.test
new file mode 100644
index 0000000..b5c726d
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_compatible_verify.test
@@ -0,0 +1,16 @@
+mod=example.com/basic
+version=v0.1.0
+base=v0.0.1
+release=v0.1.0
+proxyVersions=example.com/basic@v0.0.1
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/b
+## compatible changes
+package added
+
+# summary
+v0.1.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test b/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test
new file mode 100644
index 0000000..d07a48f
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic
+version=v0.1.2
+base=v0.1.1
+proxyVersions=example.com/basic@v0.1.1
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+Suggested version: v0.2.0
diff --git a/cmd/gorelease/testdata/basic/v0_incompatible_verify.test b/cmd/gorelease/testdata/basic/v0_incompatible_verify.test
new file mode 100644
index 0000000..b8355ea
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_incompatible_verify.test
@@ -0,0 +1,16 @@
+mod=example.com/basic
+version=v0.1.2
+base=v0.1.1
+release=v0.1.2
+proxyVersions=example.com/basic@v0.1.1
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+v0.1.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v0_nobase_suggest.test b/cmd/gorelease/testdata/basic/v0_nobase_suggest.test
new file mode 100644
index 0000000..b059fc0
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_nobase_suggest.test
@@ -0,0 +1,6 @@
+mod=example.com/basic
+version=v0.1.1
+base=none
+-- want --
+# summary
+Suggested version: v0.1.0
diff --git a/cmd/gorelease/testdata/basic/v0_patch_suggest.test b/cmd/gorelease/testdata/basic/v0_patch_suggest.test
new file mode 100644
index 0000000..a5f957a
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_patch_suggest.test
@@ -0,0 +1,7 @@
+mod=example.com/basic
+version=v0.1.1
+base=v0.1.0
+proxyVersions=example.com/basic@v0.1.0
+-- want --
+# summary
+Suggested version: v0.1.1
diff --git a/cmd/gorelease/testdata/basic/v0_patch_verify.test b/cmd/gorelease/testdata/basic/v0_patch_verify.test
new file mode 100644
index 0000000..d26ff70
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_patch_verify.test
@@ -0,0 +1,8 @@
+mod=example.com/basic
+version=v0.1.1
+base=v0.1.0
+release=v0.1.1
+proxyVersions=example.com/basic@v0.1.0
+-- want --
+# summary
+v0.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v0_pre_suggest.test b/cmd/gorelease/testdata/basic/v0_pre_suggest.test
new file mode 100644
index 0000000..ca45c40
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_pre_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic
+version=v0.1.2
+base=v0.1.1-pre
+proxyVersions=example.com/basic@v0.1.1-pre
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+Suggested version: v0.1.1
diff --git a/cmd/gorelease/testdata/basic/v0_v1_incompatible_verify.test b/cmd/gorelease/testdata/basic/v0_v1_incompatible_verify.test
new file mode 100644
index 0000000..7a68306
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v0_v1_incompatible_verify.test
@@ -0,0 +1,15 @@
+mod=example.com/basic
+version=v0.1.2
+base=v0.1.1
+release=v1.0.0
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+v1.0.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_autobase_suggest.test b/cmd/gorelease/testdata/basic/v1_autobase_suggest.test
new file mode 100644
index 0000000..100cc4c
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_autobase_suggest.test
@@ -0,0 +1,6 @@
+mod=example.com/basic
+version=v0.1.2
+-- want --
+# summary
+Inferred base version: v1.1.2
+Suggested version: v1.1.3
diff --git a/cmd/gorelease/testdata/basic/v1_autobase_verify.test b/cmd/gorelease/testdata/basic/v1_autobase_verify.test
new file mode 100644
index 0000000..6df9c6e
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_autobase_verify.test
@@ -0,0 +1,7 @@
+mod=example.com/basic
+version=v1.0.1
+release=v1.0.2
+-- want --
+# summary
+Inferred base version: v1.0.1
+v1.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_compatible_suggest.test b/cmd/gorelease/testdata/basic/v1_compatible_suggest.test
new file mode 100644
index 0000000..fe63e75
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_compatible_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic
+version=v1.1.0
+base=v1.0.1
+proxyVersions=example.com/basic@v1.0.1
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/b
+## compatible changes
+package added
+
+# summary
+Suggested version: v1.1.0
diff --git a/cmd/gorelease/testdata/basic/v1_compatible_verify.test b/cmd/gorelease/testdata/basic/v1_compatible_verify.test
new file mode 100644
index 0000000..f552aef
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_compatible_verify.test
@@ -0,0 +1,16 @@
+mod=example.com/basic
+version=v1.1.0
+base=v1.0.1
+release=v1.1.0
+proxyVersions=example.com/basic@v1.0.1
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/b
+## compatible changes
+package added
+
+# summary
+v1.1.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test b/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test
new file mode 100644
index 0000000..a662cb1
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test
@@ -0,0 +1,11 @@
+mod=example.com/basicfork
+base=example.com/basic@v1.1.1
+version=v1.1.2
+release=v1.1.2
+proxyVersions=example.com/basic@v1.1.1
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+## compatible changes
+A3: added
diff --git a/cmd/gorelease/testdata/basic/v1_fork_base_verify.test b/cmd/gorelease/testdata/basic/v1_fork_base_verify.test
new file mode 100644
index 0000000..ff3faab
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_fork_base_verify.test
@@ -0,0 +1,15 @@
+# Compare a fork (with module path example.com/basic, downloaded from
+# example.com/basicfork) with a local module (with module path
+# example.com/basic).
+mod=example.com/basic
+version=v1.1.2
+base=example.com/basicfork@v1.1.2
+release=v1.1.3
+-- want --
+# example.com/basicfork/a
+## incompatible changes
+A3: removed
+
+# example.com/basicfork/b
+## incompatible changes
+package removed
diff --git a/cmd/gorelease/testdata/basic/v1_incompatible_suggest.test b/cmd/gorelease/testdata/basic/v1_incompatible_suggest.test
new file mode 100644
index 0000000..a955b10
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_incompatible_suggest.test
@@ -0,0 +1,16 @@
+mod=example.com/basic
+version=v1.1.2
+base=v1.1.1
+success=false
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+Cannot suggest a release version.
+Incompatible changes were detected.
diff --git a/cmd/gorelease/testdata/basic/v1_incompatible_verify.test b/cmd/gorelease/testdata/basic/v1_incompatible_verify.test
new file mode 100644
index 0000000..655ed6a
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_incompatible_verify.test
@@ -0,0 +1,18 @@
+mod=example.com/basic
+version=v1.1.2
+base=v1.1.1
+success=false
+release=v1.1.2
+proxyVersions=example.com/basic@v1.1.1
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+v1.1.2 is not a valid semantic version for this release.
+There are incompatible changes.
diff --git a/cmd/gorelease/testdata/basic/v1_patch_suggest.test b/cmd/gorelease/testdata/basic/v1_patch_suggest.test
new file mode 100644
index 0000000..936f26a
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_patch_suggest.test
@@ -0,0 +1,7 @@
+mod=example.com/basic
+version=v1.1.1
+base=v1.1.0
+proxyVersions=example.com/basic@v1.1.0
+-- want --
+# summary
+Suggested version: v1.1.1
diff --git a/cmd/gorelease/testdata/basic/v1_patch_verify.test b/cmd/gorelease/testdata/basic/v1_patch_verify.test
new file mode 100644
index 0000000..f134744
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_patch_verify.test
@@ -0,0 +1,8 @@
+mod=example.com/basic
+version=v1.1.1
+base=v1.1.0
+release=v1.1.1
+proxyVersions=example.com/basic@v1.1.0
+-- want --
+# summary
+v1.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_pre_suggest.test b/cmd/gorelease/testdata/basic/v1_pre_suggest.test
new file mode 100644
index 0000000..c715603
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_pre_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic
+version=v1.1.2
+base=v1.1.1-pre
+proxyVersions=example.com/basic@v1.1.1-pre
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+Suggested version: v1.1.1
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_higher.test b/cmd/gorelease/testdata/basic/v1_querybase_higher.test
new file mode 100644
index 0000000..ba6838f
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_querybase_higher.test
@@ -0,0 +1,7 @@
+mod=example.com/basic
+version=v1.0.1
+release=v1.0.1
+base=>v1.0.1
+error=true
+-- want --
+base version v1.1.0 (>v1.0.1) must be lower than release version v1.0.1
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_suggest.test b/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
new file mode 100644
index 0000000..c1dfb9d
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
@@ -0,0 +1,8 @@
+mod=example.com/basic
+version=v1.0.1
+base=version-1.0.1
+proxyVersions=example.com/basic@version-1.0.1,example.com/basic@v1.0.1
+-- want --
+# summary
+Base version: v1.0.1 (version-1.0.1)
+Suggested version: v1.0.2
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_verify.test b/cmd/gorelease/testdata/basic/v1_querybase_verify.test
new file mode 100644
index 0000000..9383c3f
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_querybase_verify.test
@@ -0,0 +1,8 @@
+mod=example.com/basic
+version=v1.0.1
+base=version-1.0.1
+release=v1.0.2
+-- want --
+# summary
+Base version: v1.0.1 (version-1.0.1)
+v1.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test
new file mode 100644
index 0000000..ce6b674
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test
@@ -0,0 +1,17 @@
+mod=example.com/basic/v2
+base=example.com/basic@>=v1.1.0
+version=v2.0.1
+release=v2.0.1
+proxyVersions=example.com/basic@v1.1.0
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+Base version: example.com/basic@v1.1.0 (>=v1.1.0)
+v2.0.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_suggest.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_suggest.test
new file mode 100644
index 0000000..d70ecc9
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_suggest.test
@@ -0,0 +1,17 @@
+mod=example.com/basic/v2
+base=example.com/basic
+version=v2.1.0
+success=false
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/v2/b
+## compatible changes
+package added
+
+# summary
+Inferred base version: example.com/basic@v1.1.2
+Cannot suggest a release version.
+Base module path is different from release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test
new file mode 100644
index 0000000..85f7f86
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test
@@ -0,0 +1,17 @@
+mod=example.com/basic/v2
+base=example.com/basic
+version=v2.1.0
+release=v2.1.0
+proxyVersions=example.com/basic@v1.1.2
+-- want --
+# example.com/basic/a
+## compatible changes
+A2: added
+
+# example.com/basic/v2/b
+## compatible changes
+package added
+
+# summary
+Inferred base version: example.com/basic@v1.1.2
+v2.1.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test
new file mode 100644
index 0000000..63d3322
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test
@@ -0,0 +1,16 @@
+mod=example.com/basic/v2
+base=example.com/basic@v1.1.0
+version=v2.0.1
+release=v2.0.1
+proxyVersions=example.com/basic@v1.1.0
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+v2.0.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_version_suggest.test b/cmd/gorelease/testdata/basic/v1_v2_base_version_suggest.test
new file mode 100644
index 0000000..683bb33
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_version_suggest.test
@@ -0,0 +1,8 @@
+mod=example.com/basic/v2
+base=v1.1.0
+version=v2.1.0
+success=false
+-- want --
+# summary
+Cannot suggest a release version.
+Base module path is different from release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_version_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_version_verify.test
new file mode 100644
index 0000000..bc8e31a
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_version_verify.test
@@ -0,0 +1,7 @@
+mod=example.com/basic/v2
+base=v1.1.0
+version=v2.1.0
+release=v2.1.0
+-- want --
+# summary
+v2.1.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_v2_incompatible_verify.test b/cmd/gorelease/testdata/basic/v1_v2_incompatible_verify.test
new file mode 100644
index 0000000..07bbde9
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v1_v2_incompatible_verify.test
@@ -0,0 +1,18 @@
+mod=example.com/basic
+version=v1.1.2
+base=v1.1.1
+release=v2.0.0
+success=false
+-- want --
+# example.com/basic/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/b
+## incompatible changes
+package removed
+
+# summary
+v2.0.0 is not a valid semantic version for this release.
+The module path does not end with the major version suffix /v2,
+which is required for major versions v2 or greater.
diff --git a/cmd/gorelease/testdata/basic/v2_compatible_suggest.test b/cmd/gorelease/testdata/basic/v2_compatible_suggest.test
new file mode 100644
index 0000000..86e60cb
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_compatible_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic/v2
+version=v2.1.0
+base=v2.0.1
+proxyVersions=example.com/basic/v2@v2.0.1
+-- want --
+# example.com/basic/v2/a
+## compatible changes
+A2: added
+
+# example.com/basic/v2/b
+## compatible changes
+package added
+
+# summary
+Suggested version: v2.1.0
diff --git a/cmd/gorelease/testdata/basic/v2_compatible_verify.test b/cmd/gorelease/testdata/basic/v2_compatible_verify.test
new file mode 100644
index 0000000..79eca35
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_compatible_verify.test
@@ -0,0 +1,16 @@
+mod=example.com/basic/v2
+version=v2.1.0
+base=v2.0.1
+release=v2.1.0
+proxyVersions=example.com/basic/v2@v2.0.1
+-- want --
+# example.com/basic/v2/a
+## compatible changes
+A2: added
+
+# example.com/basic/v2/b
+## compatible changes
+package added
+
+# summary
+v2.1.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v2_incompatible_suggest.test b/cmd/gorelease/testdata/basic/v2_incompatible_suggest.test
new file mode 100644
index 0000000..43aa3fe
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_incompatible_suggest.test
@@ -0,0 +1,16 @@
+mod=example.com/basic/v2
+version=v2.1.2
+base=v2.1.1
+success=false
+-- want --
+# example.com/basic/v2/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/v2/b
+## incompatible changes
+package removed
+
+# summary
+Cannot suggest a release version.
+Incompatible changes were detected.
diff --git a/cmd/gorelease/testdata/basic/v2_incompatible_verify.test b/cmd/gorelease/testdata/basic/v2_incompatible_verify.test
new file mode 100644
index 0000000..68b7321
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_incompatible_verify.test
@@ -0,0 +1,18 @@
+mod=example.com/basic/v2
+version=v2.1.2
+base=v2.1.1
+success=false
+release=v2.1.2
+proxyVersions=example.com/basic/v2@v2.1.1
+-- want --
+# example.com/basic/v2/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/v2/b
+## incompatible changes
+package removed
+
+# summary
+v2.1.2 is not a valid semantic version for this release.
+There are incompatible changes.
diff --git a/cmd/gorelease/testdata/basic/v2_nobase_suggest.test b/cmd/gorelease/testdata/basic/v2_nobase_suggest.test
new file mode 100644
index 0000000..605edf9
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_nobase_suggest.test
@@ -0,0 +1,6 @@
+mod=example.com/basic/v2
+version=v2.1.1
+base=none
+-- want --
+# summary
+Suggested version: v2.0.0
diff --git a/cmd/gorelease/testdata/basic/v2_patch_suggest.test b/cmd/gorelease/testdata/basic/v2_patch_suggest.test
new file mode 100644
index 0000000..b16a863
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_patch_suggest.test
@@ -0,0 +1,7 @@
+mod=example.com/basic/v2
+version=v2.1.1
+base=v2.1.0
+proxyVersions=example.com/basic/v2@v2.1.0
+-- want --
+# summary
+Suggested version: v2.1.1
diff --git a/cmd/gorelease/testdata/basic/v2_patch_verify.test b/cmd/gorelease/testdata/basic/v2_patch_verify.test
new file mode 100644
index 0000000..35d617a
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_patch_verify.test
@@ -0,0 +1,8 @@
+mod=example.com/basic/v2
+version=v2.1.1
+base=v2.1.0
+release=v2.1.1
+proxyVersions=example.com/basic/v2@v2.1.0
+-- want --
+# summary
+v2.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v2_pre_suggest.test b/cmd/gorelease/testdata/basic/v2_pre_suggest.test
new file mode 100644
index 0000000..75d0c39
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v2_pre_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/basic/v2
+version=v2.1.2
+base=v2.1.1-pre
+proxyVersions=example.com/basic/v2@v2.1.1-pre
+-- want --
+# example.com/basic/v2/a
+## incompatible changes
+A2: removed
+
+# example.com/basic/v2/b
+## incompatible changes
+package removed
+
+# summary
+Suggested version: v2.1.1
diff --git a/cmd/gorelease/testdata/basic/v3_autobase_suggest.test b/cmd/gorelease/testdata/basic/v3_autobase_suggest.test
new file mode 100644
index 0000000..eda87d2
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v3_autobase_suggest.test
@@ -0,0 +1,9 @@
+mod=example.com/basic/v3
+-- go.mod --
+module example.com/basic/v3
+
+go 1.13
+-- want --
+# summary
+Inferred base version: none
+Suggested version: v3.0.0
diff --git a/cmd/gorelease/testdata/basic/v3_autobase_verify_error.test b/cmd/gorelease/testdata/basic/v3_autobase_verify_error.test
new file mode 100644
index 0000000..9519d65
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v3_autobase_verify_error.test
@@ -0,0 +1,6 @@
+mod=example.com/basic/v3
+version=v3.0.0-ignore
+release=v3.1.0
+error=true
+-- want --
+could not find base version. Consider setting -version=v3.0.0 if this is a first release, or explicitly set -base=none: no versions found lower than v3.1.0
diff --git a/cmd/gorelease/testdata/basic/v3_autobase_verify_first.test b/cmd/gorelease/testdata/basic/v3_autobase_verify_first.test
new file mode 100644
index 0000000..1cae913
--- /dev/null
+++ b/cmd/gorelease/testdata/basic/v3_autobase_verify_first.test
@@ -0,0 +1,7 @@
+mod=example.com/basic/v3
+version=v3.0.0-ignore
+release=v3.0.0
+-- want --
+# summary
+Inferred base version: none
+v3.0.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/cgo/README.txt b/cmd/gorelease/testdata/cgo/README.txt
new file mode 100644
index 0000000..8bbf9c6
--- /dev/null
+++ b/cmd/gorelease/testdata/cgo/README.txt
@@ -0,0 +1,7 @@
+Module example.com/cgo is used to test that packages with cgo code
+can be loaded without errors when cgo is enabled.
+
+TODO(jayconrod): test modules with cgo-only and cgo / pure Go implementations
+with CGO_ENABLED=0 and 1. But first, decide how multiple platforms and
+build constraints should be handled. Currently, gorelease only considers
+the same configuration as 'go list'. \ No newline at end of file
diff --git a/cmd/gorelease/testdata/cgo/cgo.test b/cmd/gorelease/testdata/cgo/cgo.test
new file mode 100644
index 0000000..b6cea21
--- /dev/null
+++ b/cmd/gorelease/testdata/cgo/cgo.test
@@ -0,0 +1,16 @@
+base=none
+release=v1.0.0
+-- go.mod --
+module example.com/cgo
+
+go 1.13
+-- c.go --
+package cgo
+
+// const int x = 12;
+import "C"
+
+func X() int { return int(C.x) }
+-- want --
+# summary
+v1.0.0 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/cycle/README.md b/cmd/gorelease/testdata/cycle/README.md
new file mode 100644
index 0000000..3596c8b
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/README.md
@@ -0,0 +1 @@
+This directory is for tests related to module cycles. \ No newline at end of file
diff --git a/cmd/gorelease/testdata/cycle/cycle_suggest.test b/cmd/gorelease/testdata/cycle/cycle_suggest.test
new file mode 100644
index 0000000..fc108d7
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/cycle_suggest.test
@@ -0,0 +1,6 @@
+mod=example.com/cycle/v2
+version=v2.0.0
+-- want --
+# summary
+Inferred base version: v2.0.0
+Suggested version: v2.0.1
diff --git a/cmd/gorelease/testdata/cycle/cycle_suggest_error.test b/cmd/gorelease/testdata/cycle/cycle_suggest_error.test
new file mode 100644
index 0000000..133e4e3
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/cycle_suggest_error.test
@@ -0,0 +1,8 @@
+mod=example.com/cycle
+base=v1.0.0
+version=v1.0.0
+success=false
+-- want --
+# summary
+Cannot suggest a release version.
+Module indirectly depends on a higher version of itself (v1.5.0) than the base version (v1.0.0).
diff --git a/cmd/gorelease/testdata/cycle/cycle_verify.test b/cmd/gorelease/testdata/cycle/cycle_verify.test
new file mode 100644
index 0000000..84b4106
--- /dev/null
+++ b/cmd/gorelease/testdata/cycle/cycle_verify.test
@@ -0,0 +1,9 @@
+mod=example.com/cycle
+base=v1.0.0
+version=v1.0.0
+release=v1.0.1
+success=false
+-- want --
+# summary
+v1.0.1 is not a valid semantic version for this release.
+Module indirectly depends on a higher version of itself (v1.5.0).
diff --git a/cmd/gorelease/testdata/empty/README.txt b/cmd/gorelease/testdata/empty/README.txt
new file mode 100644
index 0000000..283befa
--- /dev/null
+++ b/cmd/gorelease/testdata/empty/README.txt
@@ -0,0 +1,2 @@
+Module example.com/empty is used to test that gorelease works
+in a module with no packages.
diff --git a/cmd/gorelease/testdata/empty/empty.test b/cmd/gorelease/testdata/empty/empty.test
new file mode 100644
index 0000000..e9bebb4
--- /dev/null
+++ b/cmd/gorelease/testdata/empty/empty.test
@@ -0,0 +1,8 @@
+mod=example.com/empty
+base=v0.0.1
+version=v0.0.2
+release=v0.0.2
+proxyVersions=example.com/empty@v0.0.1
+-- want --
+# summary
+v0.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/errors/README.txt b/cmd/gorelease/testdata/errors/README.txt
new file mode 100644
index 0000000..071708a
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/README.txt
@@ -0,0 +1,2 @@
+Tests in this directory check that user errors invoking gorelease
+are correctly reported.
diff --git a/cmd/gorelease/testdata/errors/bad_filenames.test b/cmd/gorelease/testdata/errors/bad_filenames.test
new file mode 100644
index 0000000..c8bf6e0
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/bad_filenames.test
@@ -0,0 +1,17 @@
+mod=example.com/ziperrors
+dir=x
+base=none
+error=true
+vcs=git
+-- want --
+testdata/this_file_also_has_a_bad_filename'.txt: malformed file path "testdata/this_file_also_has_a_bad_filename'.txt": invalid char '\''
+testdata/this_file_has_a_bad_filename'.txt: malformed file path "testdata/this_file_has_a_bad_filename'.txt": invalid char '\''
+-- x/go.mod --
+module example.com/x
+
+go 1.12
+-- x/testdata/this_file_has_a_bad_filename'.txt --
+-- x/testdata/this_file_also_has_a_bad_filename'.txt --
+# Verify that errors in submodules are not reported.
+-- x/y/go.mod --
+-- x/y/submodule_bad_filename'.txt -- \ No newline at end of file
diff --git a/cmd/gorelease/testdata/errors/bad_release.test b/cmd/gorelease/testdata/errors/bad_release.test
new file mode 100644
index 0000000..74dc009
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/bad_release.test
@@ -0,0 +1,8 @@
+mod=example.com/errors
+base=v0.1.0
+release=master
+error=true
+-- want --
+usage: gorelease [-base=version] [-version=version]
+release version "master" is not a canonical semantic version
+For more information, run go doc golang.org/x/exp/cmd/gorelease
diff --git a/cmd/gorelease/testdata/errors/base_higher.test b/cmd/gorelease/testdata/errors/base_higher.test
new file mode 100644
index 0000000..a2325f9
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/base_higher.test
@@ -0,0 +1,8 @@
+mod=example.com/errors
+base=v0.2.0
+release=v0.1.0
+error=true
+-- want --
+usage: gorelease [-base=version] [-version=version]
+base version ("v0.2.0") must be lower than release version ("v0.1.0")
+For more information, run go doc golang.org/x/exp/cmd/gorelease
diff --git a/cmd/gorelease/testdata/errors/base_modpath_none.test b/cmd/gorelease/testdata/errors/base_modpath_none.test
new file mode 100644
index 0000000..74a6946
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/base_modpath_none.test
@@ -0,0 +1,7 @@
+mod=example.com/basic/v2
+base=example.com/basic@none
+error=true
+-- want --
+usage: gorelease [-base=version] [-version=version]
+base version ("example.com/basic@none") cannot have version "none" with explicit module path
+For more information, run go doc golang.org/x/exp/cmd/gorelease
diff --git a/cmd/gorelease/testdata/errors/errors.test b/cmd/gorelease/testdata/errors/errors.test
new file mode 100644
index 0000000..2675218
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/errors.test
@@ -0,0 +1,38 @@
+mod=example.com/errors
+version=v0.2.0
+base=v0.1.0
+release=v0.2.0
+success=false
+proxyVersions=example.com/errors@v0.1.0
+-- want --
+# example.com/errors/added
+## errors in release version:
+added/added.go:3:15: undefined: Missing
+
+## compatible changes
+package added
+
+# example.com/errors/broken
+## errors in release version:
+broken/broken.go:3:15: undefined: Missing
+
+## incompatible changes
+X: value changed from 12 to unknown
+
+# example.com/errors/deleted
+## errors in base version:
+deleted/deleted.go:3:15: undefined: Missing
+
+## incompatible changes
+package removed
+
+# example.com/errors/fixed
+## errors in base version:
+fixed/fixed.go:3:15: undefined: Missing
+
+## incompatible changes
+X: value changed from unknown to 12
+
+# summary
+v0.2.0 is not a valid semantic version for this release.
+Errors were found in one or more packages.
diff --git a/cmd/gorelease/testdata/errors/same_base_release.test b/cmd/gorelease/testdata/errors/same_base_release.test
new file mode 100644
index 0000000..4da457b
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/same_base_release.test
@@ -0,0 +1,8 @@
+mod=example.com/errors
+base=v0.1.0
+release=v0.1.0
+error=true
+-- want --
+usage: gorelease [-base=version] [-version=version]
+-base and -version must be different
+For more information, run go doc golang.org/x/exp/cmd/gorelease
diff --git a/cmd/gorelease/testdata/errors/upgrade_base.test b/cmd/gorelease/testdata/errors/upgrade_base.test
new file mode 100644
index 0000000..994b3b7
--- /dev/null
+++ b/cmd/gorelease/testdata/errors/upgrade_base.test
@@ -0,0 +1,6 @@
+mod=example.com/errors
+version=v0.1.0
+base=upgrade
+error=true
+-- want --
+could not resolve version example.com/errors@upgrade: query is based on requirements in main go.mod file
diff --git a/cmd/gorelease/testdata/first/README.txt b/cmd/gorelease/testdata/first/README.txt
new file mode 100644
index 0000000..c735527
--- /dev/null
+++ b/cmd/gorelease/testdata/first/README.txt
@@ -0,0 +1 @@
+Module example.com/first is used to test the first tag for a major version.
diff --git a/cmd/gorelease/testdata/first/v0_0_0.test b/cmd/gorelease/testdata/first/v0_0_0.test
new file mode 100644
index 0000000..5d473ee
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v0_0_0.test
@@ -0,0 +1,12 @@
+mod=example.com/first
+base=none
+release=v0.0.0
+-- want --
+# summary
+v0.0.0 is a valid semantic version for this release.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/first/v0_0_1.test b/cmd/gorelease/testdata/first/v0_0_1.test
new file mode 100644
index 0000000..f32c69e
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v0_0_1.test
@@ -0,0 +1,12 @@
+mod=example.com/first
+base=none
+release=v0.0.1
+-- want --
+# summary
+v0.0.1 is a valid semantic version for this release.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test b/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test
new file mode 100644
index 0000000..90985ee
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test
@@ -0,0 +1,12 @@
+mod=example.com/first
+base=none
+release=v0.1.0-alpha.1
+-- want --
+# summary
+v0.1.0-alpha.1 is a valid semantic version for this release.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/first/v0_1_0.test b/cmd/gorelease/testdata/first/v0_1_0.test
new file mode 100644
index 0000000..05eef2a
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v0_1_0.test
@@ -0,0 +1,12 @@
+mod=example.com/first
+base=none
+release=v0.1.0
+-- want --
+# summary
+v0.1.0 is a valid semantic version for this release.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/first/v0_err.test b/cmd/gorelease/testdata/first/v0_err.test
new file mode 100644
index 0000000..c655554
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v0_err.test
@@ -0,0 +1,19 @@
+mod=example.com/first
+base=none
+release=v0.0.0
+success=false
+
+# TODO(golang.org/issue/36087): go list doesn't report positions in correct
+# place for scanner errors.
+skip=packages.Load gives error with extra "-: " prefix
+-- want --
+example.com/first
+-----------------
+errors in new version:
+ p.go:1:9: illegal character U+003F '?'
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package ?
diff --git a/cmd/gorelease/testdata/first/v1_0_0.test b/cmd/gorelease/testdata/first/v1_0_0.test
new file mode 100644
index 0000000..c7400dc
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v1_0_0.test
@@ -0,0 +1,12 @@
+mod=example.com/first
+base=none
+release=v1.0.0
+-- want --
+# summary
+v1.0.0 is a valid semantic version for this release.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/first/v1_2_3_explicit_none_base.test b/cmd/gorelease/testdata/first/v1_2_3_explicit_none_base.test
new file mode 100644
index 0000000..2e1588f
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v1_2_3_explicit_none_base.test
@@ -0,0 +1,13 @@
+mod=example.com/first
+base=none
+release=v1.2.3
+proxyVersions=
+-- want --
+# summary
+v1.2.3 is a valid semantic version for this release.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/first/v2_err.test b/cmd/gorelease/testdata/first/v2_err.test
new file mode 100644
index 0000000..dfc71d5
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v2_err.test
@@ -0,0 +1,19 @@
+mod=example.com/first/v2
+base=none
+release=v2.0.0
+success=false
+
+# TODO(golang.org/issue/36087): go list doesn't report positions in correct
+# place for scanner errors.
+skip=packages.Load gives error with extra "-: " prefix
+-- want --
+example.com/first
+-----------------
+errors in new version:
+ p.go:1:9: illegal character U+003F '?'
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package ?
diff --git a/cmd/gorelease/testdata/first/v2_moderr.test b/cmd/gorelease/testdata/first/v2_moderr.test
new file mode 100644
index 0000000..833cfa9
--- /dev/null
+++ b/cmd/gorelease/testdata/first/v2_moderr.test
@@ -0,0 +1,15 @@
+mod=example.com/first
+base=none
+release=v2.0.0
+success=false
+-- want --
+# summary
+v2.0.0 is not a valid semantic version for this release.
+The module path does not end with the major version suffix /v2,
+which is required for major versions v2 or greater.
+-- go.mod --
+module example.com/first
+
+go 1.12
+-- p.go --
+package p
diff --git a/cmd/gorelease/testdata/fix/README.txt b/cmd/gorelease/testdata/fix/README.txt
new file mode 100644
index 0000000..e153198
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/README.txt
@@ -0,0 +1,7 @@
+Tests in this directory cover scenarios where errors in a package are fixed.
+
+v1.0.0 is used as the base version for all tests.
+It has an error: the return type of bad.Broken is undefined.
+
+Each test fixes the error and may make other changes (compatible or not).
+Note that fixing a type error in the API appears to be an incompatible change.
diff --git a/cmd/gorelease/testdata/fix/compatible_other_suggest.test b/cmd/gorelease/testdata/fix/compatible_other_suggest.test
new file mode 100644
index 0000000..b4f91d3
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/compatible_other_suggest.test
@@ -0,0 +1,19 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-compatible-other
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# example.com/fix/good
+## compatible changes
+Better: added
+
+# summary
+Cannot suggest a release version.
+Errors were found.
diff --git a/cmd/gorelease/testdata/fix/compatible_other_verify.test b/cmd/gorelease/testdata/fix/compatible_other_verify.test
new file mode 100644
index 0000000..1403643
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/compatible_other_verify.test
@@ -0,0 +1,19 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-compatible-other
+release=v1.1.0
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# example.com/fix/good
+## compatible changes
+Better: added
+
+# summary
+v1.1.0 is a valid semantic version for this release.
+Errors were found in the base version. Some API changes may be omitted.
diff --git a/cmd/gorelease/testdata/fix/compatible_other_verify_err.test b/cmd/gorelease/testdata/fix/compatible_other_verify_err.test
new file mode 100644
index 0000000..d922b15
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/compatible_other_verify_err.test
@@ -0,0 +1,21 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-compatible-other
+release=v1.0.1
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# example.com/fix/good
+## compatible changes
+Better: added
+
+# summary
+v1.0.1 is not a valid semantic version for this release.
+There are compatible changes, but the minor version is not incremented
+over the base version (v1.0.0).
diff --git a/cmd/gorelease/testdata/fix/compatible_same_suggest.test b/cmd/gorelease/testdata/fix/compatible_same_suggest.test
new file mode 100644
index 0000000..72aa3e1
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/compatible_same_suggest.test
@@ -0,0 +1,17 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-compatible-same
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+## compatible changes
+Worse: added
+
+# summary
+Cannot suggest a release version.
+Errors were found.
diff --git a/cmd/gorelease/testdata/fix/compatible_same_verify.test b/cmd/gorelease/testdata/fix/compatible_same_verify.test
new file mode 100644
index 0000000..420f380
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/compatible_same_verify.test
@@ -0,0 +1,17 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-compatible-same
+release=v1.0.1 # not actually valid, but gorelease can't tell
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+## compatible changes
+Worse: added
+
+# summary
+v1.0.1 is a valid semantic version for this release.
+Errors were found in the base version. Some API changes may be omitted.
diff --git a/cmd/gorelease/testdata/fix/incompatible_other_suggest.test b/cmd/gorelease/testdata/fix/incompatible_other_suggest.test
new file mode 100644
index 0000000..8d9465e
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/incompatible_other_suggest.test
@@ -0,0 +1,19 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-incompatible-other
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# example.com/fix/good
+## incompatible changes
+Good: changed from func() int to func() string
+
+# summary
+Cannot suggest a release version.
+Errors were found.
diff --git a/cmd/gorelease/testdata/fix/incompatible_other_verify.test b/cmd/gorelease/testdata/fix/incompatible_other_verify.test
new file mode 100644
index 0000000..20f6f8c
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/incompatible_other_verify.test
@@ -0,0 +1,20 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-incompatible-other
+release=v1.1.0
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# example.com/fix/good
+## incompatible changes
+Good: changed from func() int to func() string
+
+# summary
+v1.1.0 is not a valid semantic version for this release.
+There are incompatible changes.
diff --git a/cmd/gorelease/testdata/fix/incompatible_same_suggest.test b/cmd/gorelease/testdata/fix/incompatible_same_suggest.test
new file mode 100644
index 0000000..f05dd78
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/incompatible_same_suggest.test
@@ -0,0 +1,16 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-incompatible-same
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Bad: changed from func() int to func() string
+Broken: changed from func() invalid type to func() int
+
+# summary
+Cannot suggest a release version.
+Errors were found.
diff --git a/cmd/gorelease/testdata/fix/incompatible_same_verify.test b/cmd/gorelease/testdata/fix/incompatible_same_verify.test
new file mode 100644
index 0000000..2866753
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/incompatible_same_verify.test
@@ -0,0 +1,16 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.1.0-incompatible-same
+release=v1.0.1 # not actually valid, but gorelease can't tell
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Bad: changed from func() int to func() string
+Broken: changed from func() invalid type to func() int
+
+# summary
+v1.0.1 is a valid semantic version for this release.
+Errors were found in the base version. Some API changes may be omitted.
diff --git a/cmd/gorelease/testdata/fix/patch_suggest.test b/cmd/gorelease/testdata/fix/patch_suggest.test
new file mode 100644
index 0000000..3c78cd3
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/patch_suggest.test
@@ -0,0 +1,15 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.0.1-patch
+success=false
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# summary
+Cannot suggest a release version.
+Errors were found.
diff --git a/cmd/gorelease/testdata/fix/patch_verify.test b/cmd/gorelease/testdata/fix/patch_verify.test
new file mode 100644
index 0000000..d1dda68
--- /dev/null
+++ b/cmd/gorelease/testdata/fix/patch_verify.test
@@ -0,0 +1,15 @@
+mod=example.com/fix
+base=v1.0.0
+version=v1.0.1-patch
+release=v1.0.1
+-- want --
+# example.com/fix/bad
+## errors in base version:
+bad/bad.go:3:15: undefined: NOTYPE
+
+## incompatible changes
+Broken: changed from func() invalid type to func() int
+
+# summary
+v1.0.1 is a valid semantic version for this release.
+Errors were found in the base version. Some API changes may be omitted.
diff --git a/cmd/gorelease/testdata/generics/changed_param.test b/cmd/gorelease/testdata/generics/changed_param.test
new file mode 100644
index 0000000..8415e48
--- /dev/null
+++ b/cmd/gorelease/testdata/generics/changed_param.test
@@ -0,0 +1,25 @@
+mod=example.com/generics
+base=v0.0.1
+-- want --
+# example.com/generics/a
+## incompatible changes
+Foo: changed from Foo[V any] to Foo[V Number]
+## compatible changes
+Number: added
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/generics
+
+go 1.18
+-- a/a.go --
+package a
+
+type Number interface {
+ int64 | float64
+}
+
+type Foo[V Number] interface {
+ Value() any
+}
diff --git a/cmd/gorelease/testdata/generics/changed_return.test b/cmd/gorelease/testdata/generics/changed_return.test
new file mode 100644
index 0000000..1592d64
--- /dev/null
+++ b/cmd/gorelease/testdata/generics/changed_return.test
@@ -0,0 +1,19 @@
+mod=example.com/generics
+base=v0.0.1
+-- want --
+# example.com/generics/a
+## incompatible changes
+Foo[V any].Value: changed from func() V to func() int
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/generics
+
+go 1.18
+-- a/a.go --
+package a
+
+type Foo[V any] interface {
+ Value() int
+}
diff --git a/cmd/gorelease/testdata/generics/unchanged.test b/cmd/gorelease/testdata/generics/unchanged.test
new file mode 100644
index 0000000..6cf0d05
--- /dev/null
+++ b/cmd/gorelease/testdata/generics/unchanged.test
@@ -0,0 +1,15 @@
+mod=example.com/generics
+base=v0.0.1
+-- want --
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/generics
+
+go 1.18
+-- a/a.go --
+package a
+
+type Foo[V any] interface {
+ Value() V
+}
diff --git a/cmd/gorelease/testdata/internalcompat/README.txt b/cmd/gorelease/testdata/internalcompat/README.txt
new file mode 100644
index 0000000..89d079e
--- /dev/null
+++ b/cmd/gorelease/testdata/internalcompat/README.txt
@@ -0,0 +1,31 @@
+Modules example.com/internalcompat/{a,b} are copies. One could be a fork
+of the other. An external package p exposes a type from a package q
+within the same module.
+
+If gorelease ran apidiff on the two modules instead of on the individual
+packages, then it should not report differences between these packages. The types
+are distinct, but they correspond (in apidiff terminology), which is the
+important property when considering differences between modules. More
+specifically, the fully qualified type names are identical modulo the change
+to the module path.
+
+But at the time gorelease was written, apidiff did not support module
+comparison. If considered at the package level, the two packages
+example.com/internalcompat/a/p and example.com/internalcompat/b/p
+are incompatible, because the packages they refer to are different.
+
+So case 2 below would apply if whole modules were being diffed, but
+it doesn't here because individual packages are being diffed.
+
+There are three use cases to consider:
+
+1. One module substitutes for the other via a `replace` directive.
+ Only the replacement module is used, and the package paths are effectively
+ identical, so the types are not distinct.
+2. One module subsititutes for the other by rewriting `import` statements
+ globally. All references to the original type become references to the
+ new type, so there is no conflict.
+3. One module substitutes for the other by rewriting some `import` statements
+ but not others (for example, those within a specific consumer package).
+ In this case, the types are distinct, and even if there are no changes,
+ the types are not compatible.
diff --git a/cmd/gorelease/testdata/internalcompat/internalcompat.test b/cmd/gorelease/testdata/internalcompat/internalcompat.test
new file mode 100644
index 0000000..8aee2f5
--- /dev/null
+++ b/cmd/gorelease/testdata/internalcompat/internalcompat.test
@@ -0,0 +1,8 @@
+mod=example.com/internalcompat/b
+version=v1.0.0
+release=v1.0.1
+base=example.com/internalcompat/a@v1.0.0
+-- want --
+# example.com/internalcompat/a/p
+## incompatible changes
+V: changed from example.com/internalcompat/a/q.Q to example.com/internalcompat/b/q.Q
diff --git a/cmd/gorelease/testdata/main/README.txt b/cmd/gorelease/testdata/main/README.txt
new file mode 100644
index 0000000..a8d8104
--- /dev/null
+++ b/cmd/gorelease/testdata/main/README.txt
@@ -0,0 +1,3 @@
+Module example.com/main is used to test changes in main packages.
+Main packages aren't importable, so changes to exported functions should not
+be reported. But we should still report when packages are added or deleted.
diff --git a/cmd/gorelease/testdata/main/add.test b/cmd/gorelease/testdata/main/add.test
new file mode 100644
index 0000000..26089ad
--- /dev/null
+++ b/cmd/gorelease/testdata/main/add.test
@@ -0,0 +1,21 @@
+mod=example.com/main
+base=v0.0.1
+-- want --
+# example.com/main/b
+## compatible changes
+package added
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/main
+
+go 1.14
+-- a/a.go --
+package main
+
+func main() {}
+-- b/b.go --
+package main
+
+func main() {}
diff --git a/cmd/gorelease/testdata/main/change.test b/cmd/gorelease/testdata/main/change.test
new file mode 100644
index 0000000..6960373
--- /dev/null
+++ b/cmd/gorelease/testdata/main/change.test
@@ -0,0 +1,15 @@
+mod=example.com/main
+base=v0.0.1
+-- want --
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/main
+
+go 1.14
+-- a/a.go --
+package main
+
+func Foo() {}
+
+func main() { Foo() }
diff --git a/cmd/gorelease/testdata/main/remove.test b/cmd/gorelease/testdata/main/remove.test
new file mode 100644
index 0000000..702e1d1
--- /dev/null
+++ b/cmd/gorelease/testdata/main/remove.test
@@ -0,0 +1,13 @@
+mod=example.com/main
+base=v0.0.1
+-- want --
+# example.com/main/a
+## incompatible changes
+package removed
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/main
+
+go 1.14
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_v0.0.1.txt
new file mode 100644
index 0000000..e5352bf
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v0.0.1.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v0.1.0.txt b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.0.txt
new file mode 100644
index 0000000..bb39290
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.0.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v0.1.1-pre.txt b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.1-pre.txt
new file mode 100644
index 0000000..743517b
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.1-pre.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v0.1.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.1.txt
new file mode 100644
index 0000000..743517b
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.1.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v0.1.2.txt b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.2.txt
new file mode 100644
index 0000000..4055eaa
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v0.1.2.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v1.0.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_v1.0.1.txt
new file mode 100644
index 0000000..e5352bf
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v1.0.1.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v1.1.0.txt b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.0.txt
new file mode 100644
index 0000000..bb39290
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.0.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v1.1.1-pre.txt b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.1-pre.txt
new file mode 100644
index 0000000..743517b
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.1-pre.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v1.1.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.1.txt
new file mode 100644
index 0000000..743517b
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.1.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v1.1.2.txt b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.2.txt
new file mode 100644
index 0000000..4055eaa
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v1.1.2.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.0.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.0.1.txt
new file mode 100644
index 0000000..97ea69f
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.0.1.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/basic/v2
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.0.txt b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.0.txt
new file mode 100644
index 0000000..e3593b9
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.0.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic/v2
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1-pre.txt b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1-pre.txt
new file mode 100644
index 0000000..aaa86be
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1-pre.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic/v2
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1.txt
new file mode 100644
index 0000000..aaa86be
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.1.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basic/v2
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.2.txt b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.2.txt
new file mode 100644
index 0000000..0d950c1
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v2_v2.1.2.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/basic/v2
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_v3_v3.0.0-ignore.txt b/cmd/gorelease/testdata/mod/example.com_basic_v3_v3.0.0-ignore.txt
new file mode 100644
index 0000000..8e3cecf
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_v3_v3.0.0-ignore.txt
@@ -0,0 +1,6 @@
+-- .info --
+{"Version":"v3.0.0-ignore"}
+-- go.mod --
+module example.com/basic/v3
+
+go 1.13
diff --git a/cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt b/cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt
new file mode 100644
index 0000000..34e769e
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basic_version-1.0.1.txt
@@ -0,0 +1,2 @@
+-- .info --
+{"Version":"v1.0.1"}
diff --git a/cmd/gorelease/testdata/mod/example.com_basicfork_v1.1.2.txt b/cmd/gorelease/testdata/mod/example.com_basicfork_v1.1.2.txt
new file mode 100644
index 0000000..63cb887
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_basicfork_v1.1.2.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/basicfork
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A3() int { return 4 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
new file mode 100644
index 0000000..8f073b0
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
@@ -0,0 +1,15 @@
+-- go.sum --
+example.com/cycle v1.5.0 h1:OkE6KLRVRM5XqIH9MMFIvoYCVjxMh8kqsxUzx5481s4=
+example.com/cycle v1.5.0/go.mod h1://AqZbyNHeLOKZB3J/UPPXaBvk3nCqvqVRbPkffDx60=
+example.com/cycledep v1.0.0 h1:5UDqvIlbZsKzzbZCOaHkxV+X0H6Fi4othxBS57NtjSs=
+example.com/cycledep v1.0.0/go.mod h1:Gc4hO1S1BMZaxOcGHwCRmdVcQP8+jAu/PyEgLdGe0xU=
+-- go.mod --
+module example.com/cycle
+
+go 1.12
+
+require example.com/cycledep v1.0.0
+-- main.go --
+package main
+
+import _ "example.com/cycledep" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt b/cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt
new file mode 100644
index 0000000..98e8fe8
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycle_v1.5.0.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/cycle
+
+go 1.12
+-- a.go --
+package a \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_cycle_v2_v2.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycle_v2_v2.0.0.txt
new file mode 100644
index 0000000..95ae8d4
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycle_v2_v2.0.0.txt
@@ -0,0 +1,19 @@
+# Note: "go get -d ." will add another example.com/cycle/v2 line. That line is
+# non-deterministic, since the module is generate by prepareLoadDir each time.
+# However, gorelease should ignore new go.sum entries for the module it's
+# testing, since the requirement on that module is fake (a simulation: the user
+# isn't actually relying on their own module).
+-- go.sum --
+example.com/cycle/v2 v2.0.0/go.mod h1:lkmoN54Yqku+pnE3i6U+PjV87yiHyv3Rbei+phlzGGU=
+example.com/cycledep/v2 v2.0.0 h1:B8tgq8pxH4IbvvozFpGx7k+HUeLoAPcmCixOXPZiuTE=
+example.com/cycledep/v2 v2.0.0/go.mod h1:wBHRfgrlyovU4csu71ja8ySemxEOKOr8PpAiMU82nLw=
+-- go.mod --
+module example.com/cycle/v2
+
+go 1.12
+
+require example.com/cycledep/v2 v2.0.0
+-- a.go --
+package a
+
+import _ "example.com/cycledep/v2" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt
new file mode 100644
index 0000000..83f7e34
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycledep_v1.0.0.txt
@@ -0,0 +1,10 @@
+-- go.mod --
+module example.com/cycledep
+
+go 1.12
+
+require example.com/cycle v1.5.0
+-- a.go --
+package a
+
+import _ "example.com/cycle"
diff --git a/cmd/gorelease/testdata/mod/example.com_cycledep_v2_v2.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycledep_v2_v2.0.0.txt
new file mode 100644
index 0000000..7496524
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_cycledep_v2_v2.0.0.txt
@@ -0,0 +1,12 @@
+-- go.mod --
+module example.com/cycledep/v2
+
+go 1.12
+
+require example.com/cycle/v2 v2.0.0
+-- b.go --
+package b
+-- c/c.go --
+package c
+
+import _ "example.com/cycle/v2"
diff --git a/cmd/gorelease/testdata/mod/example.com_empty_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_empty_v0.0.1.txt
new file mode 100644
index 0000000..8e7df04
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_empty_v0.0.1.txt
@@ -0,0 +1,4 @@
+-- go.mod --
+module example.com/empty
+
+go 1.14
diff --git a/cmd/gorelease/testdata/mod/example.com_empty_v0.0.2.txt b/cmd/gorelease/testdata/mod/example.com_empty_v0.0.2.txt
new file mode 100644
index 0000000..8e7df04
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_empty_v0.0.2.txt
@@ -0,0 +1,4 @@
+-- go.mod --
+module example.com/empty
+
+go 1.14
diff --git a/cmd/gorelease/testdata/mod/example.com_errors_v0.1.0.txt b/cmd/gorelease/testdata/mod/example.com_errors_v0.1.0.txt
new file mode 100644
index 0000000..3ba57c4
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_errors_v0.1.0.txt
@@ -0,0 +1,26 @@
+Module example.com/errors is used to compare modules with errors across
+two versions.
+
+* Package "fixed" has type errors in v0.1.0, fixed in v0.2.0.
+* Package "deleted" has type errors in v0.1.0, deleted in v0.2.0.
+* Package "broken" is correct in v0.1.0, has type errors in v0.2.0.
+* Package "added" doesn't exist in v0.1.0, has type errors in v0.2.0.
+
+-- go.mod --
+module example.com/errors
+
+go 1.12
+-- fixed/fixed.go --
+package fixed
+
+const X int = Missing
+
+-- deleted/deleted.go --
+package deleted
+
+const X int = Missing
+
+-- broken/broken.go --
+package broken
+
+const X int = 12
diff --git a/cmd/gorelease/testdata/mod/example.com_errors_v0.2.0.txt b/cmd/gorelease/testdata/mod/example.com_errors_v0.2.0.txt
new file mode 100644
index 0000000..668d59b
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_errors_v0.2.0.txt
@@ -0,0 +1,18 @@
+-- go.mod --
+module example.com/errors
+
+go 1.12
+-- fixed/fixed.go --
+package fixed
+
+const X int = 12
+
+-- broken/broken.go --
+package broken
+
+const X int = Missing
+
+-- added/added.go --
+package added
+
+const X int = Missing
diff --git a/cmd/gorelease/testdata/mod/example.com_fix_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_fix_v1.0.0.txt
new file mode 100644
index 0000000..21daf65
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_fix_v1.0.0.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/fix
+
+go 1.13
+-- bad/bad.go --
+package bad
+
+func Broken() NOTYPE { return 0 }
+func Bad() int { return 1 }
+-- good/good.go --
+package good
+
+func Good() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_fix_v1.0.1-patch.txt b/cmd/gorelease/testdata/mod/example.com_fix_v1.0.1-patch.txt
new file mode 100644
index 0000000..56c1c66
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_fix_v1.0.1-patch.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/fix
+
+go 1.13
+-- bad/bad.go --
+package bad
+
+func Broken() int { return 0 }
+func Bad() int { return 1 }
+-- good/good.go --
+package good
+
+func Good() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-other.txt b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-other.txt
new file mode 100644
index 0000000..068ed8f
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-other.txt
@@ -0,0 +1,14 @@
+-- go.mod --
+module example.com/fix
+
+go 1.13
+-- bad/bad.go --
+package bad
+
+func Broken() int { return 0 }
+func Bad() int { return 1 }
+-- good/good.go --
+package good
+
+func Good() int { return 1 }
+func Better() int { return 2 }
diff --git a/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-same.txt b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-same.txt
new file mode 100644
index 0000000..6ff9299
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-compatible-same.txt
@@ -0,0 +1,14 @@
+-- go.mod --
+module example.com/fix
+
+go 1.13
+-- bad/bad.go --
+package bad
+
+func Broken() int { return 0 }
+func Bad() int { return 1 }
+func Worse() int { return -1 }
+-- good/good.go --
+package good
+
+func Good() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-other.txt b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-other.txt
new file mode 100644
index 0000000..a7013b8
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-other.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/fix
+
+go 1.13
+-- bad/bad.go --
+package bad
+
+func Broken() int { return 0 }
+func Bad() int { return 1 }
+-- good/good.go --
+package good
+
+func Good() string { return "1" }
diff --git a/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-same.txt b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-same.txt
new file mode 100644
index 0000000..1fb4891
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_fix_v1.1.0-incompatible-same.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/fix
+
+go 1.13
+-- bad/bad.go --
+package bad
+
+func Broken() int { return 0 }
+func Bad() string { return "1" }
+-- good/good.go --
+package good
+
+func Good() int { return 1 }
diff --git a/cmd/gorelease/testdata/mod/example.com_generics_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_generics_v0.0.1.txt
new file mode 100644
index 0000000..f706512
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_generics_v0.0.1.txt
@@ -0,0 +1,10 @@
+-- go.mod --
+module example.com/generics
+
+go 1.18
+-- a/a.go --
+package a
+
+type Foo[V any] interface {
+ Value() V
+}
diff --git a/cmd/gorelease/testdata/mod/example.com_internalcompat_a_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_internalcompat_a_v1.0.0.txt
new file mode 100644
index 0000000..5f15418
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_internalcompat_a_v1.0.0.txt
@@ -0,0 +1,16 @@
+-- go.mod --
+module example.com/internalcompat/a
+
+go 1.15
+
+-- p/p.go --
+package p
+
+import "example.com/internalcompat/a/q"
+
+var V q.Q
+
+-- q/q.go --
+package q
+
+type Q struct{}
diff --git a/cmd/gorelease/testdata/mod/example.com_internalcompat_b_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_internalcompat_b_v1.0.0.txt
new file mode 100644
index 0000000..318db36
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_internalcompat_b_v1.0.0.txt
@@ -0,0 +1,16 @@
+-- go.mod --
+module example.com/internalcompat/b
+
+go 1.15
+
+-- p/p.go --
+package p
+
+import "example.com/internalcompat/b/q"
+
+var V q.Q
+
+-- q/q.go --
+package q
+
+type Q struct{}
diff --git a/cmd/gorelease/testdata/mod/example.com_issue37756_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_issue37756_v1.0.0.txt
new file mode 100644
index 0000000..b576df8
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_issue37756_v1.0.0.txt
@@ -0,0 +1,18 @@
+-- go.mod --
+module example.com/issue37756
+
+go 1.14
+-- a/a.go --
+package a
+
+import _ "example.com/issue37756/b"
+
+func A1() {}
+-- b/b.go --
+package b
+
+func B1() {}
+-- c/c.go --
+package c
+
+func C1() {}
diff --git a/cmd/gorelease/testdata/mod/example.com_issue37756_v1.1.0.txt b/cmd/gorelease/testdata/mod/example.com_issue37756_v1.1.0.txt
new file mode 100644
index 0000000..e35bc51
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_issue37756_v1.1.0.txt
@@ -0,0 +1,21 @@
+-- go.mod --
+module example.com/issue37756
+
+go 1.14
+-- a/a.go --
+package a
+
+import _ "example.com/issue37756/c"
+
+func A1() {}
+func A2() {}
+-- b/b.go --
+package b
+
+func B1() {}
+func B2() {}
+-- c/c.go --
+package c
+
+func C1() {}
+func C2() {}
diff --git a/cmd/gorelease/testdata/mod/example.com_main_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_main_v0.0.1.txt
new file mode 100644
index 0000000..a76b5b2
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_main_v0.0.1.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/main
+
+go 1.14
+-- a/a.go --
+package main
+
+func main() {}
diff --git a/cmd/gorelease/testdata/mod/example.com_nomod_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_nomod_v0.0.1.txt
new file mode 100644
index 0000000..760a8e8
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_nomod_v0.0.1.txt
@@ -0,0 +1,6 @@
+-- .mod --
+module example.com/nomod
+-- p/p.go --
+package p // import "example.com/something/different"
+
+// The import comment above is ignored by gorelease and by modules.
diff --git a/cmd/gorelease/testdata/mod/example.com_nomod_v0.0.2.txt b/cmd/gorelease/testdata/mod/example.com_nomod_v0.0.2.txt
new file mode 100644
index 0000000..06e2024
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_nomod_v0.0.2.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/nomod
+
+go 1.12
+-- p/p.go --
+package p
diff --git a/cmd/gorelease/testdata/mod/example.com_prerelease_v0.0.0-20300101000000-000000000000.txt b/cmd/gorelease/testdata/mod/example.com_prerelease_v0.0.0-20300101000000-000000000000.txt
new file mode 100644
index 0000000..e165690
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_prerelease_v0.0.0-20300101000000-000000000000.txt
@@ -0,0 +1,4 @@
+-- go.mod --
+module example.com/prerelease
+
+go 1.12
diff --git a/cmd/gorelease/testdata/mod/example.com_private_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_private_v1.0.0.txt
new file mode 100644
index 0000000..ca71c99
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_private_v1.0.0.txt
@@ -0,0 +1,17 @@
+-- go.mod --
+module example.com/private
+
+go 1.12
+-- p/p.go --
+package p
+
+import "example.com/private/internal/i"
+
+type P i.I
+
+-- internal/i/i.go --
+package i
+
+type I interface{}
+
+func Delete() {}
diff --git a/cmd/gorelease/testdata/mod/example.com_private_v1.0.1.txt b/cmd/gorelease/testdata/mod/example.com_private_v1.0.1.txt
new file mode 100644
index 0000000..1fe445c
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_private_v1.0.1.txt
@@ -0,0 +1,15 @@
+-- go.mod --
+module example.com/private
+
+go 1.12
+-- p/p.go --
+package p
+
+import "example.com/private/internal/i"
+
+type P i.I
+
+-- internal/i/i.go --
+package i
+
+type I interface{}
diff --git a/cmd/gorelease/testdata/mod/example.com_private_v1.0.2-break.txt b/cmd/gorelease/testdata/mod/example.com_private_v1.0.2-break.txt
new file mode 100644
index 0000000..84e3616
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_private_v1.0.2-break.txt
@@ -0,0 +1,17 @@
+-- go.mod --
+module example.com/private
+
+go 1.12
+-- p/p.go --
+package p
+
+import "example.com/private/internal/i"
+
+type P i.I
+
+-- internal/i/i.go --
+package i
+
+type I interface{
+ Foo()
+}
diff --git a/cmd/gorelease/testdata/mod/example.com_require_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_require_v0.0.1.txt
new file mode 100644
index 0000000..bc7200f
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_require_v0.0.1.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/require
+
+go 1.12
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/mod/example.com_require_v0.1.0.txt b/cmd/gorelease/testdata/mod/example.com_require_v0.1.0.txt
new file mode 100644
index 0000000..8f858bf
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_require_v0.1.0.txt
@@ -0,0 +1,14 @@
+-- go.mod --
+module example.com/require
+
+go 1.12
+
+require example.com/basic v1.0.1
+-- go.sum --
+example.com/basic v1.0.1/go.mod h1:pv9xTX7lhV6R1XNYo1EcI/DQqKxDyhNTN+K1DjHW2Oo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- require.go --
+package require
+
diff --git a/cmd/gorelease/testdata/mod/example.com_require_v0.1.1.txt b/cmd/gorelease/testdata/mod/example.com_require_v0.1.1.txt
new file mode 100644
index 0000000..620bf66
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_require_v0.1.1.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/require
+
+go 1.12
+
+require example.com/basic v1.1.0
+-- go.sum --
+example.com/basic v1.1.0/go.mod h1:pv9xTX7lhV6R1XNYo1EcI/DQqKxDyhNTN+K1DjHW2Oo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt
new file mode 100644
index 0000000..b1466d0
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt
@@ -0,0 +1,13 @@
+-- go.mod --
+module example.com/retract
+
+go 1.12
+
+require example.com/retractdep v1.0.0
+-- go.sum --
+example.com/retractdep v1.0.0 h1:SOVn6jA2ygQY+v8/5aAwxVUJ9teuLrdH/UmbUtp2C44=
+example.com/retractdep v1.0.0/go.mod h1:UjjWSH/ulfbAGgQQwm7pAZ988MFRngUSkJnzcuPsYDI=
+-- a.go --
+package a
+
+import _ "example.com/retractdep"
diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt
new file mode 100644
index 0000000..36aa3d9
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/retractdep
+
+go 1.12
+-- a.go --
+package a
+
+const A = "a" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt
new file mode 100644
index 0000000..7548ed2
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt
@@ -0,0 +1,11 @@
+-- go.mod --
+module example.com/retractdep
+
+go 1.12
+
+// Remote-triggered crash in package foo. See CVE-2021-01234.
+retract v1.0.0
+-- a.go --
+package a
+
+const A = "a" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt
new file mode 100644
index 0000000..77619e3
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt
@@ -0,0 +1,12 @@
+# Identical to v1.0.0: just need a new version so that we can test different
+# error messages based on the vX.0.1 retraction comments. We can't test them in
+# the same major version because go mod will always use the latest version's
+# error message.
+-- go.mod --
+module example.com/retractdep/v2
+
+go 1.12
+-- a.go --
+package a
+
+const A = "a" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt
new file mode 100644
index 0000000..5682baa
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt
@@ -0,0 +1,10 @@
+-- go.mod --
+module example.com/retractdep/v2
+
+go 1.12
+
+retract v2.0.0
+-- a.go --
+package a
+
+const A = "a" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt
new file mode 100644
index 0000000..1d526a9
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt
@@ -0,0 +1,12 @@
+# Identical to v1.0.0: just need a new version so that we can test different
+# error messages based on the vX.0.1 retraction comments. We can't test them in
+# the same major version because go mod will always use the latest version's
+# error message.
+-- go.mod --
+module example.com/retractdep/v3
+
+go 1.12
+-- a.go --
+package a
+
+const A = "a" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt
new file mode 100644
index 0000000..ed39efb
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt
@@ -0,0 +1,11 @@
+-- go.mod --
+module example.com/retractdep/v3
+
+go 1.12
+
+// This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message.
+retract v3.0.0
+-- a.go --
+package a
+
+const A = "a" \ No newline at end of file
diff --git a/cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt
new file mode 100644
index 0000000..885906f
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt
@@ -0,0 +1,15 @@
+-- go.mod --
+module example.com/retracttransitive
+
+go 1.12
+
+require example.com/retract v0.0.1
+-- go.sum --
+example.com/retract v0.0.1 h1:Afj8efoHilltHZNLlEARzpc1Vkc5d6ugWKIE/YDmXuQ=
+example.com/retract v0.0.1/go.mod h1:DUqXjcGF3aJhkjxsUjQ0DG65b51DDBvFrEbcr9kkyto=
+example.com/retractdep v1.0.0 h1:SOVn6jA2ygQY+v8/5aAwxVUJ9teuLrdH/UmbUtp2C44=
+example.com/retractdep v1.0.0/go.mod h1:UjjWSH/ulfbAGgQQwm7pAZ988MFRngUSkJnzcuPsYDI=
+-- a.go --
+package a
+
+import _ "example.com/retract"
diff --git a/cmd/gorelease/testdata/mod/example.com_sub_nest_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_sub_nest_v1.0.0.txt
new file mode 100644
index 0000000..36f5b33
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_sub_nest_v1.0.0.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/sub/nest
+
+go 1.12
+-- nest.go --
+package nest
diff --git a/cmd/gorelease/testdata/mod/example.com_sub_nest_v2_v2.0.0.txt b/cmd/gorelease/testdata/mod/example.com_sub_nest_v2_v2.0.0.txt
new file mode 100644
index 0000000..b7dc8a7
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_sub_nest_v2_v2.0.0.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/sub/nest/v2
+
+go 1.12
+-- nest.go --
+package nest
diff --git a/cmd/gorelease/testdata/mod/example.com_sub_v2_v2.0.0.txt b/cmd/gorelease/testdata/mod/example.com_sub_v2_v2.0.0.txt
new file mode 100644
index 0000000..488f2dc
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_sub_v2_v2.0.0.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/sub/v2
+
+go 1.12
+-- sub.go --
+package sub
diff --git a/cmd/gorelease/testdata/mod/example.com_tidy_a_v0.1.0.txt b/cmd/gorelease/testdata/mod/example.com_tidy_a_v0.1.0.txt
new file mode 100644
index 0000000..d9c71aa
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_tidy_a_v0.1.0.txt
@@ -0,0 +1,10 @@
+-- go.mod --
+module example.com/tidy/a
+
+go 1.12
+
+require example.com/tidy/b v0.2.0
+-- p.go --
+package a
+
+import _ "example.com/tidy/b"
diff --git a/cmd/gorelease/testdata/mod/example.com_tidy_b_v0.1.0.txt b/cmd/gorelease/testdata/mod/example.com_tidy_b_v0.1.0.txt
new file mode 100644
index 0000000..d169972
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_tidy_b_v0.1.0.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/tidy/b
+
+go 1.12
+-- p.go --
+package b
diff --git a/cmd/gorelease/testdata/mod/example.com_tidy_b_v0.2.0.txt b/cmd/gorelease/testdata/mod/example.com_tidy_b_v0.2.0.txt
new file mode 100644
index 0000000..470b743
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_tidy_b_v0.2.0.txt
@@ -0,0 +1,8 @@
+-- go.mod --
+module example.com/tidy/b
+
+go 1.12
+-- p.go --
+package b
+
+func B() {}
diff --git a/cmd/gorelease/testdata/mod/example.com_tidy_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_tidy_v0.0.1.txt
new file mode 100644
index 0000000..f6e7fff
--- /dev/null
+++ b/cmd/gorelease/testdata/mod/example.com_tidy_v0.0.1.txt
@@ -0,0 +1,6 @@
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+-- tidy.go --
+package tidy
diff --git a/cmd/gorelease/testdata/nomod/README.txt b/cmd/gorelease/testdata/nomod/README.txt
new file mode 100644
index 0000000..ed50bb0
--- /dev/null
+++ b/cmd/gorelease/testdata/nomod/README.txt
@@ -0,0 +1,2 @@
+Module example.com/nomod is used to test situations where no go.mod file
+is present.
diff --git a/cmd/gorelease/testdata/nomod/nomod.test b/cmd/gorelease/testdata/nomod/nomod.test
new file mode 100644
index 0000000..c307d13
--- /dev/null
+++ b/cmd/gorelease/testdata/nomod/nomod.test
@@ -0,0 +1,8 @@
+mod=example.com/nomod
+version=v0.0.2
+base=v0.0.1
+release=v0.0.2
+proxyVersions=example.com/nomod@v0.0.1
+-- want --
+# summary
+v0.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/patherrors/README.txt b/cmd/gorelease/testdata/patherrors/README.txt
new file mode 100644
index 0000000..82f95f1
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/README.txt
@@ -0,0 +1 @@
+Module example.com/patherrors tests errors related to paths. \ No newline at end of file
diff --git a/cmd/gorelease/testdata/patherrors/abspath.test b/cmd/gorelease/testdata/patherrors/abspath.test
new file mode 100644
index 0000000..317364e
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/abspath.test
@@ -0,0 +1,8 @@
+mod=example.com/patherrors
+base=none
+error=true
+-- want --
+module path "/home/gopher/go/src/mymod" must not be an absolute path.
+It must be an address where your module may be found.
+-- go.mod --
+module /home/gopher/go/src/mymod
diff --git a/cmd/gorelease/testdata/patherrors/badmajor.test b/cmd/gorelease/testdata/patherrors/badmajor.test
new file mode 100644
index 0000000..4f5d027
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/badmajor.test
@@ -0,0 +1,10 @@
+mod=example.com/patherrors
+base=none
+error=true
+-- want --
+module path "example.com/patherrors/v0" has major version suffix "v0".
+A major version suffix is only allowed for v2 or later.
+-- go.mod --
+module example.com/patherrors/v0
+
+go 1.12
diff --git a/cmd/gorelease/testdata/patherrors/dup_roots_branch.test b/cmd/gorelease/testdata/patherrors/dup_roots_branch.test
new file mode 100644
index 0000000..66f79bb
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/dup_roots_branch.test
@@ -0,0 +1,20 @@
+dir=dup
+base=none
+success=false
+vcs=git
+-- dup/go.mod --
+module example.com/dup/v2
+
+go 1.13
+-- dup/v2/go.mod --
+module example.com/dup/v2
+
+go 1.13
+-- want --
+# diagnostics
+module is defined in two locations:
+ dup/go.mod
+ dup/v2/go.mod
+
+# summary
+Suggested version: v2.0.0 (with tag dup/v2.0.0)
diff --git a/cmd/gorelease/testdata/patherrors/dup_roots_dir.test b/cmd/gorelease/testdata/patherrors/dup_roots_dir.test
new file mode 100644
index 0000000..3898da4
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/dup_roots_dir.test
@@ -0,0 +1,20 @@
+dir=dup/v2
+base=none
+success=false
+vcs=git
+-- dup/go.mod --
+module example.com/dup/v2
+
+go 1.13
+-- dup/v2/go.mod --
+module example.com/dup/v2
+
+go 1.13
+-- want --
+# diagnostics
+module is defined in two locations:
+ dup/v2/go.mod
+ dup/go.mod
+
+# summary
+Suggested version: v2.0.0 (with tag dup/v2.0.0)
diff --git a/cmd/gorelease/testdata/patherrors/dup_roots_ok.test b/cmd/gorelease/testdata/patherrors/dup_roots_ok.test
new file mode 100644
index 0000000..afd157c
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/dup_roots_ok.test
@@ -0,0 +1,14 @@
+dir=dup/v2
+base=none
+vcs=git
+-- dup/go.mod --
+module example.com/dup
+
+go 1.13
+-- dup/v2/go.mod --
+module example.com/dup/v2
+
+go 1.13
+-- want --
+# summary
+Suggested version: v2.0.0 (with tag dup/v2.0.0)
diff --git a/cmd/gorelease/testdata/patherrors/gopkginsub.test b/cmd/gorelease/testdata/patherrors/gopkginsub.test
new file mode 100644
index 0000000..2c2fb7c
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/gopkginsub.test
@@ -0,0 +1,16 @@
+mod=example.com/patherrors
+base=none
+dir=yaml
+success=false
+vcs=git
+-- want --
+# diagnostics
+go.mod: go directive is missing
+gopkg.in/yaml.v2: module path starts with gopkg.in and must be declared in the root directory of the repository
+
+# summary
+Suggested version: v2.0.0
+-- .mod --
+module example.com/patherrors
+-- yaml/go.mod --
+module gopkg.in/yaml.v2
diff --git a/cmd/gorelease/testdata/patherrors/pathsub.test b/cmd/gorelease/testdata/patherrors/pathsub.test
new file mode 100644
index 0000000..000c02a
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/pathsub.test
@@ -0,0 +1,17 @@
+mod=example.com/patherrors
+dir=x
+base=none
+success=false
+vcs=git
+-- want --
+# diagnostics
+example.com/y: module path must end with "x", since it is in subdirectory "x"
+
+# summary
+Suggested version: v0.1.0
+-- .mod --
+module example.com/patherrors
+-- x/go.mod --
+module example.com/y
+
+go 1.12
diff --git a/cmd/gorelease/testdata/patherrors/pathsubv2.test b/cmd/gorelease/testdata/patherrors/pathsubv2.test
new file mode 100644
index 0000000..8ab2c80
--- /dev/null
+++ b/cmd/gorelease/testdata/patherrors/pathsubv2.test
@@ -0,0 +1,17 @@
+mod=example.com/patherrors
+base=none
+dir=x
+success=false
+vcs=git
+-- want --
+# diagnostics
+example.com/y/v2: module path must end with "x" or "x/v2", since it is in subdirectory "x"
+
+# summary
+Suggested version: v2.0.0
+-- .mod --
+module example.com/patherrors
+-- x/go.mod --
+module example.com/y/v2
+
+go 1.12
diff --git a/cmd/gorelease/testdata/prerelease/README.txt b/cmd/gorelease/testdata/prerelease/README.txt
new file mode 100644
index 0000000..64a4c96
--- /dev/null
+++ b/cmd/gorelease/testdata/prerelease/README.txt
@@ -0,0 +1 @@
+This directory contains tests for pre-release versions. \ No newline at end of file
diff --git a/cmd/gorelease/testdata/prerelease/prerelease_2.test b/cmd/gorelease/testdata/prerelease/prerelease_2.test
new file mode 100644
index 0000000..0597098
--- /dev/null
+++ b/cmd/gorelease/testdata/prerelease/prerelease_2.test
@@ -0,0 +1,16 @@
+mod=example.com/prerelease
+release=v0.0.0-2
+success=false
+-- want --
+# diagnostics
+Version v0.0.0-2 is lower than most pseudo-versions. Consider releasing v0.1.0-0 instead.
+
+# summary
+Inferred base version: none
+v0.0.0-2 is a valid semantic version for this release.
+Note: v0.0.0-2 sorts lower in MVS than pseudo-versions, which may be
+unexpected for users. So, it may be better to choose a different suffix.
+-- go.mod --
+module example.com/prerelease
+
+go 1.12
diff --git a/cmd/gorelease/testdata/prerelease/prerelease_3.test b/cmd/gorelease/testdata/prerelease/prerelease_3.test
new file mode 100644
index 0000000..7f6b26f
--- /dev/null
+++ b/cmd/gorelease/testdata/prerelease/prerelease_3.test
@@ -0,0 +1,16 @@
+mod=example.com/prerelease
+release=v0.0.0-3
+success=false
+-- want --
+# diagnostics
+Version v0.0.0-3 is lower than most pseudo-versions. Consider releasing v0.1.0-0 instead.
+
+# summary
+Inferred base version: none
+v0.0.0-3 is a valid semantic version for this release.
+Note: v0.0.0-3 sorts lower in MVS than pseudo-versions, which may be
+unexpected for users. So, it may be better to choose a different suffix.
+-- go.mod --
+module example.com/prerelease
+
+go 1.12
diff --git a/cmd/gorelease/testdata/prerelease/prerelease_beta2.test b/cmd/gorelease/testdata/prerelease/prerelease_beta2.test
new file mode 100644
index 0000000..b265a86
--- /dev/null
+++ b/cmd/gorelease/testdata/prerelease/prerelease_beta2.test
@@ -0,0 +1,10 @@
+mod=example.com/prerelease
+release=v0.0.0-beta2
+-- want --
+# summary
+Inferred base version: none
+v0.0.0-beta2 is a valid semantic version for this release.
+-- go.mod --
+module example.com/prerelease
+
+go 1.12
diff --git a/cmd/gorelease/testdata/prerelease/prerelease_build_metadata.test b/cmd/gorelease/testdata/prerelease/prerelease_build_metadata.test
new file mode 100644
index 0000000..4d3be83
--- /dev/null
+++ b/cmd/gorelease/testdata/prerelease/prerelease_build_metadata.test
@@ -0,0 +1,11 @@
+mod=example.com/prerelease
+release=v1.0.0-alpha+001
+error=true
+-- want --
+usage: gorelease [-base=version] [-version=version]
+release version "v1.0.0-alpha+001" is not a canonical semantic version: build metadata is not supported
+For more information, run go doc golang.org/x/exp/cmd/gorelease
+-- go.mod --
+module example.com/prerelease
+
+go 1.12 \ No newline at end of file
diff --git a/cmd/gorelease/testdata/prerelease/prerelease_wordsonly.test b/cmd/gorelease/testdata/prerelease/prerelease_wordsonly.test
new file mode 100644
index 0000000..a59aa40
--- /dev/null
+++ b/cmd/gorelease/testdata/prerelease/prerelease_wordsonly.test
@@ -0,0 +1,10 @@
+mod=example.com/prerelease
+release=v0.0.0-some-words-only-suffix
+-- want --
+# summary
+Inferred base version: none
+v0.0.0-some-words-only-suffix is a valid semantic version for this release.
+-- go.mod --
+module example.com/prerelease
+
+go 1.12
diff --git a/cmd/gorelease/testdata/private/README.txt b/cmd/gorelease/testdata/private/README.txt
new file mode 100644
index 0000000..226b06d
--- /dev/null
+++ b/cmd/gorelease/testdata/private/README.txt
@@ -0,0 +1,3 @@
+Module example.com/private is used to test that changes to
+internal packages are not reported unless they affect the exported
+API of non-internal packages.
diff --git a/cmd/gorelease/testdata/private/break.test b/cmd/gorelease/testdata/private/break.test
new file mode 100644
index 0000000..e996a9b
--- /dev/null
+++ b/cmd/gorelease/testdata/private/break.test
@@ -0,0 +1,12 @@
+mod=example.com/private
+version=v1.0.2-break
+base=v1.0.1
+success=false
+-- want --
+# example.com/private/p
+## incompatible changes
+example.com/private/internal/i.I.Foo: added
+
+# summary
+Cannot suggest a release version.
+Incompatible changes were detected.
diff --git a/cmd/gorelease/testdata/private/unreported.test b/cmd/gorelease/testdata/private/unreported.test
new file mode 100644
index 0000000..20567b6
--- /dev/null
+++ b/cmd/gorelease/testdata/private/unreported.test
@@ -0,0 +1,7 @@
+mod=example.com/private
+version=v1.0.1
+base=v1.0.0
+proxyVersions=example.com/private@v1.0.0
+-- want --
+# summary
+Suggested version: v1.0.1
diff --git a/cmd/gorelease/testdata/regress/README.txt b/cmd/gorelease/testdata/regress/README.txt
new file mode 100644
index 0000000..ee55a78
--- /dev/null
+++ b/cmd/gorelease/testdata/regress/README.txt
@@ -0,0 +1 @@
+This directory contains unrelated tests, used to verify issues are fixed. \ No newline at end of file
diff --git a/cmd/gorelease/testdata/regress/issue37756.test b/cmd/gorelease/testdata/regress/issue37756.test
new file mode 100644
index 0000000..07b6017
--- /dev/null
+++ b/cmd/gorelease/testdata/regress/issue37756.test
@@ -0,0 +1,21 @@
+# Verifies golang.org/issue/37756.
+# Packages should be compared in lexical order by package path.
+mod=example.com/issue37756
+version=v1.1.0
+base=v1.0.0
+proxyVersions=example.com/issue37756@v1.0.0
+-- want --
+# example.com/issue37756/a
+## compatible changes
+A2: added
+
+# example.com/issue37756/b
+## compatible changes
+B2: added
+
+# example.com/issue37756/c
+## compatible changes
+C2: added
+
+# summary
+Suggested version: v1.1.0
diff --git a/cmd/gorelease/testdata/require/README.txt b/cmd/gorelease/testdata/require/README.txt
new file mode 100644
index 0000000..f905232
--- /dev/null
+++ b/cmd/gorelease/testdata/require/README.txt
@@ -0,0 +1,2 @@
+This directory contain tests that assert gorelease behavior when module
+requirements (and require statements in the go.mod) have changed. \ No newline at end of file
diff --git a/cmd/gorelease/testdata/require/add_requirement.test b/cmd/gorelease/testdata/require/add_requirement.test
new file mode 100644
index 0000000..59ef3db
--- /dev/null
+++ b/cmd/gorelease/testdata/require/add_requirement.test
@@ -0,0 +1,19 @@
+mod=example.com/require
+base=v0.0.1
+proxyVersions=example.com/require@v0.0.1,example.com/basic@v1.0.1
+-- want --
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/require
+
+go 1.12
+
+require example.com/basic v1.0.1
+-- go.sum --
+example.com/basic v1.0.1/go.mod h1:pv9xTX7lhV6R1XNYo1EcI/DQqKxDyhNTN+K1DjHW2Oo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/require/decrement_go_version.test b/cmd/gorelease/testdata/require/decrement_go_version.test
new file mode 100644
index 0000000..ce461d5
--- /dev/null
+++ b/cmd/gorelease/testdata/require/decrement_go_version.test
@@ -0,0 +1,13 @@
+mod=example.com/require
+base=v0.0.1
+proxyVersions=example.com/require@v0.0.1
+-- want --
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/require
+
+go 1.11
+-- go.sum --
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/require/decrement_requirement_minor.test b/cmd/gorelease/testdata/require/decrement_requirement_minor.test
new file mode 100644
index 0000000..f64e3e3
--- /dev/null
+++ b/cmd/gorelease/testdata/require/decrement_requirement_minor.test
@@ -0,0 +1,18 @@
+mod=example.com/require
+base=v0.1.1
+-- want --
+# summary
+Suggested version: v0.1.2
+-- go.mod --
+module example.com/require
+
+go 1.12
+
+require example.com/basic v0.0.1
+-- go.sum --
+example.com/basic v0.0.1/go.mod h1:pv9xTX7lhV6R1XNYo1EcI/DQqKxDyhNTN+K1DjHW2Oo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/require/increment_go_version.test b/cmd/gorelease/testdata/require/increment_go_version.test
new file mode 100644
index 0000000..802ac6e
--- /dev/null
+++ b/cmd/gorelease/testdata/require/increment_go_version.test
@@ -0,0 +1,13 @@
+mod=example.com/require
+base=v0.0.1
+proxyVersions=example.com/require@v0.0.1
+-- want --
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/require
+
+go 1.13
+-- go.sum --
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/require/increment_requirement_minor.test b/cmd/gorelease/testdata/require/increment_requirement_minor.test
new file mode 100644
index 0000000..821ff2a
--- /dev/null
+++ b/cmd/gorelease/testdata/require/increment_requirement_minor.test
@@ -0,0 +1,19 @@
+mod=example.com/require
+base=v0.1.0
+proxyVersions=example.com/require@v0.1.0,example.com/basic@v1.1.0,example.com/basic@v1.0.1
+-- want --
+# summary
+Suggested version: v0.2.0
+-- go.mod --
+module example.com/require
+
+go 1.12
+
+require example.com/basic v1.1.0
+-- go.sum --
+example.com/basic v1.1.0/go.mod h1:pv9xTX7lhV6R1XNYo1EcI/DQqKxDyhNTN+K1DjHW2Oo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/require/increment_requirement_patch.test b/cmd/gorelease/testdata/require/increment_requirement_patch.test
new file mode 100644
index 0000000..d183300
--- /dev/null
+++ b/cmd/gorelease/testdata/require/increment_requirement_patch.test
@@ -0,0 +1,19 @@
+mod=example.com/require
+base=v0.1.1
+proxyVersions=example.com/require@v0.1.1,example.com/basic@v1.1.1,example.com/basic@v1.1.0
+-- want --
+# summary
+Suggested version: v0.1.2
+-- go.mod --
+module example.com/require
+
+go 1.12
+
+require example.com/basic v1.1.1
+-- go.sum --
+example.com/basic v1.1.1/go.mod h1:pv9xTX7lhV6R1XNYo1EcI/DQqKxDyhNTN+K1DjHW2Oo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/require/remove_requirements.test b/cmd/gorelease/testdata/require/remove_requirements.test
new file mode 100644
index 0000000..91a8837
--- /dev/null
+++ b/cmd/gorelease/testdata/require/remove_requirements.test
@@ -0,0 +1,13 @@
+mod=example.com/require
+base=v0.0.1
+proxyVersions=example.com/require@v0.0.1
+-- want --
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/require
+
+go 1.12
+-- go.sum --
+-- require.go --
+package require
diff --git a/cmd/gorelease/testdata/retract/retract_verify_direct_dep.test b/cmd/gorelease/testdata/retract/retract_verify_direct_dep.test
new file mode 100644
index 0000000..7777dff
--- /dev/null
+++ b/cmd/gorelease/testdata/retract/retract_verify_direct_dep.test
@@ -0,0 +1,10 @@
+mod=example.com/retract
+version=v0.0.1
+success=false
+-- want --
+# diagnostics
+required module example.com/retractdep@v1.0.0 retracted by module author: Remote-triggered crash in package foo. See CVE-2021-01234.
+
+# summary
+Inferred base version: v0.0.1
+Suggested version: v0.0.2
diff --git a/cmd/gorelease/testdata/retract/retract_verify_long_msg.test b/cmd/gorelease/testdata/retract/retract_verify_long_msg.test
new file mode 100644
index 0000000..1f2776f
--- /dev/null
+++ b/cmd/gorelease/testdata/retract/retract_verify_long_msg.test
@@ -0,0 +1,22 @@
+mod=example.com/retract
+success=false
+-- want --
+# diagnostics
+required module example.com/retractdep/v3@v3.0.0 retracted by module author
+
+# summary
+Inferred base version: v0.0.1
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/retract
+
+go 1.12
+
+require example.com/retractdep/v3 v3.0.0
+-- go.sum --
+example.com/retractdep/v3 v3.0.0 h1:LEaqsEpt7J4Er+qSPqL7bENpIkRdZdaOE6KaUaiNB5I=
+example.com/retractdep/v3 v3.0.0/go.mod h1:B2rEwAWayv3FJ2jyeiq9O3UBbxSvdDqZUtxmKsLyg6k=
+-- a.go --
+package a
+
+import _ "example.com/retractdep/v3"
diff --git a/cmd/gorelease/testdata/retract/retract_verify_no_msg.test b/cmd/gorelease/testdata/retract/retract_verify_no_msg.test
new file mode 100644
index 0000000..7a748f7
--- /dev/null
+++ b/cmd/gorelease/testdata/retract/retract_verify_no_msg.test
@@ -0,0 +1,22 @@
+mod=example.com/retract
+success=false
+-- want --
+# diagnostics
+required module example.com/retractdep/v2@v2.0.0 retracted by module author
+
+# summary
+Inferred base version: v0.0.1
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/retract
+
+go 1.12
+
+require example.com/retractdep/v2 v2.0.0
+-- go.sum --
+example.com/retractdep/v2 v2.0.0 h1:ehV4yfX3A3jNlRnBmHPxq1TyVs1EhmCYI5miEva6Gv8=
+example.com/retractdep/v2 v2.0.0/go.mod h1:rV+p/Yqwnupg15GPVGFRq+un/MYczBZcF1IZ8ubecag=
+-- a.go --
+package a
+
+import _ "example.com/retractdep/v2"
diff --git a/cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test b/cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test
new file mode 100644
index 0000000..681702a
--- /dev/null
+++ b/cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test
@@ -0,0 +1,12 @@
+# When a retracted version is transitively depended upon, it should still
+# result in a retraction error.
+mod=example.com/retracttransitive
+version=v0.0.1
+success=false
+-- want --
+# diagnostics
+required module example.com/retractdep@v1.0.0 retracted by module author: Remote-triggered crash in package foo. See CVE-2021-01234.
+
+# summary
+Inferred base version: v0.0.1
+Suggested version: v0.0.2
diff --git a/cmd/gorelease/testdata/sub/README.txt b/cmd/gorelease/testdata/sub/README.txt
new file mode 100644
index 0000000..54ff480
--- /dev/null
+++ b/cmd/gorelease/testdata/sub/README.txt
@@ -0,0 +1,10 @@
+This directory contains tests for modules that aren't at the root
+of the repository, which is marked with a .git directory.
+We're comparing against an earlier published version with a
+trivial package. Nothing has changed except the location of the
+module within the repository.
+
+ example.com/sub - corresponds to the root directory. Not a module.
+ example.com/sub/v2 - may be in v2 subdirectory.
+ example.com/sub/nest - nested module in subdirectory
+ example.com/sub/nest/v2 - may be in nest or nest/v2.
diff --git a/cmd/gorelease/testdata/sub/nest.test b/cmd/gorelease/testdata/sub/nest.test
new file mode 100644
index 0000000..c2a6436
--- /dev/null
+++ b/cmd/gorelease/testdata/sub/nest.test
@@ -0,0 +1,13 @@
+mod=example.com/sub/nest
+dir=nest
+base=v1.0.0
+vcs=git
+-- want --
+# summary
+Suggested version: v1.0.1 (with tag nest/v1.0.1)
+-- nest/go.mod --
+module example.com/sub/nest
+
+go 1.12
+-- nest/nest.go --
+package nest
diff --git a/cmd/gorelease/testdata/sub/nest_v2.test b/cmd/gorelease/testdata/sub/nest_v2.test
new file mode 100644
index 0000000..6d8bf0c
--- /dev/null
+++ b/cmd/gorelease/testdata/sub/nest_v2.test
@@ -0,0 +1,13 @@
+mod=example.com/sub/nest/v2
+dir=nest
+base=v2.0.0
+vcs=git
+-- want --
+# summary
+Suggested version: v2.0.1 (with tag nest/v2.0.1)
+-- nest/go.mod --
+module example.com/sub/nest/v2
+
+go 1.12
+-- nest/nest.go --
+package nest
diff --git a/cmd/gorelease/testdata/sub/nest_v2_dir.test b/cmd/gorelease/testdata/sub/nest_v2_dir.test
new file mode 100644
index 0000000..7e99c1e
--- /dev/null
+++ b/cmd/gorelease/testdata/sub/nest_v2_dir.test
@@ -0,0 +1,13 @@
+mod=example.com/sub/nest/v2
+dir=nest/v2
+base=v2.0.0
+vcs=git
+-- want --
+# summary
+Suggested version: v2.0.1 (with tag nest/v2.0.1)
+-- nest/v2/go.mod --
+module example.com/sub/nest/v2
+
+go 1.12
+-- nest/v2/nest.go --
+package nest
diff --git a/cmd/gorelease/testdata/sub/v2_dir.test b/cmd/gorelease/testdata/sub/v2_dir.test
new file mode 100644
index 0000000..ed07eb9
--- /dev/null
+++ b/cmd/gorelease/testdata/sub/v2_dir.test
@@ -0,0 +1,13 @@
+mod=example.com/sub/v2
+dir=v2
+base=v2.0.0
+vcs=git
+-- want --
+# summary
+Suggested version: v2.0.1
+-- v2/go.mod --
+module example.com/sub/v2
+
+go 1.12
+-- v2/sub.go --
+package sub
diff --git a/cmd/gorelease/testdata/sub/v2_root.test b/cmd/gorelease/testdata/sub/v2_root.test
new file mode 100644
index 0000000..1a34c67
--- /dev/null
+++ b/cmd/gorelease/testdata/sub/v2_root.test
@@ -0,0 +1,12 @@
+mod=example.com/sub/v2
+base=v2.0.0
+vcs=git
+-- want --
+# summary
+Suggested version: v2.0.1
+-- go.mod --
+module example.com/sub/v2
+
+go 1.12
+-- sub.go --
+package sub
diff --git a/cmd/gorelease/testdata/tidy/README.txt b/cmd/gorelease/testdata/tidy/README.txt
new file mode 100644
index 0000000..0ca2fb4
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/README.txt
@@ -0,0 +1,5 @@
+Module example.com/tidy tests versions that do not have tidy
+go.mod or go.sum files.
+
+v0.0.1 has a trivial package with no imports. It has no requirements
+and no go.sum, so it is tidy. Tests make changes on top of this.
diff --git a/cmd/gorelease/testdata/tidy/empty_sum.test b/cmd/gorelease/testdata/tidy/empty_sum.test
new file mode 100644
index 0000000..69a51fb
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/empty_sum.test
@@ -0,0 +1,20 @@
+mod=example.com/tidy
+base=v0.0.1
+success=false
+-- want --
+# diagnostics
+go.sum: one or more sums are missing. Run 'go mod tidy' to add missing sums.
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+
+require example.com/basic v1.0.1
+-- go.sum --
+-- tidy.go --
+package tidy
+
+import _ "example.com/basic/a"
diff --git a/cmd/gorelease/testdata/tidy/misleading_req.test b/cmd/gorelease/testdata/tidy/misleading_req.test
new file mode 100644
index 0000000..b4b2954
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/misleading_req.test
@@ -0,0 +1,31 @@
+mod=example.com/tidy
+base=none
+success=false
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+
+require (
+ example.com/tidy/a v0.1.0 // actually transitively requires v0.2.0
+ example.com/tidy/b v0.1.0
+)
+-- go.sum --
+example.com/tidy/a v0.1.0 h1:hxFAdyLfJ6TV25ffYI2oA+g3ffLp+XJgo6lrVkT8ufU=
+example.com/tidy/a v0.1.0/go.mod h1:/KTGkbP1cnyJLO5kGL/QSCswh5I8R66epCmEAxgAK+I=
+example.com/tidy/b v0.1.0/go.mod h1:92saqyRYqaI4eqrr6LGMnPfBDXc2yofWznwSxsvqfEw=
+example.com/tidy/b v0.2.0 h1:dSh97fZcMRg87GDb1Gqwy8/mebsrmE4kX3S7d+KeSZU=
+example.com/tidy/b v0.2.0/go.mod h1:92saqyRYqaI4eqrr6LGMnPfBDXc2yofWznwSxsvqfEw=
+-- tidy.go --
+package tidy
+
+import _ "example.com/tidy/a"
+import _ "example.com/tidy/b"
+-- want --
+# diagnostics
+go.mod: the following requirements are needed
+ example.com/tidy/b@v0.2.0
+Run 'go mod tidy' to add missing requirements.
+
+# summary
+Suggested version: v0.1.0
diff --git a/cmd/gorelease/testdata/tidy/missing_go.test b/cmd/gorelease/testdata/tidy/missing_go.test
new file mode 100644
index 0000000..99ca05a
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/missing_go.test
@@ -0,0 +1,13 @@
+mod=example.com/tidy
+base=v0.0.1
+success=0
+-- want --
+# diagnostics
+go.mod: go directive is missing
+
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/tidy
+-- tidy.go --
+package tidy
diff --git a/cmd/gorelease/testdata/tidy/missing_req_basic.test b/cmd/gorelease/testdata/tidy/missing_req_basic.test
new file mode 100644
index 0000000..97a95a3
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/missing_req_basic.test
@@ -0,0 +1,19 @@
+mod=example.com/tidy
+base=v0.0.1
+success=false
+-- want --
+# diagnostics
+go.mod: the following requirements are needed
+ example.com/basic@v1.1.2
+Run 'go mod tidy' to add missing requirements.
+
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+-- tidy.go --
+package tidy
+
+import _ "example.com/basic/a"
diff --git a/cmd/gorelease/testdata/tidy/missing_req_nested.test b/cmd/gorelease/testdata/tidy/missing_req_nested.test
new file mode 100644
index 0000000..1533d6a
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/missing_req_nested.test
@@ -0,0 +1,25 @@
+mod=example.com/tidy
+base=v0.0.1
+success=false
+-- want --
+# example.com/tidy/subdir
+## compatible changes
+package added
+
+# diagnostics
+go.mod: the following requirements are needed
+ example.com/basic@v1.1.2
+Run 'go mod tidy' to add missing requirements.
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+-- tidy.go --
+package tidy
+-- subdir/tidy.go --
+package subpkg
+
+import _ "example.com/basic/a"
diff --git a/cmd/gorelease/testdata/tidy/missing_req_submodule.test b/cmd/gorelease/testdata/tidy/missing_req_submodule.test
new file mode 100644
index 0000000..e210c2d
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/missing_req_submodule.test
@@ -0,0 +1,20 @@
+mod=example.com/tidy
+base=v0.0.1
+success=true
+-- want --
+# summary
+Suggested version: v0.0.2
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+-- foo/go.mod --
+module example.com/tidy/foo
+
+go 1.12
+-- tidy.go --
+package tidy
+-- foo/tidy.go --
+package subpkg
+
+import _ "example.com/basic/a"
diff --git a/cmd/gorelease/testdata/tidy/missing_req_twice_nested.test b/cmd/gorelease/testdata/tidy/missing_req_twice_nested.test
new file mode 100644
index 0000000..c06f465
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/missing_req_twice_nested.test
@@ -0,0 +1,25 @@
+mod=example.com/tidy
+base=v0.0.1
+success=false
+-- want --
+# example.com/tidy/subdir/subsubdir
+## compatible changes
+package added
+
+# diagnostics
+go.mod: the following requirements are needed
+ example.com/basic@v1.1.2
+Run 'go mod tidy' to add missing requirements.
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+-- tidy.go --
+package tidy
+-- subdir/subsubdir/tidy.go --
+package subpkg
+
+import _ "example.com/basic/a"
diff --git a/cmd/gorelease/testdata/tidy/no_sum.test b/cmd/gorelease/testdata/tidy/no_sum.test
new file mode 100644
index 0000000..f4ff48b
--- /dev/null
+++ b/cmd/gorelease/testdata/tidy/no_sum.test
@@ -0,0 +1,19 @@
+mod=example.com/tidy
+base=v0.0.1
+success=false
+-- want --
+# diagnostics
+go.sum: one or more sums are missing. Run 'go mod tidy' to add missing sums.
+
+# summary
+Suggested version: v0.1.0
+-- go.mod --
+module example.com/tidy
+
+go 1.12
+
+require example.com/basic v1.1.2
+-- tidy.go --
+package tidy
+
+import _ "example.com/basic/a"
diff --git a/cmd/macos-roots-test/main.go b/cmd/macos-roots-test/main.go
new file mode 100644
index 0000000..a7d2c80
--- /dev/null
+++ b/cmd/macos-roots-test/main.go
@@ -0,0 +1,121 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin
+
+// Command macOS-roots-test runs crypto/x509.TestSystemRoots as a
+// stand-alone binary for crowdsourced testing.
+package main
+
+import (
+ "crypto/x509"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "time"
+ "unsafe"
+)
+
+type CertPool struct {
+ bySubjectKeyId map[string][]int
+ byName map[string][]int
+ certs []*x509.Certificate
+}
+
+func (s *CertPool) contains(cert *x509.Certificate) bool {
+ if s == nil {
+ return false
+ }
+
+ candidates := s.byName[string(cert.RawSubject)]
+ for _, c := range candidates {
+ if s.certs[c].Equal(cert) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func main() {
+ var failed bool
+
+ t0 := time.Now()
+ sysRootsExt, err := loadSystemRoots() // actual system roots
+ sysRootsDuration := time.Since(t0)
+
+ if err != nil {
+ log.Fatalf("failed to read system roots (cgo): %v", err)
+ }
+ sysRoots := (*CertPool)(unsafe.Pointer(sysRootsExt))
+
+ t1 := time.Now()
+ execRootsExt, err := execSecurityRoots() // non-cgo roots
+ execSysRootsDuration := time.Since(t1)
+
+ if err != nil {
+ log.Fatalf("failed to read system roots (nocgo): %v", err)
+ }
+ execRoots := (*CertPool)(unsafe.Pointer(execRootsExt))
+
+ fmt.Printf(" cgo sys roots: %v\n", sysRootsDuration)
+ fmt.Printf("non-cgo sys roots: %v\n", execSysRootsDuration)
+
+ // On Mavericks, there are 212 bundled certs, at least there was at
+ // one point in time on one machine. (Maybe it was a corp laptop
+ // with extra certs?) Other OS X users report 135, 142, 145...
+ // Let's try requiring at least 100, since this is just a sanity
+ // check.
+ if want, have := 100, len(sysRoots.certs); have < want {
+ failed = true
+ fmt.Printf("want at least %d system roots, have %d\n", want, have)
+ }
+
+ // Check that the two cert pools are the same.
+ sysPool := make(map[string]*x509.Certificate, len(sysRoots.certs))
+ for _, c := range sysRoots.certs {
+ sysPool[string(c.Raw)] = c
+ }
+ for _, c := range execRoots.certs {
+ if _, ok := sysPool[string(c.Raw)]; ok {
+ delete(sysPool, string(c.Raw))
+ } else {
+ // verify-cert lets in certificates that are not trusted roots, but are
+ // signed by trusted roots. This should not be a problem, so confirm that's
+ // the case and skip them.
+ if _, err := c.Verify(x509.VerifyOptions{
+ Roots: sysRootsExt,
+ Intermediates: execRootsExt, // the intermediates for EAP certs are stored in the keychain
+ KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
+ }); err != nil {
+ failed = true
+ fmt.Printf("certificate only present in non-cgo pool: %v (verify error: %v)\n", c.Subject, err)
+ } else {
+ fmt.Printf("signed certificate only present in non-cgo pool (acceptable): %v\n", c.Subject)
+ }
+ }
+ }
+ for _, c := range sysPool {
+ failed = true
+ fmt.Printf("certificate only present in cgo pool: %v\n", c.Subject)
+ }
+
+ if failed && debugDarwinRoots {
+ cmd := exec.Command("security", "dump-trust-settings")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Run()
+ cmd = exec.Command("security", "dump-trust-settings", "-d")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Run()
+ }
+
+ if failed {
+ fmt.Printf("\n\n!!! The test failed!\n\nPlease report *the whole output* at https://github.com/golang/go/issues/24652 wrapping it in ``` a code block ```\nThank you!\n")
+ } else {
+ fmt.Printf("\n\nThe test passed, no need to report the output. Thank you.\n")
+ }
+}
diff --git a/cmd/macos-roots-test/root_cgo_darwin.go b/cmd/macos-roots-test/root_cgo_darwin.go
new file mode 100644
index 0000000..591098f
--- /dev/null
+++ b/cmd/macos-roots-test/root_cgo_darwin.go
@@ -0,0 +1,290 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+/*
+#cgo CFLAGS: -mmacosx-version-min=10.10 -D__MAC_OS_X_VERSION_MAX_ALLOWED=101300
+#cgo LDFLAGS: -framework CoreFoundation -framework Security
+
+#include <errno.h>
+#include <sys/sysctl.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+
+void CFReleaseIfNotNULL(CFTypeRef cf) {
+ if (cf != NULL) CFRelease(cf);
+}
+
+static bool isSSLPolicy(SecPolicyRef policyRef) {
+ if (!policyRef) {
+ return false;
+ }
+ CFDictionaryRef properties = SecPolicyCopyProperties(policyRef);
+ if (properties == NULL) {
+ return false;
+ }
+ CFTypeRef value = NULL;
+ if (CFDictionaryGetValueIfPresent(properties, kSecPolicyOid, (const void **)&value)) {
+ CFRelease(properties);
+ return CFEqual(value, kSecPolicyAppleSSL);
+ }
+ CFRelease(properties);
+ return false;
+}
+
+// sslTrustSettingsResult obtains the final kSecTrustSettingsResult value
+// for a certificate in the user or admin domain, combining usage constraints
+// for the SSL SecTrustSettingsPolicy, ignoring SecTrustSettingsKeyUsage,
+// kSecTrustSettingsAllowedError and kSecTrustSettingsPolicyString.
+// https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
+static SInt32 sslTrustSettingsResult(SecCertificateRef cert) {
+ CFArrayRef trustSettings = NULL;
+ OSStatus err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings);
+
+ // According to Apple's SecTrustServer.c, "user trust settings overrule admin trust settings",
+ // but the rules of the override are unclear. Let's assume admin trust settings are applicable
+ // if and only if user trust settings fail to load or are NULL.
+ if (err != errSecSuccess || trustSettings == NULL) {
+ CFReleaseIfNotNULL(trustSettings);
+ err = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainAdmin, &trustSettings);
+ }
+
+ // > no trust settings [...] means "this certificate must be verified to a known trusted certificate”
+ if (err != errSecSuccess || trustSettings == NULL) {
+ CFReleaseIfNotNULL(trustSettings);
+ return kSecTrustSettingsResultUnspecified;
+ }
+
+ // > An empty trust settings array means "always trust this certificate” with an
+ // > overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot.
+ if (CFArrayGetCount(trustSettings) == 0) {
+ CFReleaseIfNotNULL(trustSettings);
+ return kSecTrustSettingsResultTrustRoot;
+ }
+
+ // kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"),
+ // but the Go linker's internal linking mode can't handle CFSTR relocations.
+ // Create our own dynamic string instead and release it below.
+ CFStringRef _kSecTrustSettingsResult = CFStringCreateWithCString(
+ NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8);
+ CFStringRef _kSecTrustSettingsPolicy = CFStringCreateWithCString(
+ NULL, "kSecTrustSettingsPolicy", kCFStringEncodingUTF8);
+
+ CFIndex m; SInt32 result = 0;
+ for (m = 0; m < CFArrayGetCount(trustSettings); m++) {
+ CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, m);
+
+ // First, check if this trust setting applies to our policy. We assume
+ // only one will. The docs suggest that there might be multiple applying
+ // but don't explain how to combine them.
+ SecPolicyRef policyRef;
+ if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsPolicy, (const void**)&policyRef)) {
+ if (!isSSLPolicy(policyRef)) {
+ continue;
+ }
+ } else {
+ continue;
+ }
+
+ CFNumberRef cfNum;
+ if (CFDictionaryGetValueIfPresent(tSetting, _kSecTrustSettingsResult, (const void**)&cfNum)) {
+ CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result);
+ } else {
+ // > If the value of the kSecTrustSettingsResult component is not
+ // > kSecTrustSettingsResultUnspecified for a usage constraints dictionary that has
+ // > no constraints, the default value kSecTrustSettingsResultTrustRoot is assumed.
+ result = kSecTrustSettingsResultTrustRoot;
+ }
+
+ break;
+ }
+
+ // If trust settings are present, but none of them match the policy...
+ // the docs don't tell us what to do.
+ //
+ // "Trust settings for a given use apply if any of the dictionaries in the
+ // certificate’s trust settings array satisfies the specified use." suggests
+ // that it's as if there were no trust settings at all, so we should probably
+ // fallback to the admin trust settings. TODO.
+ if (result == 0) {
+ result = kSecTrustSettingsResultUnspecified;
+ }
+
+ CFRelease(_kSecTrustSettingsResult);
+ CFRelease(trustSettings);
+
+ return result;
+}
+
+// FetchPEMRoots fetches the system's list of trusted X.509 root certificates
+// for the kSecTrustSettingsPolicy SSL.
+//
+// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
+// certificates of the system. On failure, the function returns -1.
+// Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots.
+//
+// Note: The CFDataRef returned in pemRoots and untrustedPemRoots must
+// be released (using CFRelease) after we've consumed its content.
+int _FetchPEMRoots(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots, bool debugDarwinRoots) {
+ int i;
+
+ if (debugDarwinRoots) {
+ printf("crypto/x509: kSecTrustSettingsResultInvalid = %d\n", kSecTrustSettingsResultInvalid);
+ printf("crypto/x509: kSecTrustSettingsResultTrustRoot = %d\n", kSecTrustSettingsResultTrustRoot);
+ printf("crypto/x509: kSecTrustSettingsResultTrustAsRoot = %d\n", kSecTrustSettingsResultTrustAsRoot);
+ printf("crypto/x509: kSecTrustSettingsResultDeny = %d\n", kSecTrustSettingsResultDeny);
+ printf("crypto/x509: kSecTrustSettingsResultUnspecified = %d\n", kSecTrustSettingsResultUnspecified);
+ }
+
+ // Get certificates from all domains, not just System, this lets
+ // the user add CAs to their "login" keychain, and Admins to add
+ // to the "System" keychain
+ SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem,
+ kSecTrustSettingsDomainAdmin,
+ kSecTrustSettingsDomainUser };
+
+ int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain);
+ if (pemRoots == NULL) {
+ return -1;
+ }
+
+ CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ for (i = 0; i < numDomains; i++) {
+ int j;
+ CFArrayRef certs = NULL;
+ OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs);
+ if (err != noErr) {
+ continue;
+ }
+
+ CFIndex numCerts = CFArrayGetCount(certs);
+ for (j = 0; j < numCerts; j++) {
+ CFDataRef data = NULL;
+ CFArrayRef trustSettings = NULL;
+ SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j);
+ if (cert == NULL) {
+ continue;
+ }
+
+ SInt32 result;
+ if (domains[i] == kSecTrustSettingsDomainSystem) {
+ // Certs found in the system domain are always trusted. If the user
+ // configures "Never Trust" on such a cert, it will also be found in the
+ // admin or user domain, causing it to be added to untrustedPemRoots. The
+ // Go code will then clean this up.
+ result = kSecTrustSettingsResultTrustAsRoot;
+ } else {
+ result = sslTrustSettingsResult(cert);
+ if (debugDarwinRoots) {
+ CFErrorRef errRef = NULL;
+ CFStringRef summary = SecCertificateCopyShortDescription(NULL, cert, &errRef);
+ if (errRef != NULL) {
+ printf("crypto/x509: SecCertificateCopyShortDescription failed\n");
+ CFRelease(errRef);
+ continue;
+ }
+
+ CFIndex length = CFStringGetLength(summary);
+ CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
+ char *buffer = malloc(maxSize);
+ if (CFStringGetCString(summary, buffer, maxSize, kCFStringEncodingUTF8)) {
+ printf("crypto/x509: %s returned %d\n", buffer, result);
+ }
+ free(buffer);
+ CFRelease(summary);
+ }
+ }
+
+ CFMutableDataRef appendTo;
+ if (result == kSecTrustSettingsResultTrustRoot) {
+ // "can only be applied to root (self-signed) certificates", so
+ // make sure Subject and Issuer Name match.
+ CFErrorRef errRef = NULL;
+ CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef);
+ if (errRef != NULL) {
+ CFRelease(errRef);
+ continue;
+ }
+ CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef);
+ if (errRef != NULL) {
+ CFRelease(subjectName);
+ CFRelease(errRef);
+ continue;
+ }
+ Boolean equal = CFEqual(subjectName, issuerName);
+ CFRelease(subjectName);
+ CFRelease(issuerName);
+ if (!equal) {
+ continue;
+ }
+
+ appendTo = combinedData;
+ } else if (result == kSecTrustSettingsResultTrustAsRoot) {
+ // In theory "can only be applied to non-root certificates", but ignore
+ // this for now, also because it's the state we assume for the system domain.
+ appendTo = combinedData;
+ } else if (result == kSecTrustSettingsResultDeny) {
+ appendTo = combinedUntrustedData;
+ } else if (result == kSecTrustSettingsResultUnspecified) {
+ continue;
+ } else {
+ continue;
+ }
+
+ err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
+ if (err != noErr) {
+ continue;
+ }
+ if (data != NULL) {
+ CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data));
+ CFRelease(data);
+ }
+ }
+ CFRelease(certs);
+ }
+ *pemRoots = combinedData;
+ *untrustedPemRoots = combinedUntrustedData;
+ return 0;
+}
+*/
+import "C"
+import (
+ "crypto/x509"
+ "errors"
+ "unsafe"
+)
+
+func loadSystemRoots() (*x509.CertPool, error) {
+ roots := x509.NewCertPool()
+
+ var data C.CFDataRef = 0
+ var untrustedData C.CFDataRef = 0
+ err := C._FetchPEMRoots(&data, &untrustedData, C.bool(debugDarwinRoots))
+ if err == -1 {
+ // TODO: better error message
+ return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo")
+ }
+
+ defer C.CFRelease(C.CFTypeRef(data))
+ buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
+ roots.AppendCertsFromPEM(buf)
+ if untrustedData == 0 {
+ return roots, nil
+ }
+ defer C.CFRelease(C.CFTypeRef(untrustedData))
+ buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData)))
+ untrustedRoots := x509.NewCertPool()
+ untrustedRoots.AppendCertsFromPEM(buf)
+
+ trustedRoots := x509.NewCertPool()
+ for _, c := range (*CertPool)(unsafe.Pointer(roots)).certs {
+ if !(*CertPool)(unsafe.Pointer(untrustedRoots)).contains(c) {
+ trustedRoots.AddCert(c)
+ }
+ }
+ return trustedRoots, nil
+}
diff --git a/cmd/macos-roots-test/root_darwin.go b/cmd/macos-roots-test/root_darwin.go
new file mode 100644
index 0000000..46f8c39
--- /dev/null
+++ b/cmd/macos-roots-test/root_darwin.go
@@ -0,0 +1,173 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "sync"
+)
+
+var debugDarwinRoots = true
+
+// This code is only used when compiling without cgo.
+// It is here, instead of root_nocgo_darwin.go, so that tests can check it
+// even if the tests are run with cgo enabled.
+// The linker will not include these unused functions in binaries built with cgo enabled.
+
+// execSecurityRoots finds the macOS list of trusted root certificates
+// using only command-line tools. This is our fallback path when cgo isn't available.
+//
+// The strategy is as follows:
+//
+// 1. Run "security find-certificate" to dump the list of system root
+// CAs in PEM format.
+//
+// 2. For each dumped cert, conditionally verify it with "security
+// verify-cert" if that cert was not in the SystemRootCertificates
+// keychain, which can't have custom trust policies.
+//
+// We need to run "verify-cert" for all certificates not in SystemRootCertificates
+// because there might be certificates in the keychains without a corresponding
+// trust entry, in which case the logic is complicated (see root_cgo_darwin.go).
+//
+// TODO: actually parse the "trust-settings-export" output and apply the full
+// logic. See Issue 26830.
+func execSecurityRoots() (*x509.CertPool, error) {
+ keychains := []string{"/Library/Keychains/System.keychain"}
+
+ // Note that this results in trusting roots from $HOME/... (the environment
+ // variable), which might not be expected.
+ u, err := user.Current()
+ if err != nil {
+ if debugDarwinRoots {
+ fmt.Printf("crypto/x509: get current user: %v\n", err)
+ }
+ } else {
+ keychains = append(keychains,
+ filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain"),
+
+ // Fresh installs of Sierra use a slightly different path for the login keychain
+ filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain-db"),
+ )
+ }
+
+ var (
+ mu sync.Mutex
+ roots = x509.NewCertPool()
+ numVerified int // number of execs of 'security verify-cert', for debug stats
+ wg sync.WaitGroup
+ verifyCh = make(chan *x509.Certificate)
+ )
+
+ // Using 4 goroutines to pipe into verify-cert seems to be
+ // about the best we can do. The verify-cert binary seems to
+ // just RPC to another server with coarse locking anyway, so
+ // running 16 at a time for instance doesn't help at all.
+ for i := 0; i < 4; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for cert := range verifyCh {
+ valid := verifyCertWithSystem(cert)
+
+ mu.Lock()
+ numVerified++
+ if valid {
+ roots.AddCert(cert)
+ }
+ mu.Unlock()
+ }
+ }()
+ }
+ err = forEachCertInKeychains(keychains, func(cert *x509.Certificate) {
+ verifyCh <- cert
+ })
+ if err != nil {
+ return nil, err
+ }
+ close(verifyCh)
+ wg.Wait()
+
+ if debugDarwinRoots {
+ fmt.Printf("crypto/x509: ran security verify-cert %d times\n", numVerified)
+ }
+
+ err = forEachCertInKeychains([]string{
+ "/System/Library/Keychains/SystemRootCertificates.keychain",
+ }, roots.AddCert)
+ if err != nil {
+ return nil, err
+ }
+
+ return roots, nil
+}
+
+func forEachCertInKeychains(paths []string, f func(*x509.Certificate)) error {
+ args := append([]string{"find-certificate", "-a", "-p"}, paths...)
+ cmd := exec.Command("/usr/bin/security", args...)
+ data, err := cmd.Output()
+ if err != nil {
+ return err
+ }
+ for len(data) > 0 {
+ var block *pem.Block
+ block, data = pem.Decode(data)
+ if block == nil {
+ break
+ }
+ if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
+ continue
+ }
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ continue
+ }
+ f(cert)
+ }
+ return nil
+}
+
+func verifyCertWithSystem(cert *x509.Certificate) bool {
+ data := pem.EncodeToMemory(&pem.Block{
+ Type: "CERTIFICATE", Bytes: cert.Raw,
+ })
+
+ f, err := os.CreateTemp("", "cert")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
+ return false
+ }
+ defer os.Remove(f.Name())
+ if _, err := f.Write(data); err != nil {
+ fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
+ return false
+ }
+ if err := f.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
+ return false
+ }
+ cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
+ var stderr bytes.Buffer
+ if debugDarwinRoots {
+ cmd.Stderr = &stderr
+ }
+ if err := cmd.Run(); err != nil {
+ if debugDarwinRoots {
+ fmt.Printf("crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
+ }
+ return false
+ }
+ if debugDarwinRoots {
+ fmt.Printf("crypto/x509: verify-cert approved %s\n", cert.Subject)
+ }
+ return true
+}
diff --git a/cmd/macos-roots-test/root_nocgo_darwin.go b/cmd/macos-roots-test/root_nocgo_darwin.go
new file mode 100644
index 0000000..94c6105
--- /dev/null
+++ b/cmd/macos-roots-test/root_nocgo_darwin.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !cgo
+
+package main
+
+import (
+ "crypto/x509"
+ "errors"
+)
+
+func loadSystemRoots() (*x509.CertPool, error) {
+ return nil, errors.New("can't load system roots: cgo not enabled")
+}
diff --git a/cmd/modgraphviz/main.go b/cmd/modgraphviz/main.go
new file mode 100644
index 0000000..a0351dc
--- /dev/null
+++ b/cmd/modgraphviz/main.go
@@ -0,0 +1,166 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Modgraphviz converts β€œgo mod graph” output into Graphviz's DOT language,
+// for use with Graphviz visualization and analysis tools like dot, dotty, and sccmap.
+//
+// Usage:
+//
+// go mod graph | modgraphviz > graph.dot
+// go mod graph | modgraphviz | dot -Tpng -o graph.png
+//
+// Modgraphviz takes no options or arguments; it reads a graph in the format
+// generated by β€œgo mod graph” on standard input and writes DOT language
+// on standard output.
+//
+// For each module, the node representing the greatest version (i.e., the
+// version chosen by Go's minimal version selection algorithm) is colored green.
+// Other nodes, which aren't in the final build list, are colored grey.
+//
+// See http://www.graphviz.org/doc/info/lang.html for details of the DOT language
+// and http://www.graphviz.org/about/ for Graphviz itself.
+//
+// See also golang.org/x/tools/cmd/digraph for general queries and analysis
+// of β€œgo mod graph” output.
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "sort"
+ "strings"
+
+ "golang.org/x/mod/semver"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage: go mod graph | modgraphviz | dot -Tpng -o graph.png
+
+For each module, the node representing the greatest version (i.e., the
+version chosen by Go's minimal version selection algorithm) is colored green.
+Other nodes, which aren't in the final build list, are colored grey.
+`)
+ os.Exit(2)
+}
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("modgraphviz: ")
+
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 0 {
+ usage()
+ }
+
+ if err := modgraphviz(os.Stdin, os.Stdout); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func modgraphviz(in io.Reader, out io.Writer) error {
+ graph, err := convert(in)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(out, "digraph gomodgraph {\n")
+ fmt.Fprintf(out, "\tnode [ shape=rectangle fontsize=12 ]\n")
+ out.Write(graph.edgesAsDOT())
+ for _, n := range graph.mvsPicked {
+ fmt.Fprintf(out, "\t%q [style = filled, fillcolor = green]\n", n)
+ }
+ for _, n := range graph.mvsUnpicked {
+ fmt.Fprintf(out, "\t%q [style = filled, fillcolor = gray]\n", n)
+ }
+ fmt.Fprintf(out, "}\n")
+
+ return nil
+}
+
+type edge struct{ from, to string }
+type graph struct {
+ edges []edge
+ mvsPicked []string
+ mvsUnpicked []string
+}
+
+// convert reads β€œgo mod graph” output from r and returns a graph, recording
+// MVS picked and unpicked nodes along the way.
+func convert(r io.Reader) (*graph, error) {
+ scanner := bufio.NewScanner(r)
+ var g graph
+ seen := map[string]bool{}
+ mvsPicked := map[string]string{} // module name -> module version
+
+ for scanner.Scan() {
+ l := scanner.Text()
+ if l == "" {
+ continue
+ }
+ parts := strings.Fields(l)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("expected 2 words in line, but got %d: %s", len(parts), l)
+ }
+ from := parts[0]
+ to := parts[1]
+ g.edges = append(g.edges, edge{from: from, to: to})
+
+ for _, node := range []string{from, to} {
+ if _, ok := seen[node]; ok {
+ // Skip over nodes we've already seen.
+ continue
+ }
+ seen[node] = true
+
+ var m, v string
+ if i := strings.IndexByte(node, '@'); i >= 0 {
+ m, v = node[:i], node[i+1:]
+ } else {
+ // Root node doesn't have a version.
+ continue
+ }
+
+ if maxV, ok := mvsPicked[m]; ok {
+ if semver.Compare(maxV, v) < 0 {
+ // This version is higher - replace it and consign the old
+ // max to the unpicked list.
+ g.mvsUnpicked = append(g.mvsUnpicked, m+"@"+maxV)
+ mvsPicked[m] = v
+ } else {
+ // Other version is higher - stick this version in the
+ // unpicked list.
+ g.mvsUnpicked = append(g.mvsUnpicked, node)
+ }
+ } else {
+ mvsPicked[m] = v
+ }
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ for m, v := range mvsPicked {
+ g.mvsPicked = append(g.mvsPicked, m+"@"+v)
+ }
+
+ // Make this function deterministic.
+ sort.Strings(g.mvsPicked)
+ return &g, nil
+}
+
+// edgesAsDOT returns the edges in DOT notation.
+func (g *graph) edgesAsDOT() []byte {
+ var buf bytes.Buffer
+ for _, e := range g.edges {
+ fmt.Fprintf(&buf, "\t%q -> %q\n", e.from, e.to)
+ }
+ return buf.Bytes()
+}
diff --git a/cmd/modgraphviz/main_test.go b/cmd/modgraphviz/main_test.go
new file mode 100644
index 0000000..bec88cf
--- /dev/null
+++ b/cmd/modgraphviz/main_test.go
@@ -0,0 +1,116 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+func TestRun(t *testing.T) {
+ out := &bytes.Buffer{}
+ in := bytes.NewBuffer([]byte(`
+test.com/A@v1.0.0 test.com/B@v1.2.3
+test.com/B@v1.0.0 test.com/C@v4.5.6
+`))
+ if err := modgraphviz(in, out); err != nil {
+ t.Fatal(err)
+ }
+
+ gotGraph := string(out.Bytes())
+ wantGraph := `digraph gomodgraph {
+ node [ shape=rectangle fontsize=12 ]
+ "test.com/A@v1.0.0" -> "test.com/B@v1.2.3"
+ "test.com/B@v1.0.0" -> "test.com/C@v4.5.6"
+ "test.com/A@v1.0.0" [style = filled, fillcolor = green]
+ "test.com/B@v1.2.3" [style = filled, fillcolor = green]
+ "test.com/C@v4.5.6" [style = filled, fillcolor = green]
+ "test.com/B@v1.0.0" [style = filled, fillcolor = gray]
+}
+`
+ if gotGraph != wantGraph {
+ t.Fatalf("\ngot: %s\nwant: %s", gotGraph, wantGraph)
+ }
+}
+
+func TestMVSPicking(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ in []string
+ wantPicked []string
+ wantUnpicked []string
+ }{
+ {
+ name: "single node",
+ in: []string{"foo@v0.0.1"},
+ wantPicked: []string{"foo@v0.0.1"},
+ wantUnpicked: nil,
+ },
+ {
+ name: "duplicate same node",
+ in: []string{"foo@v0.0.1", "foo@v0.0.1"},
+ wantPicked: []string{"foo@v0.0.1"},
+ wantUnpicked: nil,
+ },
+ {
+ name: "multiple semver - same major",
+ in: []string{"foo@v1.0.0", "foo@v1.3.7", "foo@v1.2.0", "foo@v1.0.1"},
+ wantPicked: []string{"foo@v1.3.7"},
+ wantUnpicked: []string{"foo@v1.0.0", "foo@v1.2.0", "foo@v1.0.1"},
+ },
+ {
+ name: "multiple semver - multiple major",
+ in: []string{"foo@v1.0.0", "foo@v1.3.7", "foo/v2@v2.2.0", "foo/v2@v2.0.1", "foo@v1.1.1"},
+ wantPicked: []string{"foo/v2@v2.2.0", "foo@v1.3.7"},
+ wantUnpicked: []string{"foo@v1.0.0", "foo/v2@v2.0.1", "foo@v1.1.1"},
+ },
+ {
+ name: "semver and pseudo version",
+ in: []string{"foo@v1.0.0", "foo@v1.3.7", "foo/v2@v2.2.0", "foo/v2@v2.0.1", "foo@v1.1.1", "foo@v0.0.0-20190311183353-d8887717615a"},
+ wantPicked: []string{"foo/v2@v2.2.0", "foo@v1.3.7"},
+ wantUnpicked: []string{"foo@v1.0.0", "foo/v2@v2.0.1", "foo@v1.1.1", "foo@v0.0.0-20190311183353-d8887717615a"},
+ },
+ {
+ name: "multiple pseudo version",
+ in: []string{
+ "foo@v0.0.0-20190311183353-d8887717615a",
+ "foo@v0.0.0-20190227222117-0694c2d4d067",
+ "foo@v0.0.0-20190312151545-0bb0c0a6e846",
+ },
+ wantPicked: []string{"foo@v0.0.0-20190312151545-0bb0c0a6e846"},
+ wantUnpicked: []string{
+ "foo@v0.0.0-20190227222117-0694c2d4d067",
+ "foo@v0.0.0-20190311183353-d8887717615a",
+ },
+ },
+ {
+ name: "semver and suffix",
+ in: []string{"foo@v1.0.0", "foo@v1.3.8-rc1", "foo@v1.3.7"},
+ wantPicked: []string{"foo@v1.3.8-rc1"},
+ wantUnpicked: []string{"foo@v1.0.0", "foo@v1.3.7"},
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ buf := bytes.Buffer{}
+ for _, node := range tc.in {
+ fmt.Fprintf(&buf, "A %s\n", node)
+ }
+
+ g, err := convert(&buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(g.mvsPicked, tc.wantPicked) {
+ t.Fatalf("picked: got %v, want %v", g.mvsPicked, tc.wantPicked)
+ }
+ if !reflect.DeepEqual(g.mvsUnpicked, tc.wantUnpicked) {
+ t.Fatalf("unpicked: got %v, want %v", g.mvsUnpicked, tc.wantUnpicked)
+ }
+ })
+ }
+}
diff --git a/cmd/txtar/txtar.go b/cmd/txtar/txtar.go
new file mode 100644
index 0000000..4757a03
--- /dev/null
+++ b/cmd/txtar/txtar.go
@@ -0,0 +1,221 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The txtar command writes or extracts a text-based file archive in the format
+// provided by the golang.org/x/tools/txtar package.
+//
+// The default behavior is to read a comment from stdin and write the archive
+// file containing the recursive contents of the named files and directories,
+// including hidden files, to stdout. Any non-flag arguments to the command name
+// the files and/or directories to include, with the contents of directories
+// included recursively. An empty argument list is equivalent to ".".
+//
+// The --extract (or -x) flag instructs txtar to instead read the archive file
+// from stdin and extract all of its files to corresponding locations relative
+// to the current, writing the archive's comment to stdout.
+//
+// The --list flag instructs txtar to instead read the archive file from stdin
+// and list all of its files to stdout. Note that shell variables in paths are
+// not expanded in this mode.
+//
+// Archive files are by default extracted only to the current directory or its
+// subdirectories. To allow extracting outside the current directory, use the
+// --unsafe flag.
+//
+// When extracting, shell variables in paths are expanded (using os.Expand) if
+// the corresponding variable is set in the process environment. When writing an
+// archive, the variables (before expansion) are preserved in the archived paths.
+//
+// Example usage:
+//
+// txtar *.go <README >testdata/example.txt
+//
+// txtar --extract <playground_example.txt >main.go
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "golang.org/x/tools/txtar"
+)
+
+var (
+ extractFlag = flag.Bool("extract", false, "if true, extract files from the archive instead of writing to it")
+ listFlag = flag.Bool("list", false, "if true, list files from the archive instead of writing to it")
+ unsafeFlag = flag.Bool("unsafe", false, "allow extraction of files outside the current directory")
+)
+
+func init() {
+ flag.BoolVar(extractFlag, "x", *extractFlag, "short alias for --extract")
+}
+
+func main() {
+ flag.Parse()
+
+ var err error
+ switch {
+ case *extractFlag:
+ if len(flag.Args()) > 0 {
+ fmt.Fprintln(os.Stderr, "Usage: txtar --extract <archive.txt")
+ os.Exit(2)
+ }
+ err = extract()
+ case *listFlag:
+ if len(flag.Args()) > 0 {
+ fmt.Fprintln(os.Stderr, "Usage: txtar --list <archive.txt")
+ os.Exit(2)
+ }
+ err = list()
+ default:
+ paths := flag.Args()
+ if len(paths) == 0 {
+ paths = []string{"."}
+ }
+ err = archive(paths)
+ }
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+func extract() (err error) {
+ b, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ return err
+ }
+
+ ar := txtar.Parse(b)
+
+ if !*unsafeFlag {
+ // Check that no files are extracted outside the current directory
+ wd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+ // Add trailing separator to terminate wd.
+ // This prevents extracting to outside paths which prefix wd,
+ // e.g. extracting to /home/foobar when wd is /home/foo
+ if !strings.HasSuffix(wd, string(filepath.Separator)) {
+ wd += string(filepath.Separator)
+ }
+
+ for _, f := range ar.Files {
+ fileName := filepath.Clean(expand(f.Name))
+
+ if strings.HasPrefix(fileName, "..") ||
+ (filepath.IsAbs(fileName) && !strings.HasPrefix(fileName, wd)) {
+ return fmt.Errorf("file path '%s' is outside the current directory", f.Name)
+ }
+ }
+ }
+
+ for _, f := range ar.Files {
+ fileName := filepath.FromSlash(path.Clean(expand(f.Name)))
+ if err := os.MkdirAll(filepath.Dir(fileName), 0777); err != nil {
+ return err
+ }
+ if err := os.WriteFile(fileName, f.Data, 0666); err != nil {
+ return err
+ }
+ }
+
+ if len(ar.Comment) > 0 {
+ os.Stdout.Write(ar.Comment)
+ }
+ return nil
+}
+
+func list() (err error) {
+ b, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ return err
+ }
+
+ ar := txtar.Parse(b)
+ for _, f := range ar.Files {
+ fmt.Println(f.Name)
+ }
+ return nil
+}
+
+func archive(paths []string) (err error) {
+ txtarHeader := regexp.MustCompile(`(?m)^-- .* --$`)
+
+ ar := new(txtar.Archive)
+ for _, p := range paths {
+ root := filepath.Clean(expand(p))
+ prefix := root + string(filepath.Separator)
+ err := filepath.Walk(root, func(fileName string, info os.FileInfo, err error) error {
+ if err != nil || info.IsDir() {
+ return err
+ }
+
+ suffix := ""
+ if fileName != root {
+ suffix = strings.TrimPrefix(fileName, prefix)
+ }
+ name := filepath.ToSlash(filepath.Join(p, suffix))
+
+ data, err := os.ReadFile(fileName)
+ if err != nil {
+ return err
+ }
+ if txtarHeader.Match(data) {
+ return fmt.Errorf("cannot archive %s: file contains a txtar header", name)
+ }
+
+ ar.Files = append(ar.Files, txtar.File{Name: name, Data: data})
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ // After we have read all of the source files, read the comment from stdin.
+ //
+ // Wait until the read has been blocked for a while before prompting the user
+ // to enter it: if they are piping the comment in from some other file, the
+ // read should complete very quickly and there is no need for a prompt.
+ // (200ms is typically long enough to read a reasonable comment from the local
+ // machine, but short enough that humans don't notice it.)
+ //
+ // Don't prompt until we have successfully read the other files:
+ // if we encountered an error, we don't need to ask for a comment.
+ timer := time.AfterFunc(200*time.Millisecond, func() {
+ fmt.Fprintln(os.Stderr, "Enter comment:")
+ })
+ comment, err := io.ReadAll(os.Stdin)
+ timer.Stop()
+ if err != nil {
+ return fmt.Errorf("reading comment from %s: %v", os.Stdin.Name(), err)
+ }
+ ar.Comment = bytes.TrimSpace(comment)
+
+ _, err = os.Stdout.Write(txtar.Format(ar))
+ return err
+}
+
+// expand is like os.ExpandEnv, but preserves unescaped variables (instead
+// of escaping them to the empty string) if the variable is not set.
+func expand(p string) string {
+ return os.Expand(p, func(key string) string {
+ v, ok := os.LookupEnv(key)
+ if !ok {
+ return "$" + key
+ }
+ return v
+ })
+}
diff --git a/cmd/txtar/txtar_test.go b/cmd/txtar/txtar_test.go
new file mode 100644
index 0000000..35e319c
--- /dev/null
+++ b/cmd/txtar/txtar_test.go
@@ -0,0 +1,195 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main_test
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+)
+
+const comment = "This is a txtar archive.\n"
+
+const testdata = `This is a txtar archive.
+-- one.txt --
+one
+-- dir/two.txt --
+two
+-- $SPECIAL_LOCATION/three.txt --
+three
+`
+
+var filelist = `
+one.txt
+dir/two.txt
+$SPECIAL_LOCATION/three.txt
+`[1:]
+
+func TestMain(m *testing.M) {
+ code := m.Run()
+ txtarBin.once.Do(func() {})
+ if txtarBin.name != "" {
+ os.Remove(txtarBin.name)
+ }
+ os.Exit(code)
+}
+
+func TestRoundTrip(t *testing.T) {
+ os.Setenv("SPECIAL_LOCATION", "special")
+ defer os.Unsetenv("SPECIAL_LOCATION")
+
+ // Expand the testdata archive into a temporary directory.
+ parentDir, err := os.MkdirTemp("", "txtar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(parentDir)
+ dir := filepath.Join(parentDir, "dir")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ if out, err := txtar(t, dir, testdata, "--list"); err != nil {
+ t.Fatal(err)
+ } else if out != filelist {
+ t.Fatalf("txtar --list: stdout:\n%s\nwant:\n%s", out, filelist)
+ }
+ if entries, err := os.ReadDir(dir); err != nil {
+ t.Fatal(err)
+ } else if len(entries) > 0 {
+ t.Fatalf("txtar --list: did not expect any extracted files")
+ }
+
+ if out, err := txtar(t, dir, testdata, "--extract"); err != nil {
+ t.Fatal(err)
+ } else if out != comment {
+ t.Fatalf("txtar --extract: stdout:\n%s\nwant:\n%s", out, comment)
+ }
+
+ // Now, re-archive its contents explicitly and ensure that the result matches
+ // the original.
+ args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"}
+ if out, err := txtar(t, dir, comment, args...); err != nil {
+ t.Fatal(err)
+ } else if out != testdata {
+ t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata)
+ }
+}
+
+func TestUnsafePaths(t *testing.T) {
+ // Set up temporary directories for test archives.
+ parentDir, err := os.MkdirTemp("", "txtar")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(parentDir)
+ dir := filepath.Join(parentDir, "dir")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test --unsafe option for both absolute and relative paths
+ testcases := []struct{ name, path string }{
+ {"Absolute", filepath.Join(parentDir, "dirSpecial")},
+ {"Relative", "../special"},
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ // Set SPECIAL_LOCATION outside the current directory
+ t.Setenv("SPECIAL_LOCATION", tc.path)
+
+ // Expand the testdata archive into a temporary directory.
+
+ // Should fail without the --unsafe flag
+ if _, err := txtar(t, dir, testdata, "--extract"); err == nil {
+ t.Fatalf("txtar --extract: extracts to unsafe paths")
+ }
+
+ // Should allow paths outside the current dir with the --unsafe flags
+ out, err := txtar(t, dir, testdata, "--extract", "--unsafe")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if out != comment {
+ t.Fatalf("txtar --extract --unsafe: stdout:\n%s\nwant:\n%s", out, comment)
+ }
+
+ // Now, re-archive its contents explicitly and ensure that the result matches
+ // the original.
+ args := []string{"one.txt", "dir", "$SPECIAL_LOCATION"}
+ out, err = txtar(t, dir, comment, args...)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if out != testdata {
+ t.Fatalf("txtar %s: archive:\n%s\n\nwant:\n%s", strings.Join(args, " "), out, testdata)
+ }
+ })
+ }
+}
+
+// txtar runs the txtar command in the given directory with the given input and
+// arguments.
+func txtar(t *testing.T, dir, input string, args ...string) (string, error) {
+ t.Helper()
+ cmd := exec.Command(txtarName(t), args...)
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "PWD="+dir)
+ cmd.Stdin = strings.NewReader(input)
+ stderr := new(strings.Builder)
+ cmd.Stderr = stderr
+ out, err := cmd.Output()
+ if err != nil {
+ return "", fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, stderr)
+ }
+ if stderr.String() != "" {
+ t.Logf("OK: %s\n%s", strings.Join(cmd.Args, " "), stderr)
+ }
+ return string(out), nil
+}
+
+var txtarBin struct {
+ once sync.Once
+ name string
+ err error
+}
+
+// txtarName returns the name of the txtar executable, building it if needed.
+func txtarName(t *testing.T) string {
+ t.Helper()
+ if _, err := exec.LookPath("go"); err != nil {
+ t.Skipf("cannot build txtar binary: %v", err)
+ }
+
+ txtarBin.once.Do(func() {
+ exe, err := os.CreateTemp("", "txtar-*.exe")
+ if err != nil {
+ txtarBin.err = err
+ return
+ }
+ exe.Close()
+ txtarBin.name = exe.Name()
+
+ cmd := exec.Command("go", "build", "-o", txtarBin.name, ".")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ txtarBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
+ }
+ })
+
+ if txtarBin.err != nil {
+ if runtime.GOOS == "android" {
+ t.Skipf("skipping test after failing to build txtar binary: go_android_exec may have failed to copy needed dependencies (see https://golang.org/issue/37088)")
+ }
+ t.Fatal(txtarBin.err)
+ }
+ return txtarBin.name
+}
diff --git a/codereview.cfg b/codereview.cfg
new file mode 100644
index 0000000..3f8b14b
--- /dev/null
+++ b/codereview.cfg
@@ -0,0 +1 @@
+issuerepo: golang/go
diff --git a/constraints/constraints.go b/constraints/constraints.go
new file mode 100644
index 0000000..2c033df
--- /dev/null
+++ b/constraints/constraints.go
@@ -0,0 +1,50 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package constraints defines a set of useful constraints to be used
+// with type parameters.
+package constraints
+
+// Signed is a constraint that permits any signed integer type.
+// If future releases of Go add new predeclared signed integer types,
+// this constraint will be modified to include them.
+type Signed interface {
+ ~int | ~int8 | ~int16 | ~int32 | ~int64
+}
+
+// Unsigned is a constraint that permits any unsigned integer type.
+// If future releases of Go add new predeclared unsigned integer types,
+// this constraint will be modified to include them.
+type Unsigned interface {
+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
+}
+
+// Integer is a constraint that permits any integer type.
+// If future releases of Go add new predeclared integer types,
+// this constraint will be modified to include them.
+type Integer interface {
+ Signed | Unsigned
+}
+
+// Float is a constraint that permits any floating-point type.
+// If future releases of Go add new predeclared floating-point types,
+// this constraint will be modified to include them.
+type Float interface {
+ ~float32 | ~float64
+}
+
+// Complex is a constraint that permits any complex numeric type.
+// If future releases of Go add new predeclared complex numeric types,
+// this constraint will be modified to include them.
+type Complex interface {
+ ~complex64 | ~complex128
+}
+
+// Ordered is a constraint that permits any ordered type: any type
+// that supports the operators < <= >= >.
+// If future releases of Go add new ordered types,
+// this constraint will be modified to include them.
+type Ordered interface {
+ Integer | Float | ~string
+}
diff --git a/constraints/constraints_test.go b/constraints/constraints_test.go
new file mode 100644
index 0000000..28af970
--- /dev/null
+++ b/constraints/constraints_test.go
@@ -0,0 +1,161 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package constraints
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "testing"
+)
+
+type (
+ testSigned[T Signed] struct{ f T }
+ testUnsigned[T Unsigned] struct{ f T }
+ testInteger[T Integer] struct{ f T }
+ testFloat[T Float] struct{ f T }
+ testComplex[T Complex] struct{ f T }
+ testOrdered[T Ordered] struct{ f T }
+)
+
+// TestTypes passes if it compiles.
+type TestTypes struct {
+ _ testSigned[int]
+ _ testSigned[int64]
+ _ testUnsigned[uint]
+ _ testUnsigned[uintptr]
+ _ testInteger[int8]
+ _ testInteger[uint8]
+ _ testInteger[uintptr]
+ _ testFloat[float32]
+ _ testComplex[complex64]
+ _ testOrdered[int]
+ _ testOrdered[float64]
+ _ testOrdered[string]
+}
+
+var prolog = []byte(`
+package constrainttest
+
+import "golang.org/x/exp/constraints"
+
+type (
+ testSigned[T constraints.Signed] struct{ f T }
+ testUnsigned[T constraints.Unsigned] struct{ f T }
+ testInteger[T constraints.Integer] struct{ f T }
+ testFloat[T constraints.Float] struct{ f T }
+ testComplex[T constraints.Complex] struct{ f T }
+ testOrdered[T constraints.Ordered] struct{ f T }
+)
+`)
+
+func TestFailure(t *testing.T) {
+ switch runtime.GOOS {
+ case "android", "js", "ios":
+ t.Skipf("can't run go tool on %s", runtime.GOOS)
+ }
+
+ var exeSuffix string
+ if runtime.GOOS == "windows" {
+ exeSuffix = ".exe"
+ }
+ gocmd := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
+ if _, err := os.Stat(gocmd); err != nil {
+ t.Skipf("skipping because can't stat %s: %v", gocmd, err)
+ }
+
+ tmpdir := t.TempDir()
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // This package is golang.org/x/exp/constraints, so the root of the x/exp
+ // module is the parent directory of the directory in which this test runs.
+ expModDir := filepath.Dir(cwd)
+
+ modFile := fmt.Sprintf(`module constraintest
+
+go 1.18
+
+replace golang.org/x/exp => %s
+`, expModDir)
+ if err := os.WriteFile(filepath.Join(tmpdir, "go.mod"), []byte(modFile), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ // Write the prolog as its own file so that 'go mod tidy' has something to inspect.
+ // This will ensure that the go.mod and go.sum files include any dependencies
+ // needed by the constraints package (which should just be some version of
+ // x/exp itself).
+ if err := os.WriteFile(filepath.Join(tmpdir, "prolog.go"), []byte(prolog), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ tidyCmd := exec.Command(gocmd, "mod", "tidy")
+ tidyCmd.Dir = tmpdir
+ tidyCmd.Env = append(os.Environ(), "PWD="+tmpdir)
+ if out, err := tidyCmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v: %v\n%s", tidyCmd, err, out)
+ } else {
+ t.Logf("%v:\n%s", tidyCmd, out)
+ }
+
+ // Test for types that should not satisfy a constraint.
+ // For each pair of constraint and type, write a Go file
+ // var V constraint[type]
+ // For example,
+ // var V testSigned[uint]
+ // This should not compile, as testSigned (above) uses
+ // constraints.Signed, and uint does not satisfy that constraint.
+ // Therefore, the build of that code should fail.
+ for i, test := range []struct {
+ constraint, typ string
+ }{
+ {"testSigned", "uint"},
+ {"testUnsigned", "int"},
+ {"testInteger", "float32"},
+ {"testFloat", "int8"},
+ {"testComplex", "float64"},
+ {"testOrdered", "bool"},
+ } {
+ i := i
+ test := test
+ t.Run(fmt.Sprintf("%s %d", test.constraint, i), func(t *testing.T) {
+ t.Parallel()
+ name := fmt.Sprintf("go%d.go", i)
+ f, err := os.Create(filepath.Join(tmpdir, name))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := f.Write(prolog); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := fmt.Fprintf(f, "var V %s[%s]\n", test.constraint, test.typ); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
+ cmd := exec.Command(gocmd, "build", name)
+ cmd.Dir = tmpdir
+ if out, err := cmd.CombinedOutput(); err == nil {
+ t.Error("build succeeded, but expected to fail")
+ } else if len(out) > 0 {
+ t.Logf("%s", out)
+ if !wantRx.Match(out) {
+ t.Errorf("output does not match %q", wantRx)
+ }
+ } else {
+ t.Error("no error output, expected something")
+ }
+ })
+ }
+}
+
+var wantRx = regexp.MustCompile("does not (implement|satisfy)")
diff --git a/devtools/checklib.sh b/devtools/checklib.sh
new file mode 100644
index 0000000..0b6095e
--- /dev/null
+++ b/devtools/checklib.sh
@@ -0,0 +1,73 @@
+# Copyright 2021 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# Library of useful bash functions and variables for presubmit checks.
+
+RED=; GREEN=; YELLOW=; NORMAL=;
+MAXWIDTH=0
+
+if tput setaf 1 >& /dev/null; then
+ RED=`tput setaf 1`
+ GREEN=`tput setaf 2`
+ YELLOW=`tput setaf 3`
+ NORMAL=`tput sgr0`
+ MAXWIDTH=$(( $(tput cols) - 2 ))
+fi
+
+EXIT_CODE=0
+
+info() { echo -e "${GREEN}$@${NORMAL}" 1>&2; }
+warn() { echo -e "${YELLOW}$@${NORMAL}" 1>&2; }
+err() { echo -e "${RED}$@${NORMAL}" 1>&2; EXIT_CODE=1; }
+
+die() {
+ err $@
+ exit 1
+}
+
+dryrun=false
+
+# runcmd prints an info log describing the command that is about to be run, and
+# then runs it. It sets EXIT_CODE to non-zero if the command fails, but does not exit
+# the script.
+runcmd() {
+ msg="$@"
+ if $dryrun; then
+ echo -e "${YELLOW}dryrun${GREEN}\$ $msg${NORMAL}"
+ return 0
+ fi
+ # Truncate command logging for narrow terminals.
+ # Account for the 2 characters of '$ '.
+ if [[ $MAXWIDTH -gt 0 && ${#msg} -gt $MAXWIDTH ]]; then
+ msg="${msg::$(( MAXWIDTH - 3 ))}..."
+ fi
+
+ echo -e "$@\n" 1>&2;
+ $@ || err "command failed"
+}
+
+# check_header checks that all given files contain the standard header for Go
+# projects.
+check_header() {
+ if [[ "$@" != "" ]]; then
+ for FILE in $@
+ do
+ line="$(head -4 $FILE)"
+ if [[ ! $line == *"The Go Authors. All rights reserved."* ]] &&
+ [[ ! $line == "// DO NOT EDIT. This file was copied from" ]]; then
+ err "missing license header: $FILE"
+ fi
+ done
+ fi
+}
+
+# ensure_go_binary verifies that a binary exists in $PATH corresponding to the
+# given go-gettable URI. If no such binary exists, it is fetched via `go install`.
+ensure_go_binary() {
+ local binary=$(basename $1)
+ if ! [ -x "$(command -v $binary)" ]; then
+ info "Installing: $1"
+ go install $1
+ fi
+}
diff --git a/ebnf/ebnf.go b/ebnf/ebnf.go
new file mode 100644
index 0000000..a4e20f0
--- /dev/null
+++ b/ebnf/ebnf.go
@@ -0,0 +1,267 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ebnf is a library for EBNF grammars. The input is text ([]byte)
+// satisfying the following grammar (represented itself in EBNF):
+//
+// Production = name "=" [ Expression ] "." .
+// Expression = Alternative { "|" Alternative } .
+// Alternative = Term { Term } .
+// Term = name | token [ "…" token ] | Group | Option | Repetition .
+// Group = "(" Expression ")" .
+// Option = "[" Expression "]" .
+// Repetition = "{" Expression "}" .
+//
+// A name is a Go identifier, a token is a Go string, and comments
+// and white space follow the same rules as for the Go language.
+// Production names starting with an uppercase Unicode letter denote
+// non-terminal productions (i.e., productions which allow white-space
+// and comments between tokens); all other production names denote
+// lexical productions.
+package ebnf // import "golang.org/x/exp/ebnf"
+
+import (
+ "errors"
+ "fmt"
+ "text/scanner"
+ "unicode"
+ "unicode/utf8"
+)
+
+// ----------------------------------------------------------------------------
+// Error handling
+
+type errorList []error
+
+func (list errorList) Err() error {
+ if len(list) == 0 {
+ return nil
+ }
+ return list
+}
+
+func (list errorList) Error() string {
+ switch len(list) {
+ case 0:
+ return "no errors"
+ case 1:
+ return list[0].Error()
+ }
+ return fmt.Sprintf("%s (and %d more errors)", list[0], len(list)-1)
+}
+
+func newError(pos scanner.Position, msg string) error {
+ return errors.New(fmt.Sprintf("%s: %s", pos, msg))
+}
+
+// ----------------------------------------------------------------------------
+// Internal representation
+
+type (
+ // An Expression node represents a production expression.
+ Expression interface {
+ // Pos is the position of the first character of the syntactic construct
+ Pos() scanner.Position
+ }
+
+ // An Alternative node represents a non-empty list of alternative expressions.
+ Alternative []Expression // x | y | z
+
+ // A Sequence node represents a non-empty list of sequential expressions.
+ Sequence []Expression // x y z
+
+ // A Name node represents a production name.
+ Name struct {
+ StringPos scanner.Position
+ String string
+ }
+
+ // A Token node represents a literal.
+ Token struct {
+ StringPos scanner.Position
+ String string
+ }
+
+ // A List node represents a range of characters.
+ Range struct {
+ Begin, End *Token // begin ... end
+ }
+
+ // A Group node represents a grouped expression.
+ Group struct {
+ Lparen scanner.Position
+ Body Expression // (body)
+ }
+
+ // An Option node represents an optional expression.
+ Option struct {
+ Lbrack scanner.Position
+ Body Expression // [body]
+ }
+
+ // A Repetition node represents a repeated expression.
+ Repetition struct {
+ Lbrace scanner.Position
+ Body Expression // {body}
+ }
+
+ // A Production node represents an EBNF production.
+ Production struct {
+ Name *Name
+ Expr Expression
+ }
+
+ // A Bad node stands for pieces of source code that lead to a parse error.
+ Bad struct {
+ TokPos scanner.Position
+ Error string // parser error message
+ }
+
+ // A Grammar is a set of EBNF productions. The map
+ // is indexed by production name.
+ //
+ Grammar map[string]*Production
+)
+
+func (x Alternative) Pos() scanner.Position { return x[0].Pos() } // the parser always generates non-empty Alternative
+func (x Sequence) Pos() scanner.Position { return x[0].Pos() } // the parser always generates non-empty Sequences
+func (x *Name) Pos() scanner.Position { return x.StringPos }
+func (x *Token) Pos() scanner.Position { return x.StringPos }
+func (x *Range) Pos() scanner.Position { return x.Begin.Pos() }
+func (x *Group) Pos() scanner.Position { return x.Lparen }
+func (x *Option) Pos() scanner.Position { return x.Lbrack }
+func (x *Repetition) Pos() scanner.Position { return x.Lbrace }
+func (x *Production) Pos() scanner.Position { return x.Name.Pos() }
+func (x *Bad) Pos() scanner.Position { return x.TokPos }
+
+// ----------------------------------------------------------------------------
+// Grammar verification
+
+func isLexical(name string) bool {
+ ch, _ := utf8.DecodeRuneInString(name)
+ return !unicode.IsUpper(ch)
+}
+
+type verifier struct {
+ errors errorList
+ worklist []*Production
+ reached Grammar // set of productions reached from (and including) the root production
+ grammar Grammar
+}
+
+func (v *verifier) error(pos scanner.Position, msg string) {
+ v.errors = append(v.errors, newError(pos, msg))
+}
+
+func (v *verifier) push(prod *Production) {
+ name := prod.Name.String
+ if _, found := v.reached[name]; !found {
+ v.worklist = append(v.worklist, prod)
+ v.reached[name] = prod
+ }
+}
+
+func (v *verifier) verifyChar(x *Token) rune {
+ s := x.String
+ if utf8.RuneCountInString(s) != 1 {
+ v.error(x.Pos(), "single char expected, found "+s)
+ return 0
+ }
+ ch, _ := utf8.DecodeRuneInString(s)
+ return ch
+}
+
+func (v *verifier) verifyExpr(expr Expression, lexical bool) {
+ switch x := expr.(type) {
+ case nil:
+ // empty expression
+ case Alternative:
+ for _, e := range x {
+ v.verifyExpr(e, lexical)
+ }
+ case Sequence:
+ for _, e := range x {
+ v.verifyExpr(e, lexical)
+ }
+ case *Name:
+ // a production with this name must exist;
+ // add it to the worklist if not yet processed
+ if prod, found := v.grammar[x.String]; found {
+ v.push(prod)
+ } else {
+ v.error(x.Pos(), "missing production "+x.String)
+ }
+ // within a lexical production references
+ // to non-lexical productions are invalid
+ if lexical && !isLexical(x.String) {
+ v.error(x.Pos(), "reference to non-lexical production "+x.String)
+ }
+ case *Token:
+ // nothing to do for now
+ case *Range:
+ i := v.verifyChar(x.Begin)
+ j := v.verifyChar(x.End)
+ if i >= j {
+ v.error(x.Pos(), "decreasing character range")
+ }
+ case *Group:
+ v.verifyExpr(x.Body, lexical)
+ case *Option:
+ v.verifyExpr(x.Body, lexical)
+ case *Repetition:
+ v.verifyExpr(x.Body, lexical)
+ case *Bad:
+ v.error(x.Pos(), x.Error)
+ default:
+ panic(fmt.Sprintf("internal error: unexpected type %T", expr))
+ }
+}
+
+func (v *verifier) verify(grammar Grammar, start string) {
+ // find root production
+ root, found := grammar[start]
+ if !found {
+ var noPos scanner.Position
+ v.error(noPos, "no start production "+start)
+ return
+ }
+
+ // initialize verifier
+ v.worklist = v.worklist[0:0]
+ v.reached = make(Grammar)
+ v.grammar = grammar
+
+ // work through the worklist
+ v.push(root)
+ for {
+ n := len(v.worklist) - 1
+ if n < 0 {
+ break
+ }
+ prod := v.worklist[n]
+ v.worklist = v.worklist[0:n]
+ v.verifyExpr(prod.Expr, isLexical(prod.Name.String))
+ }
+
+ // check if all productions were reached
+ if len(v.reached) < len(v.grammar) {
+ for name, prod := range v.grammar {
+ if _, found := v.reached[name]; !found {
+ v.error(prod.Pos(), name+" is unreachable")
+ }
+ }
+ }
+}
+
+// Verify checks that:
+// - all productions used are defined
+// - all productions defined are used when beginning at start
+// - lexical productions refer only to other lexical productions
+//
+// Position information is interpreted relative to the file set fset.
+func Verify(grammar Grammar, start string) error {
+ var v verifier
+ v.verify(grammar, start)
+ return v.errors.Err()
+}
diff --git a/ebnf/ebnf_test.go b/ebnf/ebnf_test.go
new file mode 100644
index 0000000..8219986
--- /dev/null
+++ b/ebnf/ebnf_test.go
@@ -0,0 +1,73 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ebnf
+
+import (
+ "bytes"
+ "testing"
+)
+
+var goodGrammars = []string{
+ `Program = .`,
+
+ `Program = foo .
+ foo = "foo" .`,
+
+ `Program = "a" | "b" "c" .`,
+
+ `Program = "a" … "z" .`,
+
+ `Program = Song .
+ Song = { Note } .
+ Note = Do | (Re | Mi | Fa | So | La) | Ti .
+ Do = "c" .
+ Re = "d" .
+ Mi = "e" .
+ Fa = "f" .
+ So = "g" .
+ La = "a" .
+ Ti = ti .
+ ti = "b" .`,
+
+ "Program = `\"` .",
+}
+
+var badGrammars = []string{
+ `Program = | .`,
+ `Program = | b .`,
+ `Program = a … b .`,
+ `Program = "a" … .`,
+ `Program = … "b" .`,
+ `Program = () .`,
+ `Program = [] .`,
+ `Program = {} .`,
+}
+
+func checkGood(t *testing.T, src string) {
+ grammar, err := Parse("", bytes.NewBuffer([]byte(src)))
+ if err != nil {
+ t.Errorf("Parse(%s) failed: %v", src, err)
+ return
+ }
+ if err = Verify(grammar, "Program"); err != nil {
+ t.Errorf("Verify(%s) failed: %v", src, err)
+ }
+}
+
+func checkBad(t *testing.T, src string) {
+ _, err := Parse("", bytes.NewBuffer([]byte(src)))
+ if err == nil {
+ t.Errorf("Parse(%s) should have failed", src)
+ }
+}
+
+func TestGrammars(t *testing.T) {
+ for _, src := range goodGrammars {
+ checkGood(t, src)
+ }
+ for _, src := range badGrammars {
+ checkBad(t, src)
+ }
+}
diff --git a/ebnf/parser.go b/ebnf/parser.go
new file mode 100644
index 0000000..1551a8b
--- /dev/null
+++ b/ebnf/parser.go
@@ -0,0 +1,189 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ebnf
+
+import (
+ "io"
+ "strconv"
+ "text/scanner"
+)
+
+type parser struct {
+ errors errorList
+ scanner scanner.Scanner
+ pos scanner.Position // token position
+ tok rune // one token look-ahead
+ lit string // token literal
+}
+
+func (p *parser) next() {
+ p.tok = p.scanner.Scan()
+ p.pos = p.scanner.Position
+ p.lit = p.scanner.TokenText()
+}
+
+func (p *parser) error(pos scanner.Position, msg string) {
+ p.errors = append(p.errors, newError(pos, msg))
+}
+
+func (p *parser) errorExpected(pos scanner.Position, msg string) {
+ msg = `expected "` + msg + `"`
+ if pos.Offset == p.pos.Offset {
+ // the error happened at the current position;
+ // make the error message more specific
+ msg += ", found " + scanner.TokenString(p.tok)
+ if p.tok < 0 {
+ msg += " " + p.lit
+ }
+ }
+ p.error(pos, msg)
+}
+
+func (p *parser) expect(tok rune) scanner.Position {
+ pos := p.pos
+ if p.tok != tok {
+ p.errorExpected(pos, scanner.TokenString(tok))
+ }
+ p.next() // make progress in any case
+ return pos
+}
+
+func (p *parser) parseIdentifier() *Name {
+ pos := p.pos
+ name := p.lit
+ p.expect(scanner.Ident)
+ return &Name{pos, name}
+}
+
+func (p *parser) parseToken() *Token {
+ pos := p.pos
+ value := ""
+ if p.tok == scanner.String || p.tok == scanner.RawString {
+ value, _ = strconv.Unquote(p.lit)
+ // Unquote may fail with an error, but only if the scanner found
+ // an illegal string in the first place. In this case the error
+ // has already been reported.
+ p.next()
+ } else {
+ p.expect(scanner.String)
+ }
+ return &Token{pos, value}
+}
+
+// parseTerm returns nil if no term was found.
+func (p *parser) parseTerm() (x Expression) {
+ pos := p.pos
+
+ switch p.tok {
+ case scanner.Ident:
+ x = p.parseIdentifier()
+
+ case scanner.String, scanner.RawString:
+ tok := p.parseToken()
+ x = tok
+ const ellipsis = '…' // U+2026, the horizontal ellipsis character
+ if p.tok == ellipsis {
+ p.next()
+ x = &Range{tok, p.parseToken()}
+ }
+
+ case '(':
+ p.next()
+ x = &Group{pos, p.parseExpression()}
+ p.expect(')')
+
+ case '[':
+ p.next()
+ x = &Option{pos, p.parseExpression()}
+ p.expect(']')
+
+ case '{':
+ p.next()
+ x = &Repetition{pos, p.parseExpression()}
+ p.expect('}')
+ }
+
+ return x
+}
+
+func (p *parser) parseSequence() Expression {
+ var list Sequence
+
+ for x := p.parseTerm(); x != nil; x = p.parseTerm() {
+ list = append(list, x)
+ }
+
+ // no need for a sequence if list.Len() < 2
+ switch len(list) {
+ case 0:
+ p.errorExpected(p.pos, "term")
+ return &Bad{p.pos, "term expected"}
+ case 1:
+ return list[0]
+ }
+
+ return list
+}
+
+func (p *parser) parseExpression() Expression {
+ var list Alternative
+
+ for {
+ list = append(list, p.parseSequence())
+ if p.tok != '|' {
+ break
+ }
+ p.next()
+ }
+ // len(list) > 0
+
+ // no need for an Alternative node if list.Len() < 2
+ if len(list) == 1 {
+ return list[0]
+ }
+
+ return list
+}
+
+func (p *parser) parseProduction() *Production {
+ name := p.parseIdentifier()
+ p.expect('=')
+ var expr Expression
+ if p.tok != '.' {
+ expr = p.parseExpression()
+ }
+ p.expect('.')
+ return &Production{name, expr}
+}
+
+func (p *parser) parse(filename string, src io.Reader) Grammar {
+ p.scanner.Init(src)
+ p.scanner.Filename = filename
+ p.next() // initializes pos, tok, lit
+
+ grammar := make(Grammar)
+ for p.tok != scanner.EOF {
+ prod := p.parseProduction()
+ name := prod.Name.String
+ if _, found := grammar[name]; !found {
+ grammar[name] = prod
+ } else {
+ p.error(prod.Pos(), name+" declared already")
+ }
+ }
+
+ return grammar
+}
+
+// Parse parses a set of EBNF productions from source src.
+// It returns a set of productions. Errors are reported
+// for incorrect syntax and if a production is declared
+// more than once; the filename is used only for error
+// positions.
+func Parse(filename string, src io.Reader) (Grammar, error) {
+ var p parser
+ grammar := p.parse(filename, src)
+ return grammar, p.errors.Err()
+}
diff --git a/ebnflint/doc.go b/ebnflint/doc.go
new file mode 100644
index 0000000..79d3a66
--- /dev/null
+++ b/ebnflint/doc.go
@@ -0,0 +1,21 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Ebnflint verifies that EBNF productions are consistent and grammatically correct.
+It reads them from an HTML document such as the Go specification.
+
+Grammar productions are grouped in boxes demarcated by the HTML elements
+
+ <pre class="ebnf">
+ </pre>
+
+Usage:
+
+ go tool ebnflint [--start production] [file]
+
+The --start flag specifies the name of the start production for
+the grammar; it defaults to "Start".
+*/
+package main // import "golang.org/x/exp/ebnflint"
diff --git a/ebnflint/ebnflint.go b/ebnflint/ebnflint.go
new file mode 100644
index 0000000..966dadf
--- /dev/null
+++ b/ebnflint/ebnflint.go
@@ -0,0 +1,122 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/scanner"
+ "go/token"
+ "io"
+ "os"
+ "path/filepath"
+
+ "golang.org/x/exp/ebnf"
+)
+
+var fset = token.NewFileSet()
+var start = flag.String("start", "Start", "name of start production")
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: go tool ebnflint [flags] [filename]\n")
+ flag.PrintDefaults()
+ os.Exit(1)
+}
+
+// Markers around EBNF sections in .html files
+var (
+ open = []byte(`<pre class="ebnf">`)
+ close = []byte(`</pre>`)
+)
+
+func report(err error) {
+ scanner.PrintError(os.Stderr, err)
+ os.Exit(1)
+}
+
+func extractEBNF(src []byte) []byte {
+ var buf bytes.Buffer
+
+ for {
+ // i = beginning of EBNF text
+ i := bytes.Index(src, open)
+ if i < 0 {
+ break // no EBNF found - we are done
+ }
+ i += len(open)
+
+ // write as many newlines as found in the excluded text
+ // to maintain correct line numbers in error messages
+ for _, ch := range src[0:i] {
+ if ch == '\n' {
+ buf.WriteByte('\n')
+ }
+ }
+
+ // j = end of EBNF text (or end of source)
+ j := bytes.Index(src[i:], close) // close marker
+ if j < 0 {
+ j = len(src) - i
+ }
+ j += i
+
+ // copy EBNF text
+ buf.Write(src[i:j])
+
+ // advance
+ src = src[j:]
+ }
+
+ return buf.Bytes()
+}
+
+func main() {
+ flag.Parse()
+
+ var (
+ name string
+ r io.Reader
+ )
+ switch flag.NArg() {
+ case 0:
+ name, r = "<stdin>", os.Stdin
+ case 1:
+ name = flag.Arg(0)
+ default:
+ usage()
+ }
+
+ if err := verify(name, *start, r); err != nil {
+ report(err)
+ }
+}
+
+func verify(name, start string, r io.Reader) error {
+ if r == nil {
+ f, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ r = f
+ }
+
+ src, err := io.ReadAll(r)
+ if err != nil {
+ return err
+ }
+
+ if filepath.Ext(name) == ".html" || bytes.Index(src, open) >= 0 {
+ src = extractEBNF(src)
+ }
+
+ grammar, err := ebnf.Parse(name, bytes.NewBuffer(src))
+ if err != nil {
+ return err
+ }
+
+ return ebnf.Verify(grammar, start)
+}
diff --git a/ebnflint/ebnflint_test.go b/ebnflint/ebnflint_test.go
new file mode 100644
index 0000000..91fb363
--- /dev/null
+++ b/ebnflint/ebnflint_test.go
@@ -0,0 +1,22 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "os"
+ "runtime"
+ "testing"
+)
+
+func TestSpec(t *testing.T) {
+ if err := verify(runtime.GOROOT()+"/doc/go_spec.html", "SourceFile", nil); err != nil {
+ if os.IsNotExist(err) {
+ // Couldn't find/open the file - skip test rather than
+ // complain since not all builders copy the spec.
+ t.Skip("spec file not found")
+ }
+ t.Fatal(err)
+ }
+}
diff --git a/errors/errors.go b/errors/errors.go
new file mode 100644
index 0000000..bcd705d
--- /dev/null
+++ b/errors/errors.go
@@ -0,0 +1,36 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package errors implements functions to manipulate errors.
+//
+// This package implements the Go 2 draft designs for error inspection and printing:
+//
+// https://go.googlesource.com/proposal/+/master/design/go2draft.md
+//
+// This is an EXPERIMENTAL package, and may change in arbitrary ways without notice.
+package errors
+
+// errorString is a trivial implementation of error.
+type errorString struct {
+ s string
+ frame Frame
+}
+
+// New returns an error that formats as the given text.
+//
+// The returned error embeds a Frame set to the caller's location and implements
+// Formatter to show this information when printed with details.
+func New(text string) error {
+ return &errorString{text, Caller(1)}
+}
+
+func (e *errorString) Error() string {
+ return e.s
+}
+
+func (e *errorString) FormatError(p Printer) (next error) {
+ p.Print(e.s)
+ e.frame.Format(p)
+ return nil
+}
diff --git a/errors/errors_test.go b/errors/errors_test.go
new file mode 100644
index 0000000..6c0d793
--- /dev/null
+++ b/errors/errors_test.go
@@ -0,0 +1,54 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+ "fmt"
+ "testing"
+
+ "golang.org/x/exp/errors"
+)
+
+func TestNewEqual(t *testing.T) {
+ // Different allocations should not be equal.
+ if errors.New("abc") == errors.New("abc") {
+ t.Errorf(`New("abc") == New("abc")`)
+ }
+ if errors.New("abc") == errors.New("xyz") {
+ t.Errorf(`New("abc") == New("xyz")`)
+ }
+
+ // Same allocation should be equal to itself (not crash).
+ err := errors.New("jkl")
+ if err != err {
+ t.Errorf(`err != err`)
+ }
+}
+
+func TestErrorMethod(t *testing.T) {
+ err := errors.New("abc")
+ if err.Error() != "abc" {
+ t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
+ }
+}
+
+func ExampleNew() {
+ err := errors.New("emit macho dwarf: elf header corrupted")
+ if err != nil {
+ fmt.Print(err)
+ }
+ // Output: emit macho dwarf: elf header corrupted
+}
+
+// The fmt package's Errorf function lets us use the package's formatting
+// features to create descriptive error messages.
+func ExampleNew_errorf() {
+ const name, id = "bimmler", 17
+ err := fmt.Errorf("user %q (id %d) not found", name, id)
+ if err != nil {
+ fmt.Print(err)
+ }
+ // Output: user "bimmler" (id 17) not found
+}
diff --git a/errors/example_test.go b/errors/example_test.go
new file mode 100644
index 0000000..e3b07cf
--- /dev/null
+++ b/errors/example_test.go
@@ -0,0 +1,50 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "golang.org/x/exp/errors"
+)
+
+// MyError is an error implementation that includes a time and message.
+type MyError struct {
+ When time.Time
+ What string
+}
+
+func (e MyError) Error() string {
+ return fmt.Sprintf("%v: %v", e.When, e.What)
+}
+
+func oops() error {
+ return MyError{
+ time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
+ "the file system has gone away",
+ }
+}
+
+func Example() {
+ if err := oops(); err != nil {
+ fmt.Println(err)
+ }
+ // Output: 1989-03-15 22:30:00 +0000 UTC: the file system has gone away
+}
+
+func ExampleAs() {
+ _, err := os.Open("non-existing")
+ if err != nil {
+ var pathError *os.PathError
+ if errors.As(err, &pathError) {
+ fmt.Println("Failed at path:", pathError.Path)
+ }
+ }
+
+ // Output:
+ // Failed at path: non-existing
+}
diff --git a/errors/fmt/adaptor.go b/errors/fmt/adaptor.go
new file mode 100644
index 0000000..2da6a9d
--- /dev/null
+++ b/errors/fmt/adaptor.go
@@ -0,0 +1,31 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+import "golang.org/x/exp/errors"
+
+// The functionality in this file is to provide adaptors only. It will not
+// be included in the standard library.
+
+// FormatError calls the FormatError method of err with a errors.Printer
+// configured according to s and verb and writes the result to s.
+func FormatError(s State, verb rune, err errors.Formatter) {
+ // Assuming this function is only called from the Format method, and given
+ // that FormatError takes precedence over Format, it cannot be called from
+ // any package that supports errors.Formatter. It is therefore safe to
+ // disregard that State may be a specific printer implementation and use one
+ // of our choice instead.
+ p := newPrinter()
+ if verb == 'v' {
+ if s.Flag('#') {
+ p.fmt.sharpV = true
+ }
+ if s.Flag('+') {
+ p.fmt.plusV = true
+ }
+ }
+ fmtError(p, verb, err)
+ s.Write(p.buf)
+}
diff --git a/errors/fmt/doc.go b/errors/fmt/doc.go
new file mode 100644
index 0000000..56bd157
--- /dev/null
+++ b/errors/fmt/doc.go
@@ -0,0 +1,380 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package fmt implements formatted I/O with functions analogous
+to C's printf and scanf. The format 'verbs' are derived from C's but
+are simpler.
+
+# Printing
+
+The verbs:
+
+General:
+
+ %v the value in a default format
+ when printing structs, the plus flag (%+v) adds field names
+ %#v a Go-syntax representation of the value
+ %T a Go-syntax representation of the type of the value
+ %% a literal percent sign; consumes no value
+
+Boolean:
+
+ %t the word true or false
+
+Integer:
+
+ %b base 2
+ %c the character represented by the corresponding Unicode code point
+ %d base 10
+ %o base 8
+ %q a single-quoted character literal safely escaped with Go syntax.
+ %x base 16, with lower-case letters for a-f
+ %X base 16, with upper-case letters for A-F
+ %U Unicode format: U+1234; same as "U+%04X"
+
+Floating-point and complex constituents:
+
+ %b decimalless scientific notation with exponent a power of two,
+ in the manner of strconv.FormatFloat with the 'b' format,
+ e.g. -123456p-78
+ %e scientific notation, e.g. -1.234456e+78
+ %E scientific notation, e.g. -1.234456E+78
+ %f decimal point but no exponent, e.g. 123.456
+ %F synonym for %f
+ %g %e for large exponents, %f otherwise. Precision is discussed below.
+ %G %E for large exponents, %F otherwise
+
+String and slice of bytes (treated equivalently with these verbs):
+
+ %s the uninterpreted bytes of the string or slice
+ %q a double-quoted string safely escaped with Go syntax
+ %x base 16, lower-case, two characters per byte
+ %X base 16, upper-case, two characters per byte
+
+Slice:
+
+ %p address of 0th element in base 16 notation, with leading 0x
+
+Pointer:
+
+ %p base 16 notation, with leading 0x
+ The %b, %d, %o, %x and %X verbs also work with pointers,
+ formatting the value exactly as if it were an integer.
+
+The default format for %v is:
+
+ bool: %t
+ int, int8 etc.: %d
+ uint, uint8 etc.: %d, %#x if printed with %#v
+ float32, complex64, etc: %g
+ string: %s
+ chan: %p
+ pointer: %p
+
+For compound objects, the elements are printed using these rules, recursively,
+laid out like this:
+
+ struct: {field0 field1 ...}
+ array, slice: [elem0 elem1 ...]
+ maps: map[key1:value1 key2:value2 ...]
+ pointer to above: &{}, &[], &map[]
+
+Width is specified by an optional decimal number immediately preceding the verb.
+If absent, the width is whatever is necessary to represent the value.
+Precision is specified after the (optional) width by a period followed by a
+decimal number. If no period is present, a default precision is used.
+A period with no following number specifies a precision of zero.
+Examples:
+
+ %f default width, default precision
+ %9f width 9, default precision
+ %.2f default width, precision 2
+ %9.2f width 9, precision 2
+ %9.f width 9, precision 0
+
+Width and precision are measured in units of Unicode code points,
+that is, runes. (This differs from C's printf where the
+units are always measured in bytes.) Either or both of the flags
+may be replaced with the character '*', causing their values to be
+obtained from the next operand (preceding the one to format),
+which must be of type int.
+
+For most values, width is the minimum number of runes to output,
+padding the formatted form with spaces if necessary.
+
+For strings, byte slices and byte arrays, however, precision
+limits the length of the input to be formatted (not the size of
+the output), truncating if necessary. Normally it is measured in
+runes, but for these types when formatted with the %x or %X format
+it is measured in bytes.
+
+For floating-point values, width sets the minimum width of the field and
+precision sets the number of places after the decimal, if appropriate,
+except that for %g/%G precision sets the maximum number of significant
+digits (trailing zeros are removed). For example, given 12.345 the format
+%6.3f prints 12.345 while %.3g prints 12.3. The default precision for %e, %f
+and %#g is 6; for %g it is the smallest number of digits necessary to identify
+the value uniquely.
+
+For complex numbers, the width and precision apply to the two
+components independently and the result is parenthesized, so %f applied
+to 1.2+3.4i produces (1.200000+3.400000i).
+
+Other flags:
+ - always print a sign for numeric values;
+ guarantee ASCII-only output for %q (%+q)
+ - pad with spaces on the right rather than the left (left-justify the field)
+ # alternate format: add leading 0 for octal (%#o), 0x for hex (%#x);
+ 0X for hex (%#X); suppress 0x for %p (%#p);
+ for %q, print a raw (backquoted) string if strconv.CanBackquote
+ returns true;
+ always print a decimal point for %e, %E, %f, %F, %g and %G;
+ do not remove trailing zeros for %g and %G;
+ write e.g. U+0078 'x' if the character is printable for %U (%#U).
+ ' ' (space) leave a space for elided sign in numbers (% d);
+ put spaces between bytes printing strings or slices in hex (% x, % X)
+ 0 pad with leading zeros rather than spaces;
+ for numbers, this moves the padding after the sign
+
+Flags are ignored by verbs that do not expect them.
+For example there is no alternate decimal format, so %#d and %d
+behave identically.
+
+For each Printf-like function, there is also a Print function
+that takes no format and is equivalent to saying %v for every
+operand. Another variant Println inserts blanks between
+operands and appends a newline.
+
+Regardless of the verb, if an operand is an interface value,
+the internal concrete value is used, not the interface itself.
+Thus:
+
+ var i interface{} = 23
+ fmt.Printf("%v\n", i)
+
+will print 23.
+
+Except when printed using the verbs %T and %p, special
+formatting considerations apply for operands that implement
+certain interfaces. In order of application:
+
+1. If the operand is a reflect.Value, the operand is replaced by the
+concrete value that it holds, and printing continues with the next rule.
+
+2. If an operand implements the Formatter interface, and not
+errors.Formatter, it will be invoked. Formatter provides fine
+control of formatting.
+
+3. If the %v verb is used with the # flag (%#v) and the operand
+implements the GoStringer interface, that will be invoked.
+
+If the format (which is implicitly %v for Println etc.) is valid
+for a string (%s %q %v %x %X), the following three rules apply:
+
+4. If an operand implements errors.Formatter, the FormatError
+method will be invoked with an errors.Printer to print the error.
+If the %v flag is used with the + flag (%+v), the Detail method
+of the Printer will return true and the error will be formatted
+as a detailed error message. Otherwise the printed string will
+be formatted as required by the verb (if any).
+
+5. If an operand implements the error interface, the Error method
+will be invoked to convert the object to a string, which will then
+be formatted as required by the verb (if any).
+
+6. If an operand implements method String() string, that method
+will be invoked to convert the object to a string, which will then
+be formatted as required by the verb (if any).
+
+For compound operands such as slices and structs, the format
+applies to the elements of each operand, recursively, not to the
+operand as a whole. Thus %q will quote each element of a slice
+of strings, and %6.2f will control formatting for each element
+of a floating-point array.
+
+However, when printing a byte slice with a string-like verb
+(%s %q %x %X), it is treated identically to a string, as a single item.
+
+To avoid recursion in cases such as
+
+ type X string
+ func (x X) String() string { return Sprintf("<%s>", x) }
+
+convert the value before recurring:
+
+ func (x X) String() string { return Sprintf("<%s>", string(x)) }
+
+Infinite recursion can also be triggered by self-referential data
+structures, such as a slice that contains itself as an element, if
+that type has a String method. Such pathologies are rare, however,
+and the package does not protect against them.
+
+When printing a struct, fmt cannot and therefore does not invoke
+formatting methods such as Error or String on unexported fields.
+
+Explicit argument indexes:
+
+In Printf, Sprintf, and Fprintf, the default behavior is for each
+formatting verb to format successive arguments passed in the call.
+However, the notation [n] immediately before the verb indicates that the
+nth one-indexed argument is to be formatted instead. The same notation
+before a '*' for a width or precision selects the argument index holding
+the value. After processing a bracketed expression [n], subsequent verbs
+will use arguments n+1, n+2, etc. unless otherwise directed.
+
+For example,
+
+ fmt.Sprintf("%[2]d %[1]d\n", 11, 22)
+
+will yield "22 11", while
+
+ fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6)
+
+equivalent to
+
+ fmt.Sprintf("%6.2f", 12.0)
+
+will yield " 12.00". Because an explicit index affects subsequent verbs,
+this notation can be used to print the same values multiple times
+by resetting the index for the first argument to be repeated:
+
+ fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
+
+will yield "16 17 0x10 0x11".
+
+Format errors:
+
+If an invalid argument is given for a verb, such as providing
+a string to %d, the generated string will contain a
+description of the problem, as in these examples:
+
+ Wrong type or unknown verb: %!verb(type=value)
+ Printf("%d", hi): %!d(string=hi)
+ Too many arguments: %!(EXTRA type=value)
+ Printf("hi", "guys"): hi%!(EXTRA string=guys)
+ Too few arguments: %!verb(MISSING)
+ Printf("hi%d"): hi%!d(MISSING)
+ Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC)
+ Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi
+ Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi
+ Invalid or invalid use of argument index: %!(BADINDEX)
+ Printf("%*[2]d", 7): %!d(BADINDEX)
+ Printf("%.[2]d", 7): %!d(BADINDEX)
+
+All errors begin with the string "%!" followed sometimes
+by a single character (the verb) and end with a parenthesized
+description.
+
+If an Error or String method triggers a panic when called by a
+print routine, the fmt package reformats the error message
+from the panic, decorating it with an indication that it came
+through the fmt package. For example, if a String method
+calls panic("bad"), the resulting formatted message will look
+like
+
+ %!s(PANIC=bad)
+
+The %!s just shows the print verb in use when the failure
+occurred. If the panic is caused by a nil receiver to an Error
+or String method, however, the output is the undecorated
+string, "<nil>".
+
+# Scanning
+
+An analogous set of functions scans formatted text to yield
+values. Scan, Scanf and Scanln read from os.Stdin; Fscan,
+Fscanf and Fscanln read from a specified io.Reader; Sscan,
+Sscanf and Sscanln read from an argument string.
+
+Scan, Fscan, Sscan treat newlines in the input as spaces.
+
+Scanln, Fscanln and Sscanln stop scanning at a newline and
+require that the items be followed by a newline or EOF.
+
+Scanf, Fscanf, and Sscanf parse the arguments according to a
+format string, analogous to that of Printf. In the text that
+follows, 'space' means any Unicode whitespace character
+except newline.
+
+In the format string, a verb introduced by the % character
+consumes and parses input; these verbs are described in more
+detail below. A character other than %, space, or newline in
+the format consumes exactly that input character, which must
+be present. A newline with zero or more spaces before it in
+the format string consumes zero or more spaces in the input
+followed by a single newline or the end of the input. A space
+following a newline in the format string consumes zero or more
+spaces in the input. Otherwise, any run of one or more spaces
+in the format string consumes as many spaces as possible in
+the input. Unless the run of spaces in the format string
+appears adjacent to a newline, the run must consume at least
+one space from the input or find the end of the input.
+
+The handling of spaces and newlines differs from that of C's
+scanf family: in C, newlines are treated as any other space,
+and it is never an error when a run of spaces in the format
+string finds no spaces to consume in the input.
+
+The verbs behave analogously to those of Printf.
+For example, %x will scan an integer as a hexadecimal number,
+and %v will scan the default representation format for the value.
+The Printf verbs %p and %T and the flags # and + are not implemented.
+The verbs %e %E %f %F %g and %G are all equivalent and scan any
+floating-point or complex value. For float and complex literals in
+scientific notation, both the decimal (e) and binary (p) exponent
+formats are supported (for example: "2.3e+7" and "4.5p-8").
+
+Input processed by verbs is implicitly space-delimited: the
+implementation of every verb except %c starts by discarding
+leading spaces from the remaining input, and the %s verb
+(and %v reading into a string) stops consuming input at the first
+space or newline character.
+
+The familiar base-setting prefixes 0 (octal) and 0x
+(hexadecimal) are accepted when scanning integers without
+a format or with the %v verb.
+
+Width is interpreted in the input text but there is no
+syntax for scanning with a precision (no %5.2f, just %5f).
+If width is provided, it applies after leading spaces are
+trimmed and specifies the maximum number of runes to read
+to satisfy the verb. For example,
+
+ Sscanf(" 1234567 ", "%5s%d", &s, &i)
+
+will set s to "12345" and i to 67 while
+
+ Sscanf(" 12 34 567 ", "%5s%d", &s, &i)
+
+will set s to "12" and i to 34.
+
+In all the scanning functions, a carriage return followed
+immediately by a newline is treated as a plain newline
+(\r\n means the same as \n).
+
+In all the scanning functions, if an operand implements method
+Scan (that is, it implements the Scanner interface) that
+method will be used to scan the text for that operand. Also,
+if the number of arguments scanned is less than the number of
+arguments provided, an error is returned.
+
+All arguments to be scanned must be either pointers to basic
+types or implementations of the Scanner interface.
+
+Like Scanf and Fscanf, Sscanf need not consume its entire input.
+There is no way to recover how much of the input string Sscanf used.
+
+Note: Fscan etc. can read one character (rune) past the input
+they return, which means that a loop calling a scan routine
+may skip some of the input. This is usually a problem only
+when there is no space between input values. If the reader
+provided to Fscan implements ReadRune, that method will be used
+to read characters. If the reader also implements UnreadRune,
+that method will be used to save the character and successive
+calls will not lose data. To attach ReadRune and UnreadRune
+methods to a reader without that capability, use
+bufio.NewReader.
+*/
+package fmt
diff --git a/errors/fmt/errors.go b/errors/fmt/errors.go
new file mode 100644
index 0000000..f17ddb1
--- /dev/null
+++ b/errors/fmt/errors.go
@@ -0,0 +1,245 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+import (
+ "bytes"
+ "strings"
+
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/internal"
+)
+
+// Errorf formats according to a format specifier and returns the string as a
+// value that satisfies error.
+//
+// The returned error includes the file and line number of the caller when
+// formatted with additional detail enabled. If the last argument is an error
+// the returned error's Format method will return it if the format string ends
+// with ": %s", ": %v", or ": %w". If the last argument is an error and the
+// format string ends with ": %w", the returned error implements errors.Wrapper
+// with an Unwrap method returning it.
+func Errorf(format string, a ...interface{}) error {
+ err, wrap := lastError(format, a)
+ if err == nil {
+ return &noWrapError{Sprintf(format, a...), nil, errors.Caller(1)}
+ }
+
+ // TODO: this is not entirely correct. The error value could be
+ // printed elsewhere in format if it mixes numbered with unnumbered
+ // substitutions. With relatively small changes to doPrintf we can
+ // have it optionally ignore extra arguments and pass the argument
+ // list in its entirety.
+ msg := Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
+ frame := errors.Frame{}
+ if internal.EnableTrace {
+ frame = errors.Caller(1)
+ }
+ if wrap {
+ return &wrapError{msg, err, frame}
+ }
+ return &noWrapError{msg, err, frame}
+}
+
+func lastError(format string, a []interface{}) (err error, wrap bool) {
+ wrap = strings.HasSuffix(format, ": %w")
+ if !wrap &&
+ !strings.HasSuffix(format, ": %s") &&
+ !strings.HasSuffix(format, ": %v") {
+ return nil, false
+ }
+
+ if len(a) == 0 {
+ return nil, false
+ }
+
+ err, ok := a[len(a)-1].(error)
+ if !ok {
+ return nil, false
+ }
+
+ return err, wrap
+}
+
+type noWrapError struct {
+ msg string
+ err error
+ frame errors.Frame
+}
+
+func (e *noWrapError) Error() string {
+ return Sprint(e)
+}
+
+func (e *noWrapError) FormatError(p errors.Printer) (next error) {
+ p.Print(e.msg)
+ e.frame.Format(p)
+ return e.err
+}
+
+type wrapError struct {
+ msg string
+ err error
+ frame errors.Frame
+}
+
+func (e *wrapError) Error() string {
+ return Sprint(e)
+}
+
+func (e *wrapError) FormatError(p errors.Printer) (next error) {
+ p.Print(e.msg)
+ e.frame.Format(p)
+ return e.err
+}
+
+func (e *wrapError) Unwrap() error {
+ return e.err
+}
+
+func fmtError(p *pp, verb rune, err error) (handled bool) {
+ var (
+ sep = " " // separator before next error
+ w = p // print buffer where error text is written
+ )
+ switch {
+ // Note that this switch must match the preference order
+ // for ordinary string printing (%#v before %+v, and so on).
+
+ case p.fmt.sharpV:
+ if stringer, ok := p.arg.(GoStringer); ok {
+ // Print the result of GoString unadorned.
+ p.fmt.fmtS(stringer.GoString())
+ return true
+ }
+ return false
+
+ case p.fmt.plusV:
+ sep = "\n - "
+ w.fmt.fmtFlags = fmtFlags{plusV: p.fmt.plusV} // only keep detail flag
+
+ // The width or precision of a detailed view could be the number of
+ // errors to print from a list.
+
+ default:
+ // Use an intermediate buffer in the rare cases that precision,
+ // truncation, or one of the alternative verbs (q, x, and X) are
+ // specified.
+ switch verb {
+ case 's', 'v':
+ if (!w.fmt.widPresent || w.fmt.wid == 0) && !w.fmt.precPresent {
+ break
+ }
+ fallthrough
+ case 'q', 'x', 'X':
+ w = newPrinter()
+ defer w.free()
+ default:
+ w.badVerb(verb)
+ return true
+ }
+ }
+
+loop:
+ for {
+ w.fmt.inDetail = false
+ switch v := err.(type) {
+ case errors.Formatter:
+ err = v.FormatError((*errPP)(w))
+ case Formatter:
+ if w.fmt.plusV {
+ v.Format((*errPPState)(w), 'v') // indent new lines
+ } else {
+ v.Format(w, 'v') // do not indent new lines
+ }
+ break loop
+ default:
+ w.fmtString(v.Error(), 's')
+ break loop
+ }
+ if err == nil {
+ break
+ }
+ if !w.fmt.inDetail || !p.fmt.plusV {
+ w.buf.WriteByte(':')
+ }
+ // Strip last newline of detail.
+ if bytes.HasSuffix([]byte(w.buf), detailSep) {
+ w.buf = w.buf[:len(w.buf)-len(detailSep)]
+ }
+ w.buf.WriteString(sep)
+ w.fmt.inDetail = false
+ }
+
+ if w != p {
+ p.fmtString(string(w.buf), verb)
+ }
+ return true
+}
+
+var detailSep = []byte("\n ")
+
+// errPPState wraps a pp to implement State with indentation. It is used
+// for errors implementing fmt.Formatter.
+type errPPState pp
+
+func (p *errPPState) Width() (wid int, ok bool) { return (*pp)(p).Width() }
+func (p *errPPState) Precision() (prec int, ok bool) { return (*pp)(p).Precision() }
+func (p *errPPState) Flag(c int) bool { return (*pp)(p).Flag(c) }
+
+func (p *errPPState) Write(b []byte) (n int, err error) {
+ if !p.fmt.inDetail || p.fmt.plusV {
+ if len(b) == 0 {
+ return 0, nil
+ }
+ if p.fmt.inDetail && p.fmt.needNewline {
+ p.fmt.needNewline = false
+ p.buf.WriteByte(':')
+ p.buf.Write(detailSep)
+ if b[0] == '\n' {
+ b = b[1:]
+ }
+ }
+ k := 0
+ for i, c := range b {
+ if c == '\n' {
+ p.buf.Write(b[k:i])
+ p.buf.Write(detailSep)
+ k = i + 1
+ }
+ }
+ p.buf.Write(b[k:])
+ p.fmt.needNewline = !p.fmt.inDetail
+ }
+ return len(b), nil
+}
+
+// errPP wraps a pp to implement an errors.Printer.
+type errPP pp
+
+func (p *errPP) Print(args ...interface{}) {
+ if !p.fmt.inDetail || p.fmt.plusV {
+ if p.fmt.plusV {
+ Fprint((*errPPState)(p), args...)
+ } else {
+ (*pp)(p).doPrint(args)
+ }
+ }
+}
+
+func (p *errPP) Printf(format string, args ...interface{}) {
+ if !p.fmt.inDetail || p.fmt.plusV {
+ if p.fmt.plusV {
+ Fprintf((*errPPState)(p), format, args...)
+ } else {
+ (*pp)(p).doPrintf(format, args)
+ }
+ }
+}
+
+func (p *errPP) Detail() bool {
+ p.fmt.inDetail = true
+ return p.fmt.plusV
+}
diff --git a/errors/fmt/errors_test.go b/errors/fmt/errors_test.go
new file mode 100644
index 0000000..8c82389
--- /dev/null
+++ b/errors/fmt/errors_test.go
@@ -0,0 +1,535 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+ gofmt "fmt"
+ "io"
+ "os"
+ "path"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/fmt"
+)
+
+func TestErrorf(t *testing.T) {
+ chained := &wrapped{"chained", nil}
+ chain := func(s ...string) (a []string) {
+ for _, s := range s {
+ a = append(a, cleanPath(s))
+ }
+ return a
+ }
+ testCases := []struct {
+ got error
+ want []string
+ }{{
+ fmt.Errorf("no args"),
+ chain("no args/path.TestErrorf/path.go:xxx"),
+ }, {
+ fmt.Errorf("no args: %s"),
+ chain("no args: %!s(MISSING)/path.TestErrorf/path.go:xxx"),
+ }, {
+ fmt.Errorf("nounwrap: %s", "simple"),
+ chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
+ }, {
+ fmt.Errorf("nounwrap: %v", "simple"),
+ chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
+ }, {
+ fmt.Errorf("%s failed: %v", "foo", chained),
+ chain("foo failed/path.TestErrorf/path.go:xxx",
+ "chained/somefile.go:xxx"),
+ }, {
+ fmt.Errorf("no wrap: %s", chained),
+ chain("no wrap/path.TestErrorf/path.go:xxx",
+ "chained/somefile.go:xxx"),
+ }, {
+ fmt.Errorf("%s failed: %w", "foo", chained),
+ chain("wraps:foo failed/path.TestErrorf/path.go:xxx",
+ "chained/somefile.go:xxx"),
+ }, {
+ fmt.Errorf("nowrapv: %v", chained),
+ chain("nowrapv/path.TestErrorf/path.go:xxx",
+ "chained/somefile.go:xxx"),
+ }, {
+ fmt.Errorf("wrapw: %w", chained),
+ chain("wraps:wrapw/path.TestErrorf/path.go:xxx",
+ "chained/somefile.go:xxx"),
+ }, {
+ fmt.Errorf("not wrapped: %+v", chained),
+ chain("not wrapped: chained: somefile.go:123/path.TestErrorf/path.go:xxx"),
+ }}
+ for i, tc := range testCases {
+ t.Run(strconv.Itoa(i)+"/"+path.Join(tc.want...), func(t *testing.T) {
+ got := errToParts(tc.got)
+ if !reflect.DeepEqual(got, tc.want) {
+ t.Errorf("Format:\n got: %#v\nwant: %#v", got, tc.want)
+ }
+
+ gotStr := tc.got.Error()
+ wantStr := fmt.Sprint(tc.got)
+ if gotStr != wantStr {
+ t.Errorf("Error:\n got: %#v\nwant: %#v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestErrorFormatter(t *testing.T) {
+ var (
+ simple = &wrapped{"simple", nil}
+ elephant = &wrapped{
+ "can't adumbrate elephant",
+ detailed{},
+ }
+ nonascii = &wrapped{"cafΓ©", nil}
+ newline = &wrapped{"msg with\nnewline",
+ &wrapped{"and another\none", nil}}
+ fallback = &wrapped{"fallback", os.ErrNotExist}
+ oldAndNew = &wrapped{"new style", formatError("old style")}
+ framed = &withFrameAndMore{
+ frame: errors.Caller(0),
+ }
+ opaque = &wrapped{"outer",
+ errors.Opaque(&wrapped{"mid",
+ &wrapped{"inner", nil}})}
+ )
+ testCases := []struct {
+ err error
+ fmt string
+ want string
+ regexp bool
+ }{{
+ err: simple,
+ fmt: "%s",
+ want: "simple",
+ }, {
+ err: elephant,
+ fmt: "%s",
+ want: "can't adumbrate elephant: out of peanuts",
+ }, {
+ err: &wrapped{"a", &wrapped{"b", &wrapped{"c", nil}}},
+ fmt: "%s",
+ want: "a: b: c",
+ }, {
+ err: simple,
+ fmt: "%+v",
+ want: "simple:" +
+ "\n somefile.go:123",
+ }, {
+ err: elephant,
+ fmt: "%+v",
+ want: "can't adumbrate elephant:" +
+ "\n somefile.go:123" +
+ "\n - out of peanuts:" +
+ "\n the elephant is on strike" +
+ "\n and the 12 monkeys" +
+ "\n are laughing",
+ }, {
+ err: simple,
+ fmt: "%#v",
+ want: "&fmt_test.wrapped{msg:\"simple\", err:error(nil)}",
+ }, {
+ err: framed,
+ fmt: "%+v",
+ want: "something:" +
+ "\n golang.org/x/exp/errors/fmt_test.TestErrorFormatter" +
+ "\n .+/fmt/errors_test.go:98" +
+ "\n something more",
+ regexp: true,
+ }, {
+ err: fmtTwice("Hello World!"),
+ fmt: "%#v",
+ want: "2 times Hello World!",
+ }, {
+ err: fallback,
+ fmt: "%s",
+ want: "fallback: file does not exist",
+ }, {
+ err: fallback,
+ fmt: "%+v",
+ // Note: no colon after the last error, as there are no details.
+ want: "fallback:" +
+ "\n somefile.go:123" +
+ "\n - file does not exist",
+ }, {
+ err: opaque,
+ fmt: "%s",
+ want: "outer: mid: inner",
+ }, {
+ err: opaque,
+ fmt: "%+v",
+ want: "outer:" +
+ "\n somefile.go:123" +
+ "\n - mid:" +
+ "\n somefile.go:123" +
+ "\n - inner:" +
+ "\n somefile.go:123",
+ }, {
+ err: oldAndNew,
+ fmt: "%v",
+ want: "new style: old style",
+ }, {
+ err: oldAndNew,
+ fmt: "%q",
+ want: `"new style: old style"`,
+ }, {
+ err: oldAndNew,
+ fmt: "%+v",
+ // Note the extra indentation.
+ // Colon for old style error is rendered by the fmt.Formatter
+ // implementation of the old-style error.
+ want: "new style:" +
+ "\n somefile.go:123" +
+ "\n - old style:" +
+ "\n otherfile.go:456",
+ }, {
+ err: simple,
+ fmt: "%-12s",
+ want: "simple ",
+ }, {
+ // Don't use formatting flags for detailed view.
+ err: simple,
+ fmt: "%+12v",
+ want: "simple:" +
+ "\n somefile.go:123",
+ }, {
+ err: elephant,
+ fmt: "%+50s",
+ want: " can't adumbrate elephant: out of peanuts",
+ }, {
+ err: nonascii,
+ fmt: "%q",
+ want: `"cafΓ©"`,
+ }, {
+ err: nonascii,
+ fmt: "%+q",
+ want: `"caf\u00e9"`,
+ }, {
+ err: simple,
+ fmt: "% x",
+ want: "73 69 6d 70 6c 65",
+ }, {
+ err: newline,
+ fmt: "%s",
+ want: "msg with" +
+ "\nnewline: and another" +
+ "\none",
+ }, {
+ err: newline,
+ fmt: "%+v",
+ want: "msg with" +
+ "\n newline:" +
+ "\n somefile.go:123" +
+ "\n - and another" +
+ "\n one:" +
+ "\n somefile.go:123",
+ }, {
+ err: wrapped{"", wrapped{"inner message", nil}},
+ fmt: "%+v",
+ want: "somefile.go:123" +
+ "\n - inner message:" +
+ "\n somefile.go:123",
+ }, {
+ err: spurious(""),
+ fmt: "%s",
+ want: "spurious",
+ }, {
+ err: spurious(""),
+ fmt: "%+v",
+ want: "spurious",
+ }, {
+ err: spurious("extra"),
+ fmt: "%s",
+ want: "spurious",
+ }, {
+ err: spurious("extra"),
+ fmt: "%+v",
+ want: "spurious:\n" +
+ " extra",
+ }, {
+ err: nil,
+ fmt: "%+v",
+ want: "<nil>",
+ }, {
+ err: (*wrapped)(nil),
+ fmt: "%+v",
+ want: "<nil>",
+ }, {
+ err: simple,
+ fmt: "%T",
+ want: "*fmt_test.wrapped",
+ }, {
+ err: simple,
+ fmt: "%πŸ€ͺ",
+ want: "%!πŸ€ͺ(*fmt_test.wrapped=&{simple <nil>})",
+ }, {
+ err: formatError("use fmt.Formatter"),
+ fmt: "%#v",
+ want: "use fmt.Formatter",
+ }, {
+ err: wrapped{"using errors.Formatter",
+ formatError("use fmt.Formatter")},
+ fmt: "%#v",
+ want: "fmt_test.wrapped{msg:\"using errors.Formatter\", err:\"use fmt.Formatter\"}",
+ }, {
+ err: fmtTwice("%s %s", "ok", panicValue{}),
+ fmt: "%s",
+ want: "ok %!s(PANIC=panic)/ok %!s(PANIC=panic)",
+ }, {
+ err: fmtTwice("%o %s", panicValue{}, "ok"),
+ fmt: "%s",
+ want: "{} ok/{} ok",
+ }, {
+ err: adapted{"adapted", nil},
+ fmt: "%+v",
+ want: "adapted:" +
+ "\n detail",
+ }, {
+ err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}},
+ fmt: "%+v",
+ want: "outer:" +
+ "\n detail" +
+ "\n - mid:" +
+ "\n detail" +
+ "\n - inner:" +
+ "\n detail",
+ }}
+ for i, tc := range testCases {
+ t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) {
+ got := fmt.Sprintf(tc.fmt, tc.err)
+ var ok bool
+ if tc.regexp {
+ var err error
+ ok, err = regexp.MatchString(tc.want, got)
+ if err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ ok = got == tc.want
+ }
+ if !ok {
+ t.Errorf("\n got: %q\nwant: %q", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestAdaptor(t *testing.T) {
+ testCases := []struct {
+ err error
+ fmt string
+ want string
+ regexp bool
+ }{{
+ err: adapted{"adapted", nil},
+ fmt: "%+v",
+ want: "adapted:" +
+ "\n detail",
+ }, {
+ err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}},
+ fmt: "%+v",
+ want: "outer:" +
+ "\n detail" +
+ "\n - mid:" +
+ "\n detail" +
+ "\n - inner:" +
+ "\n detail",
+ }}
+ for i, tc := range testCases {
+ t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) {
+ got := gofmt.Sprintf(tc.fmt, tc.err)
+ if got != tc.want {
+ t.Errorf("\n got: %q\nwant: %q", got, tc.want)
+ }
+ })
+ }
+}
+
+var _ errors.Formatter = wrapped{}
+
+type wrapped struct {
+ msg string
+ err error
+}
+
+func (e wrapped) Error() string { return "should call Format" }
+
+func (e wrapped) FormatError(p errors.Printer) (next error) {
+ p.Print(e.msg)
+ p.Detail()
+ p.Print("somefile.go:123")
+ return e.err
+}
+
+var _ errors.Formatter = detailed{}
+
+type detailed struct{}
+
+func (e detailed) Error() string { return fmt.Sprint(e) }
+
+func (detailed) FormatError(p errors.Printer) (next error) {
+ p.Printf("out of %s", "peanuts")
+ p.Detail()
+ p.Print("the elephant is on strike\n")
+ p.Printf("and the %d monkeys\nare laughing", 12)
+ return nil
+}
+
+type withFrameAndMore struct {
+ frame errors.Frame
+}
+
+func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
+
+func (e *withFrameAndMore) FormatError(p errors.Printer) (next error) {
+ p.Print("something")
+ if p.Detail() {
+ e.frame.Format(p)
+ p.Print("something more")
+ }
+ return nil
+}
+
+type spurious string
+
+func (e spurious) Error() string { return fmt.Sprint(e) }
+
+func (e spurious) Format(fmt.State, rune) {
+ panic("should never be called by one of the tests")
+}
+
+func (e spurious) FormatError(p errors.Printer) (next error) {
+ p.Print("spurious")
+ p.Detail() // Call detail even if we don't print anything
+ if e == "" {
+ p.Print()
+ } else {
+ p.Print("\n", string(e)) // print extraneous leading newline
+ }
+ return nil
+}
+
+type adapted struct {
+ msg string
+ err error
+}
+
+func (e adapted) Error() string { return string(e.msg) }
+
+func (e adapted) Format(s fmt.State, verb rune) {
+ fmt.FormatError(s, verb, e)
+}
+
+func (e adapted) FormatError(p errors.Printer) error {
+ p.Print(e.msg)
+ p.Detail()
+ p.Print("detail")
+ return e.err
+}
+
+// formatError is an error implementing Format instead of errors.Formatter.
+// The implementation mimics the implementation of github.com/pkg/errors.
+type formatError string
+
+func (e formatError) Error() string { return string(e) }
+
+func (e formatError) Format(s fmt.State, verb rune) {
+ // Body based on pkg/errors/errors.go
+ switch verb {
+ case 'v':
+ if s.Flag('+') {
+ io.WriteString(s, string(e))
+ fmt.Fprintf(s, ":\n%s", "otherfile.go:456")
+ return
+ }
+ fallthrough
+ case 's':
+ io.WriteString(s, string(e))
+ case 'q':
+ fmt.Fprintf(s, "%q", string(e))
+ }
+}
+
+func (e formatError) GoString() string {
+ panic("should never be called")
+}
+
+type fmtTwiceErr struct {
+ format string
+ args []interface{}
+}
+
+func fmtTwice(format string, a ...interface{}) error {
+ return fmtTwiceErr{format, a}
+}
+
+func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
+
+func (e fmtTwiceErr) FormatError(p errors.Printer) (next error) {
+ p.Printf(e.format, e.args...)
+ p.Print("/")
+ p.Printf(e.format, e.args...)
+ return nil
+}
+
+func (e fmtTwiceErr) GoString() string {
+ return "2 times " + fmt.Sprintf(e.format, e.args...)
+}
+
+type panicValue struct{}
+
+func (panicValue) String() string { panic("panic") }
+
+var rePath = regexp.MustCompile(`( [^ ]*)fmt.*test\.`)
+var reLine = regexp.MustCompile(":[0-9]*\n?$")
+
+func cleanPath(s string) string {
+ s = rePath.ReplaceAllString(s, "/path.")
+ s = reLine.ReplaceAllString(s, ":xxx")
+ s = strings.Replace(s, "\n ", "", -1)
+ s = strings.Replace(s, " /", "/", -1)
+ return s
+}
+
+func errToParts(err error) (a []string) {
+ for err != nil {
+ var p testPrinter
+ if errors.Unwrap(err) != nil {
+ p.str += "wraps:"
+ }
+ f, ok := err.(errors.Formatter)
+ if !ok {
+ a = append(a, err.Error())
+ break
+ }
+ err = f.FormatError(&p)
+ a = append(a, cleanPath(p.str))
+ }
+ return a
+
+}
+
+type testPrinter struct {
+ str string
+}
+
+func (p *testPrinter) Print(a ...interface{}) {
+ p.str += fmt.Sprint(a...)
+}
+
+func (p *testPrinter) Printf(format string, a ...interface{}) {
+ p.str += fmt.Sprintf(format, a...)
+}
+
+func (p *testPrinter) Detail() bool {
+ p.str += " /"
+ return true
+}
diff --git a/errors/fmt/example_test.go b/errors/fmt/example_test.go
new file mode 100644
index 0000000..c77e788
--- /dev/null
+++ b/errors/fmt/example_test.go
@@ -0,0 +1,29 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+ "fmt"
+)
+
+// Animal has a Name and an Age to represent an animal.
+type Animal struct {
+ Name string
+ Age uint
+}
+
+// String makes Animal satisfy the Stringer interface.
+func (a Animal) String() string {
+ return fmt.Sprintf("%v (%d)", a.Name, a.Age)
+}
+
+func ExampleStringer() {
+ a := Animal{
+ Name: "Gopher",
+ Age: 2,
+ }
+ fmt.Println(a)
+ // Output: Gopher (2)
+}
diff --git a/errors/fmt/export_test.go b/errors/fmt/export_test.go
new file mode 100644
index 0000000..14163a2
--- /dev/null
+++ b/errors/fmt/export_test.go
@@ -0,0 +1,8 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+var IsSpace = isSpace
+var Parsenum = parsenum
diff --git a/errors/fmt/fmt_test.go b/errors/fmt/fmt_test.go
new file mode 100644
index 0000000..3667ccb
--- /dev/null
+++ b/errors/fmt/fmt_test.go
@@ -0,0 +1,1850 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+ "bytes"
+ . "golang.org/x/exp/errors/fmt"
+ "io"
+ "math"
+ "reflect"
+ "runtime"
+ "strings"
+ "testing"
+ "time"
+ // "unicode"
+)
+
+type (
+ renamedBool bool
+ renamedInt int
+ renamedInt8 int8
+ renamedInt16 int16
+ renamedInt32 int32
+ renamedInt64 int64
+ renamedUint uint
+ renamedUint8 uint8
+ renamedUint16 uint16
+ renamedUint32 uint32
+ renamedUint64 uint64
+ renamedUintptr uintptr
+ renamedString string
+ renamedBytes []byte
+ renamedFloat32 float32
+ renamedFloat64 float64
+ renamedComplex64 complex64
+ renamedComplex128 complex128
+)
+
+func TestFmtInterface(t *testing.T) {
+ var i1 interface{}
+ i1 = "abc"
+ s := Sprintf("%s", i1)
+ if s != "abc" {
+ t.Errorf(`Sprintf("%%s", empty("abc")) = %q want %q`, s, "abc")
+ }
+}
+
+var (
+ NaN = math.NaN()
+ posInf = math.Inf(1)
+ negInf = math.Inf(-1)
+
+ intVar = 0
+
+ array = [5]int{1, 2, 3, 4, 5}
+ iarray = [4]interface{}{1, "hello", 2.5, nil}
+ slice = array[:]
+ islice = iarray[:]
+)
+
+type A struct {
+ i int
+ j uint
+ s string
+ x []int
+}
+
+type I int
+
+func (i I) String() string { return Sprintf("<%d>", int(i)) }
+
+type B struct {
+ I I
+ j int
+}
+
+type C struct {
+ i int
+ B
+}
+
+type F int
+
+func (f F) Format(s State, c rune) {
+ Fprintf(s, "<%c=F(%d)>", c, int(f))
+}
+
+type G int
+
+func (g G) GoString() string {
+ return Sprintf("GoString(%d)", int(g))
+}
+
+type S struct {
+ F F // a struct field that Formats
+ G G // a struct field that GoStrings
+}
+
+type SI struct {
+ I interface{}
+}
+
+// P is a type with a String method with pointer receiver for testing %p.
+type P int
+
+var pValue P
+
+func (p *P) String() string {
+ return "String(p)"
+}
+
+var barray = [5]renamedUint8{1, 2, 3, 4, 5}
+var bslice = barray[:]
+
+type byteStringer byte
+
+func (byteStringer) String() string {
+ return "X"
+}
+
+var byteStringerSlice = []byteStringer{'h', 'e', 'l', 'l', 'o'}
+
+type byteFormatter byte
+
+func (byteFormatter) Format(f State, _ rune) {
+ Fprint(f, "X")
+}
+
+var byteFormatterSlice = []byteFormatter{'h', 'e', 'l', 'l', 'o'}
+
+// Copy of io.stringWriter interface used by writeStringFormatter for type assertion.
+type stringWriter interface {
+ WriteString(s string) (n int, err error)
+}
+
+type writeStringFormatter string
+
+func (sf writeStringFormatter) Format(f State, c rune) {
+ if sw, ok := f.(stringWriter); ok {
+ sw.WriteString("***" + string(sf) + "***")
+ }
+}
+
+var fmtTests = []struct {
+ fmt string
+ val interface{}
+ out string
+}{
+ {"%d", 12345, "12345"},
+ {"%v", 12345, "12345"},
+ {"%t", true, "true"},
+
+ // basic string
+ {"%s", "abc", "abc"},
+ {"%q", "abc", `"abc"`},
+ {"%x", "abc", "616263"},
+ {"%x", "\xff\xf0\x0f\xff", "fff00fff"},
+ {"%X", "\xff\xf0\x0f\xff", "FFF00FFF"},
+ {"%x", "", ""},
+ {"% x", "", ""},
+ {"%#x", "", ""},
+ {"%# x", "", ""},
+ {"%x", "xyz", "78797a"},
+ {"%X", "xyz", "78797A"},
+ {"% x", "xyz", "78 79 7a"},
+ {"% X", "xyz", "78 79 7A"},
+ {"%#x", "xyz", "0x78797a"},
+ {"%#X", "xyz", "0X78797A"},
+ {"%# x", "xyz", "0x78 0x79 0x7a"},
+ {"%# X", "xyz", "0X78 0X79 0X7A"},
+
+ // basic bytes
+ {"%s", []byte("abc"), "abc"},
+ {"%s", [3]byte{'a', 'b', 'c'}, "abc"},
+ {"%s", &[3]byte{'a', 'b', 'c'}, "&abc"},
+ {"%q", []byte("abc"), `"abc"`},
+ {"%x", []byte("abc"), "616263"},
+ {"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"},
+ {"%X", []byte("\xff\xf0\x0f\xff"), "FFF00FFF"},
+ {"%x", []byte(""), ""},
+ {"% x", []byte(""), ""},
+ {"%#x", []byte(""), ""},
+ {"%# x", []byte(""), ""},
+ {"%x", []byte("xyz"), "78797a"},
+ {"%X", []byte("xyz"), "78797A"},
+ {"% x", []byte("xyz"), "78 79 7a"},
+ {"% X", []byte("xyz"), "78 79 7A"},
+ {"%#x", []byte("xyz"), "0x78797a"},
+ {"%#X", []byte("xyz"), "0X78797A"},
+ {"%# x", []byte("xyz"), "0x78 0x79 0x7a"},
+ {"%# X", []byte("xyz"), "0X78 0X79 0X7A"},
+
+ // escaped strings
+ {"%q", "", `""`},
+ {"%#q", "", "``"},
+ {"%q", "\"", `"\""`},
+ {"%#q", "\"", "`\"`"},
+ {"%q", "`", `"` + "`" + `"`},
+ {"%#q", "`", `"` + "`" + `"`},
+ {"%q", "\n", `"\n"`},
+ {"%#q", "\n", `"\n"`},
+ {"%q", `\n`, `"\\n"`},
+ {"%#q", `\n`, "`\\n`"},
+ {"%q", "abc", `"abc"`},
+ {"%#q", "abc", "`abc`"},
+ {"%q", "ζ—₯本θͺž", `"ζ—₯本θͺž"`},
+ {"%+q", "ζ—₯本θͺž", `"\u65e5\u672c\u8a9e"`},
+ {"%#q", "ζ—₯本θͺž", "`ζ—₯本θͺž`"},
+ {"%#+q", "ζ—₯本θͺž", "`ζ—₯本θͺž`"},
+ {"%q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`},
+ {"%+q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`},
+ {"%#q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`},
+ {"%#+q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`},
+ {"%q", "☺", `"☺"`},
+ {"% q", "☺", `"☺"`}, // The space modifier should have no effect.
+ {"%+q", "☺", `"\u263a"`},
+ {"%#q", "☺", "`☺`"},
+ {"%#+q", "☺", "`☺`"},
+ {"%10q", "⌘", ` "⌘"`},
+ {"%+10q", "⌘", ` "\u2318"`},
+ {"%-10q", "⌘", `"⌘" `},
+ {"%+-10q", "⌘", `"\u2318" `},
+ {"%010q", "⌘", `0000000"⌘"`},
+ {"%+010q", "⌘", `00"\u2318"`},
+ {"%-010q", "⌘", `"⌘" `}, // 0 has no effect when - is present.
+ {"%+-010q", "⌘", `"\u2318" `},
+ {"%#8q", "\n", ` "\n"`},
+ {"%#+8q", "\r", ` "\r"`},
+ {"%#-8q", "\t", "` ` "},
+ {"%#+-8q", "\b", `"\b" `},
+ {"%q", "abc\xffdef", `"abc\xffdef"`},
+ {"%+q", "abc\xffdef", `"abc\xffdef"`},
+ {"%#q", "abc\xffdef", `"abc\xffdef"`},
+ {"%#+q", "abc\xffdef", `"abc\xffdef"`},
+ // Runes that are not printable.
+ {"%q", "\U0010ffff", `"\U0010ffff"`},
+ {"%+q", "\U0010ffff", `"\U0010ffff"`},
+ {"%#q", "\U0010ffff", "`􏿿`"},
+ {"%#+q", "\U0010ffff", "`􏿿`"},
+ // Runes that are not valid.
+ {"%q", string(rune(0x110000)), `"οΏ½"`},
+ {"%+q", string(rune(0x110000)), `"\ufffd"`},
+ {"%#q", string(rune(0x110000)), "`οΏ½`"},
+ {"%#+q", string(rune(0x110000)), "`οΏ½`"},
+
+ // characters
+ {"%c", uint('x'), "x"},
+ {"%c", 0xe4, "Γ€"},
+ {"%c", 0x672c, "本"},
+ {"%c", 'ζ—₯', "ζ—₯"},
+ {"%.0c", '⌘', "⌘"}, // Specifying precision should have no effect.
+ {"%3c", '⌘', " ⌘"},
+ {"%-3c", '⌘', "⌘ "},
+ // Runes that are not printable.
+ {"%c", '\U00000e00', "\u0e00"},
+ {"%c", '\U0010ffff', "\U0010ffff"},
+ // Runes that are not valid.
+ {"%c", -1, "οΏ½"},
+ {"%c", 0xDC80, "οΏ½"},
+ {"%c", rune(0x110000), "οΏ½"},
+ {"%c", int64(0xFFFFFFFFF), "οΏ½"},
+ {"%c", uint64(0xFFFFFFFFF), "οΏ½"},
+
+ // escaped characters
+ {"%q", uint(0), `'\x00'`},
+ {"%+q", uint(0), `'\x00'`},
+ {"%q", '"', `'"'`},
+ {"%+q", '"', `'"'`},
+ {"%q", '\'', `'\''`},
+ {"%+q", '\'', `'\''`},
+ {"%q", '`', "'`'"},
+ {"%+q", '`', "'`'"},
+ {"%q", 'x', `'x'`},
+ {"%+q", 'x', `'x'`},
+ {"%q", 'ΓΏ', `'ΓΏ'`},
+ {"%+q", 'ΓΏ', `'\u00ff'`},
+ {"%q", '\n', `'\n'`},
+ {"%+q", '\n', `'\n'`},
+ {"%q", '☺', `'☺'`},
+ {"%+q", '☺', `'\u263a'`},
+ {"% q", '☺', `'☺'`}, // The space modifier should have no effect.
+ {"%.0q", '☺', `'☺'`}, // Specifying precision should have no effect.
+ {"%10q", '⌘', ` '⌘'`},
+ {"%+10q", '⌘', ` '\u2318'`},
+ {"%-10q", '⌘', `'⌘' `},
+ {"%+-10q", '⌘', `'\u2318' `},
+ {"%010q", '⌘', `0000000'⌘'`},
+ {"%+010q", '⌘', `00'\u2318'`},
+ {"%-010q", '⌘', `'⌘' `}, // 0 has no effect when - is present.
+ {"%+-010q", '⌘', `'\u2318' `},
+ // Runes that are not printable.
+ {"%q", '\U00000e00', `'\u0e00'`},
+ {"%q", '\U0010ffff', `'\U0010ffff'`},
+ // Runes that are not valid.
+ {"%q", int32(-1), "%!q(int32=-1)"},
+ {"%q", 0xDC80, `'οΏ½'`},
+ {"%q", rune(0x110000), "%!q(int32=1114112)"},
+ {"%q", int64(0xFFFFFFFFF), "%!q(int64=68719476735)"},
+ {"%q", uint64(0xFFFFFFFFF), "%!q(uint64=68719476735)"},
+
+ // width
+ {"%5s", "abc", " abc"},
+ {"%2s", "\u263a", " ☺"},
+ {"%-5s", "abc", "abc "},
+ {"%-8q", "abc", `"abc" `},
+ {"%05s", "abc", "00abc"},
+ {"%08q", "abc", `000"abc"`},
+ {"%5s", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"},
+ {"%.5s", "abcdefghijklmnopqrstuvwxyz", "abcde"},
+ {"%.0s", "ζ—₯本θͺžζ—₯本θͺž", ""},
+ {"%.5s", "ζ—₯本θͺžζ—₯本θͺž", "ζ—₯本θͺžζ—₯本"},
+ {"%.10s", "ζ—₯本θͺžζ—₯本θͺž", "ζ—₯本θͺžζ—₯本θͺž"},
+ {"%.5s", []byte("ζ—₯本θͺžζ—₯本θͺž"), "ζ—₯本θͺžζ—₯本"},
+ {"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`},
+ {"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"},
+ {"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`},
+ {"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), "6162636465"},
+ {"%.3q", "ζ—₯本θͺžζ—₯本θͺž", `"ζ—₯本θͺž"`},
+ {"%.3q", []byte("ζ—₯本θͺžζ—₯本θͺž"), `"ζ—₯本θͺž"`},
+ {"%.1q", "ζ—₯本θͺž", `"ζ—₯"`},
+ {"%.1q", []byte("ζ—₯本θͺž"), `"ζ—₯"`},
+ {"%.1x", "ζ—₯本θͺž", "e6"},
+ {"%.1X", []byte("ζ—₯本θͺž"), "E6"},
+ {"%10.1q", "ζ—₯本θͺžζ—₯本θͺž", ` "ζ—₯"`},
+ {"%10v", nil, " <nil>"},
+ {"%-10v", nil, "<nil> "},
+
+ // integers
+ {"%d", uint(12345), "12345"},
+ {"%d", int(-12345), "-12345"},
+ {"%d", ^uint8(0), "255"},
+ {"%d", ^uint16(0), "65535"},
+ {"%d", ^uint32(0), "4294967295"},
+ {"%d", ^uint64(0), "18446744073709551615"},
+ {"%d", int8(-1 << 7), "-128"},
+ {"%d", int16(-1 << 15), "-32768"},
+ {"%d", int32(-1 << 31), "-2147483648"},
+ {"%d", int64(-1 << 63), "-9223372036854775808"},
+ {"%.d", 0, ""},
+ {"%.0d", 0, ""},
+ {"%6.0d", 0, " "},
+ {"%06.0d", 0, " "},
+ {"% d", 12345, " 12345"},
+ {"%+d", 12345, "+12345"},
+ {"%+d", -12345, "-12345"},
+ {"%b", 7, "111"},
+ {"%b", -6, "-110"},
+ {"%b", ^uint32(0), "11111111111111111111111111111111"},
+ {"%b", ^uint64(0), "1111111111111111111111111111111111111111111111111111111111111111"},
+ {"%b", int64(-1 << 63), zeroFill("-1", 63, "")},
+ {"%o", 01234, "1234"},
+ {"%#o", 01234, "01234"},
+ {"%o", ^uint32(0), "37777777777"},
+ {"%o", ^uint64(0), "1777777777777777777777"},
+ {"%#X", 0, "0X0"},
+ {"%x", 0x12abcdef, "12abcdef"},
+ {"%X", 0x12abcdef, "12ABCDEF"},
+ {"%x", ^uint32(0), "ffffffff"},
+ {"%X", ^uint64(0), "FFFFFFFFFFFFFFFF"},
+ {"%.20b", 7, "00000000000000000111"},
+ {"%10d", 12345, " 12345"},
+ {"%10d", -12345, " -12345"},
+ {"%+10d", 12345, " +12345"},
+ {"%010d", 12345, "0000012345"},
+ {"%010d", -12345, "-000012345"},
+ {"%20.8d", 1234, " 00001234"},
+ {"%20.8d", -1234, " -00001234"},
+ {"%020.8d", 1234, " 00001234"},
+ {"%020.8d", -1234, " -00001234"},
+ {"%-20.8d", 1234, "00001234 "},
+ {"%-20.8d", -1234, "-00001234 "},
+ {"%-#20.8x", 0x1234abc, "0x01234abc "},
+ {"%-#20.8X", 0x1234abc, "0X01234ABC "},
+ {"%-#20.8o", 01234, "00001234 "},
+
+ // Test correct f.intbuf overflow checks.
+ {"%068d", 1, zeroFill("", 68, "1")},
+ {"%068d", -1, zeroFill("-", 67, "1")},
+ {"%#.68x", 42, zeroFill("0x", 68, "2a")},
+ {"%.68d", -42, zeroFill("-", 68, "42")},
+ {"%+.68d", 42, zeroFill("+", 68, "42")},
+ {"% .68d", 42, zeroFill(" ", 68, "42")},
+ {"% +.68d", 42, zeroFill("+", 68, "42")},
+
+ // unicode format
+ {"%U", 0, "U+0000"},
+ {"%U", -1, "U+FFFFFFFFFFFFFFFF"},
+ {"%U", '\n', `U+000A`},
+ {"%#U", '\n', `U+000A`},
+ {"%+U", 'x', `U+0078`}, // Plus flag should have no effect.
+ {"%# U", 'x', `U+0078 'x'`}, // Space flag should have no effect.
+ {"%#.2U", 'x', `U+0078 'x'`}, // Precisions below 4 should print 4 digits.
+ {"%U", '\u263a', `U+263A`},
+ {"%#U", '\u263a', `U+263A '☺'`},
+ {"%U", '\U0001D6C2', `U+1D6C2`},
+ {"%#U", '\U0001D6C2', `U+1D6C2 '𝛂'`},
+ {"%#14.6U", '⌘', " U+002318 '⌘'"},
+ {"%#-14.6U", '⌘', "U+002318 '⌘' "},
+ {"%#014.6U", '⌘', " U+002318 '⌘'"},
+ {"%#-014.6U", '⌘', "U+002318 '⌘' "},
+ {"%.68U", uint(42), zeroFill("U+", 68, "2A")},
+ {"%#.68U", 'ζ—₯', zeroFill("U+", 68, "65E5") + " 'ζ—₯'"},
+
+ // floats
+ {"%+.3e", 0.0, "+0.000e+00"},
+ {"%+.3e", 1.0, "+1.000e+00"},
+ {"%+.3f", -1.0, "-1.000"},
+ {"%+.3F", -1.0, "-1.000"},
+ {"%+.3F", float32(-1.0), "-1.000"},
+ {"%+07.2f", 1.0, "+001.00"},
+ {"%+07.2f", -1.0, "-001.00"},
+ {"%-07.2f", 1.0, "1.00 "},
+ {"%-07.2f", -1.0, "-1.00 "},
+ {"%+-07.2f", 1.0, "+1.00 "},
+ {"%+-07.2f", -1.0, "-1.00 "},
+ {"%-+07.2f", 1.0, "+1.00 "},
+ {"%-+07.2f", -1.0, "-1.00 "},
+ {"%+10.2f", +1.0, " +1.00"},
+ {"%+10.2f", -1.0, " -1.00"},
+ {"% .3E", -1.0, "-1.000E+00"},
+ {"% .3e", 1.0, " 1.000e+00"},
+ {"%+.3g", 0.0, "+0"},
+ {"%+.3g", 1.0, "+1"},
+ {"%+.3g", -1.0, "-1"},
+ {"% .3g", -1.0, "-1"},
+ {"% .3g", 1.0, " 1"},
+ {"%b", float32(1.0), "8388608p-23"},
+ {"%b", 1.0, "4503599627370496p-52"},
+ // Test sharp flag used with floats.
+ {"%#g", 1e-323, "1.00000e-323"},
+ {"%#g", -1.0, "-1.00000"},
+ {"%#g", 1.1, "1.10000"},
+ {"%#g", 123456.0, "123456."},
+ {"%#g", 1234567.0, "1.234567e+06"},
+ {"%#g", 1230000.0, "1.23000e+06"},
+ {"%#g", 1000000.0, "1.00000e+06"},
+ {"%#.0f", 1.0, "1."},
+ {"%#.0e", 1.0, "1.e+00"},
+ {"%#.0g", 1.0, "1."},
+ {"%#.0g", 1100000.0, "1.e+06"},
+ {"%#.4f", 1.0, "1.0000"},
+ {"%#.4e", 1.0, "1.0000e+00"},
+ {"%#.4g", 1.0, "1.000"},
+ {"%#.4g", 100000.0, "1.000e+05"},
+ {"%#.0f", 123.0, "123."},
+ {"%#.0e", 123.0, "1.e+02"},
+ {"%#.0g", 123.0, "1.e+02"},
+ {"%#.4f", 123.0, "123.0000"},
+ {"%#.4e", 123.0, "1.2300e+02"},
+ {"%#.4g", 123.0, "123.0"},
+ {"%#.4g", 123000.0, "1.230e+05"},
+ {"%#9.4g", 1.0, " 1.000"},
+ // The sharp flag has no effect for binary float format.
+ {"%#b", 1.0, "4503599627370496p-52"},
+ // Precision has no effect for binary float format.
+ {"%.4b", float32(1.0), "8388608p-23"},
+ {"%.4b", -1.0, "-4503599627370496p-52"},
+ // Test correct f.intbuf boundary checks.
+ {"%.68f", 1.0, zeroFill("1.", 68, "")},
+ {"%.68f", -1.0, zeroFill("-1.", 68, "")},
+ // float infinites and NaNs
+ {"%f", posInf, "+Inf"},
+ {"%.1f", negInf, "-Inf"},
+ {"% f", NaN, " NaN"},
+ {"%20f", posInf, " +Inf"},
+ {"% 20F", posInf, " Inf"},
+ {"% 20e", negInf, " -Inf"},
+ {"%+20E", negInf, " -Inf"},
+ {"% +20g", negInf, " -Inf"},
+ {"%+-20G", posInf, "+Inf "},
+ {"%20e", NaN, " NaN"},
+ {"% +20E", NaN, " +NaN"},
+ {"% -20g", NaN, " NaN "},
+ {"%+-20G", NaN, "+NaN "},
+ // Zero padding does not apply to infinities and NaN.
+ {"%+020e", posInf, " +Inf"},
+ {"%-020f", negInf, "-Inf "},
+ {"%-020E", NaN, "NaN "},
+
+ // complex values
+ {"%.f", 0i, "(0+0i)"},
+ {"% .f", 0i, "( 0+0i)"},
+ {"%+.f", 0i, "(+0+0i)"},
+ {"% +.f", 0i, "(+0+0i)"},
+ {"%+.3e", 0i, "(+0.000e+00+0.000e+00i)"},
+ {"%+.3f", 0i, "(+0.000+0.000i)"},
+ {"%+.3g", 0i, "(+0+0i)"},
+ {"%+.3e", 1 + 2i, "(+1.000e+00+2.000e+00i)"},
+ {"%+.3f", 1 + 2i, "(+1.000+2.000i)"},
+ {"%+.3g", 1 + 2i, "(+1+2i)"},
+ {"%.3e", 0i, "(0.000e+00+0.000e+00i)"},
+ {"%.3f", 0i, "(0.000+0.000i)"},
+ {"%.3F", 0i, "(0.000+0.000i)"},
+ {"%.3F", complex64(0i), "(0.000+0.000i)"},
+ {"%.3g", 0i, "(0+0i)"},
+ {"%.3e", 1 + 2i, "(1.000e+00+2.000e+00i)"},
+ {"%.3f", 1 + 2i, "(1.000+2.000i)"},
+ {"%.3g", 1 + 2i, "(1+2i)"},
+ {"%.3e", -1 - 2i, "(-1.000e+00-2.000e+00i)"},
+ {"%.3f", -1 - 2i, "(-1.000-2.000i)"},
+ {"%.3g", -1 - 2i, "(-1-2i)"},
+ {"% .3E", -1 - 2i, "(-1.000E+00-2.000E+00i)"},
+ {"%+.3g", 1 + 2i, "(+1+2i)"},
+ {"%+.3g", complex64(1 + 2i), "(+1+2i)"},
+ {"%#g", 1 + 2i, "(1.00000+2.00000i)"},
+ {"%#g", 123456 + 789012i, "(123456.+789012.i)"},
+ {"%#g", 1e-10i, "(0.00000+1.00000e-10i)"},
+ {"%#g", -1e10 - 1.11e100i, "(-1.00000e+10-1.11000e+100i)"},
+ {"%#.0f", 1.23 + 1.0i, "(1.+1.i)"},
+ {"%#.0e", 1.23 + 1.0i, "(1.e+00+1.e+00i)"},
+ {"%#.0g", 1.23 + 1.0i, "(1.+1.i)"},
+ {"%#.0g", 0 + 100000i, "(0.+1.e+05i)"},
+ {"%#.0g", 1230000 + 0i, "(1.e+06+0.i)"},
+ {"%#.4f", 1 + 1.23i, "(1.0000+1.2300i)"},
+ {"%#.4e", 123 + 1i, "(1.2300e+02+1.0000e+00i)"},
+ {"%#.4g", 123 + 1.23i, "(123.0+1.230i)"},
+ {"%#12.5g", 0 + 100000i, "( 0.0000 +1.0000e+05i)"},
+ {"%#12.5g", 1230000 - 0i, "( 1.2300e+06 +0.0000i)"},
+ {"%b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"},
+ {"%b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"},
+ // The sharp flag has no effect for binary complex format.
+ {"%#b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"},
+ // Precision has no effect for binary complex format.
+ {"%.4b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"},
+ {"%.4b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"},
+ // complex infinites and NaNs
+ {"%f", complex(posInf, posInf), "(+Inf+Infi)"},
+ {"%f", complex(negInf, negInf), "(-Inf-Infi)"},
+ {"%f", complex(NaN, NaN), "(NaN+NaNi)"},
+ {"%.1f", complex(posInf, posInf), "(+Inf+Infi)"},
+ {"% f", complex(posInf, posInf), "( Inf+Infi)"},
+ {"% f", complex(negInf, negInf), "(-Inf-Infi)"},
+ {"% f", complex(NaN, NaN), "( NaN+NaNi)"},
+ {"%8e", complex(posInf, posInf), "( +Inf +Infi)"},
+ {"% 8E", complex(posInf, posInf), "( Inf +Infi)"},
+ {"%+8f", complex(negInf, negInf), "( -Inf -Infi)"},
+ {"% +8g", complex(negInf, negInf), "( -Inf -Infi)"},
+ {"% -8G", complex(NaN, NaN), "( NaN +NaN i)"},
+ {"%+-8b", complex(NaN, NaN), "(+NaN +NaN i)"},
+ // Zero padding does not apply to infinities and NaN.
+ {"%08f", complex(posInf, posInf), "( +Inf +Infi)"},
+ {"%-08g", complex(negInf, negInf), "(-Inf -Inf i)"},
+ {"%-08G", complex(NaN, NaN), "(NaN +NaN i)"},
+
+ // old test/fmt_test.go
+ {"%e", 1.0, "1.000000e+00"},
+ {"%e", 1234.5678e3, "1.234568e+06"},
+ {"%e", 1234.5678e-8, "1.234568e-05"},
+ {"%e", -7.0, "-7.000000e+00"},
+ {"%e", -1e-9, "-1.000000e-09"},
+ {"%f", 1234.5678e3, "1234567.800000"},
+ {"%f", 1234.5678e-8, "0.000012"},
+ {"%f", -7.0, "-7.000000"},
+ {"%f", -1e-9, "-0.000000"},
+ {"%g", 1234.5678e3, "1.2345678e+06"},
+ {"%g", float32(1234.5678e3), "1.2345678e+06"},
+ {"%g", 1234.5678e-8, "1.2345678e-05"},
+ {"%g", -7.0, "-7"},
+ {"%g", -1e-9, "-1e-09"},
+ {"%g", float32(-1e-9), "-1e-09"},
+ {"%E", 1.0, "1.000000E+00"},
+ {"%E", 1234.5678e3, "1.234568E+06"},
+ {"%E", 1234.5678e-8, "1.234568E-05"},
+ {"%E", -7.0, "-7.000000E+00"},
+ {"%E", -1e-9, "-1.000000E-09"},
+ {"%G", 1234.5678e3, "1.2345678E+06"},
+ {"%G", float32(1234.5678e3), "1.2345678E+06"},
+ {"%G", 1234.5678e-8, "1.2345678E-05"},
+ {"%G", -7.0, "-7"},
+ {"%G", -1e-9, "-1E-09"},
+ {"%G", float32(-1e-9), "-1E-09"},
+ {"%20.5s", "qwertyuiop", " qwert"},
+ {"%.5s", "qwertyuiop", "qwert"},
+ {"%-20.5s", "qwertyuiop", "qwert "},
+ {"%20c", 'x', " x"},
+ {"%-20c", 'x', "x "},
+ {"%20.6e", 1.2345e3, " 1.234500e+03"},
+ {"%20.6e", 1.2345e-3, " 1.234500e-03"},
+ {"%20e", 1.2345e3, " 1.234500e+03"},
+ {"%20e", 1.2345e-3, " 1.234500e-03"},
+ {"%20.8e", 1.2345e3, " 1.23450000e+03"},
+ {"%20f", 1.23456789e3, " 1234.567890"},
+ {"%20f", 1.23456789e-3, " 0.001235"},
+ {"%20f", 12345678901.23456789, " 12345678901.234568"},
+ {"%-20f", 1.23456789e3, "1234.567890 "},
+ {"%20.8f", 1.23456789e3, " 1234.56789000"},
+ {"%20.8f", 1.23456789e-3, " 0.00123457"},
+ {"%g", 1.23456789e3, "1234.56789"},
+ {"%g", 1.23456789e-3, "0.00123456789"},
+ {"%g", 1.23456789e20, "1.23456789e+20"},
+
+ // arrays
+ {"%v", array, "[1 2 3 4 5]"},
+ {"%v", iarray, "[1 hello 2.5 <nil>]"},
+ {"%v", barray, "[1 2 3 4 5]"},
+ {"%v", &array, "&[1 2 3 4 5]"},
+ {"%v", &iarray, "&[1 hello 2.5 <nil>]"},
+ {"%v", &barray, "&[1 2 3 4 5]"},
+
+ // slices
+ {"%v", slice, "[1 2 3 4 5]"},
+ {"%v", islice, "[1 hello 2.5 <nil>]"},
+ {"%v", bslice, "[1 2 3 4 5]"},
+ {"%v", &slice, "&[1 2 3 4 5]"},
+ {"%v", &islice, "&[1 hello 2.5 <nil>]"},
+ {"%v", &bslice, "&[1 2 3 4 5]"},
+
+ // byte arrays and slices with %b,%c,%d,%o,%U and %v
+ {"%b", [3]byte{65, 66, 67}, "[1000001 1000010 1000011]"},
+ {"%c", [3]byte{65, 66, 67}, "[A B C]"},
+ {"%d", [3]byte{65, 66, 67}, "[65 66 67]"},
+ {"%o", [3]byte{65, 66, 67}, "[101 102 103]"},
+ {"%U", [3]byte{65, 66, 67}, "[U+0041 U+0042 U+0043]"},
+ {"%v", [3]byte{65, 66, 67}, "[65 66 67]"},
+ {"%v", [1]byte{123}, "[123]"},
+ {"%012v", []byte{}, "[]"},
+ {"%#012v", []byte{}, "[]byte{}"},
+ {"%6v", []byte{1, 11, 111}, "[ 1 11 111]"},
+ {"%06v", []byte{1, 11, 111}, "[000001 000011 000111]"},
+ {"%-6v", []byte{1, 11, 111}, "[1 11 111 ]"},
+ {"%-06v", []byte{1, 11, 111}, "[1 11 111 ]"},
+ {"%#v", []byte{1, 11, 111}, "[]byte{0x1, 0xb, 0x6f}"},
+ {"%#6v", []byte{1, 11, 111}, "[]byte{ 0x1, 0xb, 0x6f}"},
+ {"%#06v", []byte{1, 11, 111}, "[]byte{0x000001, 0x00000b, 0x00006f}"},
+ {"%#-6v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"},
+ {"%#-06v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"},
+ // f.space should and f.plus should not have an effect with %v.
+ {"% v", []byte{1, 11, 111}, "[ 1 11 111]"},
+ {"%+v", [3]byte{1, 11, 111}, "[1 11 111]"},
+ {"%# -6v", []byte{1, 11, 111}, "[]byte{ 0x1 , 0xb , 0x6f }"},
+ {"%#+-6v", [3]byte{1, 11, 111}, "[3]uint8{0x1 , 0xb , 0x6f }"},
+ // f.space and f.plus should have an effect with %d.
+ {"% d", []byte{1, 11, 111}, "[ 1 11 111]"},
+ {"%+d", [3]byte{1, 11, 111}, "[+1 +11 +111]"},
+ {"%# -6d", []byte{1, 11, 111}, "[ 1 11 111 ]"},
+ {"%#+-6d", [3]byte{1, 11, 111}, "[+1 +11 +111 ]"},
+
+ // floates with %v
+ {"%v", 1.2345678, "1.2345678"},
+ {"%v", float32(1.2345678), "1.2345678"},
+
+ // complexes with %v
+ {"%v", 1 + 2i, "(1+2i)"},
+ {"%v", complex64(1 + 2i), "(1+2i)"},
+
+ // structs
+ {"%v", A{1, 2, "a", []int{1, 2}}, `{1 2 a [1 2]}`},
+ {"%+v", A{1, 2, "a", []int{1, 2}}, `{i:1 j:2 s:a x:[1 2]}`},
+
+ // +v on structs with Stringable items
+ {"%+v", B{1, 2}, `{I:<1> j:2}`},
+ {"%+v", C{1, B{2, 3}}, `{i:1 B:{I:<2> j:3}}`},
+
+ // other formats on Stringable items
+ {"%s", I(23), `<23>`},
+ {"%q", I(23), `"<23>"`},
+ {"%x", I(23), `3c32333e`},
+ {"%#x", I(23), `0x3c32333e`},
+ {"%# x", I(23), `0x3c 0x32 0x33 0x3e`},
+ // Stringer applies only to string formats.
+ {"%d", I(23), `23`},
+ // Stringer applies to the extracted value.
+ {"%s", reflect.ValueOf(I(23)), `<23>`},
+
+ // go syntax
+ {"%#v", A{1, 2, "a", []int{1, 2}}, `fmt_test.A{i:1, j:0x2, s:"a", x:[]int{1, 2}}`},
+ {"%#v", new(byte), "(*uint8)(0xPTR)"},
+ {"%#v", TestFmtInterface, "(func(*testing.T))(0xPTR)"},
+ {"%#v", make(chan int), "(chan int)(0xPTR)"},
+ {"%#v", uint64(1<<64 - 1), "0xffffffffffffffff"},
+ {"%#v", 1000000000, "1000000000"},
+ {"%#v", map[string]int{"a": 1}, `map[string]int{"a":1}`},
+ {"%#v", map[string]B{"a": {1, 2}}, `map[string]fmt_test.B{"a":fmt_test.B{I:1, j:2}}`},
+ {"%#v", []string{"a", "b"}, `[]string{"a", "b"}`},
+ {"%#v", SI{}, `fmt_test.SI{I:interface {}(nil)}`},
+ {"%#v", []int(nil), `[]int(nil)`},
+ {"%#v", []int{}, `[]int{}`},
+ {"%#v", array, `[5]int{1, 2, 3, 4, 5}`},
+ {"%#v", &array, `&[5]int{1, 2, 3, 4, 5}`},
+ {"%#v", iarray, `[4]interface {}{1, "hello", 2.5, interface {}(nil)}`},
+ {"%#v", &iarray, `&[4]interface {}{1, "hello", 2.5, interface {}(nil)}`},
+ {"%#v", map[int]byte(nil), `map[int]uint8(nil)`},
+ {"%#v", map[int]byte{}, `map[int]uint8{}`},
+ {"%#v", "foo", `"foo"`},
+ {"%#v", barray, `[5]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`},
+ {"%#v", bslice, `[]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`},
+ {"%#v", []int32(nil), "[]int32(nil)"},
+ {"%#v", 1.2345678, "1.2345678"},
+ {"%#v", float32(1.2345678), "1.2345678"},
+ // Only print []byte and []uint8 as type []byte if they appear at the top level.
+ {"%#v", []byte(nil), "[]byte(nil)"},
+ {"%#v", []uint8(nil), "[]byte(nil)"},
+ {"%#v", []byte{}, "[]byte{}"},
+ {"%#v", []uint8{}, "[]byte{}"},
+ {"%#v", reflect.ValueOf([]byte{}), "[]uint8{}"},
+ {"%#v", reflect.ValueOf([]uint8{}), "[]uint8{}"},
+ {"%#v", &[]byte{}, "&[]uint8{}"},
+ {"%#v", &[]byte{}, "&[]uint8{}"},
+ {"%#v", [3]byte{}, "[3]uint8{0x0, 0x0, 0x0}"},
+ {"%#v", [3]uint8{}, "[3]uint8{0x0, 0x0, 0x0}"},
+
+ // slices with other formats
+ {"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`},
+ {"%x", []int{1, 2, 15}, `[1 2 f]`},
+ {"%d", []int{1, 2, 15}, `[1 2 15]`},
+ {"%d", []byte{1, 2, 15}, `[1 2 15]`},
+ {"%q", []string{"a", "b"}, `["a" "b"]`},
+ {"% 02x", []byte{1}, "01"},
+ {"% 02x", []byte{1, 2, 3}, "01 02 03"},
+
+ // Padding with byte slices.
+ {"%2x", []byte{}, " "},
+ {"%#2x", []byte{}, " "},
+ {"% 02x", []byte{}, "00"},
+ {"%# 02x", []byte{}, "00"},
+ {"%-2x", []byte{}, " "},
+ {"%-02x", []byte{}, " "},
+ {"%8x", []byte{0xab}, " ab"},
+ {"% 8x", []byte{0xab}, " ab"},
+ {"%#8x", []byte{0xab}, " 0xab"},
+ {"%# 8x", []byte{0xab}, " 0xab"},
+ {"%08x", []byte{0xab}, "000000ab"},
+ {"% 08x", []byte{0xab}, "000000ab"},
+ {"%#08x", []byte{0xab}, "00000xab"},
+ {"%# 08x", []byte{0xab}, "00000xab"},
+ {"%10x", []byte{0xab, 0xcd}, " abcd"},
+ {"% 10x", []byte{0xab, 0xcd}, " ab cd"},
+ {"%#10x", []byte{0xab, 0xcd}, " 0xabcd"},
+ {"%# 10x", []byte{0xab, 0xcd}, " 0xab 0xcd"},
+ {"%010x", []byte{0xab, 0xcd}, "000000abcd"},
+ {"% 010x", []byte{0xab, 0xcd}, "00000ab cd"},
+ {"%#010x", []byte{0xab, 0xcd}, "00000xabcd"},
+ {"%# 010x", []byte{0xab, 0xcd}, "00xab 0xcd"},
+ {"%-10X", []byte{0xab}, "AB "},
+ {"% -010X", []byte{0xab}, "AB "},
+ {"%#-10X", []byte{0xab, 0xcd}, "0XABCD "},
+ {"%# -010X", []byte{0xab, 0xcd}, "0XAB 0XCD "},
+ // Same for strings
+ {"%2x", "", " "},
+ {"%#2x", "", " "},
+ {"% 02x", "", "00"},
+ {"%# 02x", "", "00"},
+ {"%-2x", "", " "},
+ {"%-02x", "", " "},
+ {"%8x", "\xab", " ab"},
+ {"% 8x", "\xab", " ab"},
+ {"%#8x", "\xab", " 0xab"},
+ {"%# 8x", "\xab", " 0xab"},
+ {"%08x", "\xab", "000000ab"},
+ {"% 08x", "\xab", "000000ab"},
+ {"%#08x", "\xab", "00000xab"},
+ {"%# 08x", "\xab", "00000xab"},
+ {"%10x", "\xab\xcd", " abcd"},
+ {"% 10x", "\xab\xcd", " ab cd"},
+ {"%#10x", "\xab\xcd", " 0xabcd"},
+ {"%# 10x", "\xab\xcd", " 0xab 0xcd"},
+ {"%010x", "\xab\xcd", "000000abcd"},
+ {"% 010x", "\xab\xcd", "00000ab cd"},
+ {"%#010x", "\xab\xcd", "00000xabcd"},
+ {"%# 010x", "\xab\xcd", "00xab 0xcd"},
+ {"%-10X", "\xab", "AB "},
+ {"% -010X", "\xab", "AB "},
+ {"%#-10X", "\xab\xcd", "0XABCD "},
+ {"%# -010X", "\xab\xcd", "0XAB 0XCD "},
+
+ // renamings
+ {"%v", renamedBool(true), "true"},
+ {"%d", renamedBool(true), "%!d(fmt_test.renamedBool=true)"},
+ {"%o", renamedInt(8), "10"},
+ {"%d", renamedInt8(-9), "-9"},
+ {"%v", renamedInt16(10), "10"},
+ {"%v", renamedInt32(-11), "-11"},
+ {"%X", renamedInt64(255), "FF"},
+ {"%v", renamedUint(13), "13"},
+ {"%o", renamedUint8(14), "16"},
+ {"%X", renamedUint16(15), "F"},
+ {"%d", renamedUint32(16), "16"},
+ {"%X", renamedUint64(17), "11"},
+ {"%o", renamedUintptr(18), "22"},
+ {"%x", renamedString("thing"), "7468696e67"},
+ {"%d", renamedBytes([]byte{1, 2, 15}), `[1 2 15]`},
+ {"%q", renamedBytes([]byte("hello")), `"hello"`},
+ {"%x", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "68656c6c6f"},
+ {"%X", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "68656C6C6F"},
+ {"%s", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "hello"},
+ {"%q", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, `"hello"`},
+ {"%v", renamedFloat32(22), "22"},
+ {"%v", renamedFloat64(33), "33"},
+ {"%v", renamedComplex64(3 + 4i), "(3+4i)"},
+ {"%v", renamedComplex128(4 - 3i), "(4-3i)"},
+
+ // Formatter
+ {"%x", F(1), "<x=F(1)>"},
+ {"%x", G(2), "2"},
+ {"%+v", S{F(4), G(5)}, "{F:<v=F(4)> G:5}"},
+
+ // GoStringer
+ {"%#v", G(6), "GoString(6)"},
+ {"%#v", S{F(7), G(8)}, "fmt_test.S{F:<v=F(7)>, G:GoString(8)}"},
+
+ // %T
+ {"%T", byte(0), "uint8"},
+ {"%T", reflect.ValueOf(nil), "reflect.Value"},
+ {"%T", (4 - 3i), "complex128"},
+ {"%T", renamedComplex128(4 - 3i), "fmt_test.renamedComplex128"},
+ {"%T", intVar, "int"},
+ {"%6T", &intVar, " *int"},
+ {"%10T", nil, " <nil>"},
+ {"%-10T", nil, "<nil> "},
+
+ // %p with pointers
+ {"%p", (*int)(nil), "0x0"},
+ {"%#p", (*int)(nil), "0"},
+ {"%p", &intVar, "0xPTR"},
+ {"%#p", &intVar, "PTR"},
+ {"%p", &array, "0xPTR"},
+ {"%p", &slice, "0xPTR"},
+ {"%8.2p", (*int)(nil), " 0x00"},
+ {"%-20.16p", &intVar, "0xPTR "},
+ // %p on non-pointers
+ {"%p", make(chan int), "0xPTR"},
+ {"%p", make(map[int]int), "0xPTR"},
+ {"%p", func() {}, "0xPTR"},
+ {"%p", 27, "%!p(int=27)"}, // not a pointer at all
+ {"%p", nil, "%!p(<nil>)"}, // nil on its own has no type ...
+ {"%#p", nil, "%!p(<nil>)"}, // ... and hence is not a pointer type.
+ // pointers with specified base
+ {"%b", &intVar, "PTR_b"},
+ {"%d", &intVar, "PTR_d"},
+ {"%o", &intVar, "PTR_o"},
+ {"%x", &intVar, "PTR_x"},
+ {"%X", &intVar, "PTR_X"},
+ // %v on pointers
+ {"%v", nil, "<nil>"},
+ {"%#v", nil, "<nil>"},
+ {"%v", (*int)(nil), "<nil>"},
+ {"%#v", (*int)(nil), "(*int)(nil)"},
+ {"%v", &intVar, "0xPTR"},
+ {"%#v", &intVar, "(*int)(0xPTR)"},
+ {"%8.2v", (*int)(nil), " <nil>"},
+ {"%-20.16v", &intVar, "0xPTR "},
+ // string method on pointer
+ {"%s", &pValue, "String(p)"}, // String method...
+ {"%p", &pValue, "0xPTR"}, // ... is not called with %p.
+
+ // %d on Stringer should give integer if possible
+ {"%s", time.Time{}.Month(), "January"},
+ {"%d", time.Time{}.Month(), "1"},
+
+ // erroneous things
+ {"", nil, "%!(EXTRA <nil>)"},
+ {"", 2, "%!(EXTRA int=2)"},
+ {"no args", "hello", "no args%!(EXTRA string=hello)"},
+ {"%s %", "hello", "hello %!(NOVERB)"},
+ {"%s %.2", "hello", "hello %!(NOVERB)"},
+ {"%017091901790959340919092959340919017929593813360", 0, "%!(NOVERB)%!(EXTRA int=0)"},
+ {"%184467440737095516170v", 0, "%!(NOVERB)%!(EXTRA int=0)"},
+ // Extra argument errors should format without flags set.
+ {"%010.2", "12345", "%!(NOVERB)%!(EXTRA string=12345)"},
+
+ // The "<nil>" show up because maps are printed by
+ // first obtaining a list of keys and then looking up
+ // each key. Since NaNs can be map keys but cannot
+ // be fetched directly, the lookup fails and returns a
+ // zero reflect.Value, which formats as <nil>.
+ // This test is just to check that it shows the two NaNs at all.
+ // {"%v", map[float64]int{NaN: 1, NaN: 2}, "map[NaN:<nil> NaN:<nil>]"},
+
+ // Comparison of padding rules with C printf.
+ /*
+ C program:
+ #include <stdio.h>
+
+ char *format[] = {
+ "[%.2f]",
+ "[% .2f]",
+ "[%+.2f]",
+ "[%7.2f]",
+ "[% 7.2f]",
+ "[%+7.2f]",
+ "[% +7.2f]",
+ "[%07.2f]",
+ "[% 07.2f]",
+ "[%+07.2f]",
+ "[% +07.2f]"
+ };
+
+ int main(void) {
+ int i;
+ for(i = 0; i < 11; i++) {
+ printf("%s: ", format[i]);
+ printf(format[i], 1.0);
+ printf(" ");
+ printf(format[i], -1.0);
+ printf("\n");
+ }
+ }
+
+ Output:
+ [%.2f]: [1.00] [-1.00]
+ [% .2f]: [ 1.00] [-1.00]
+ [%+.2f]: [+1.00] [-1.00]
+ [%7.2f]: [ 1.00] [ -1.00]
+ [% 7.2f]: [ 1.00] [ -1.00]
+ [%+7.2f]: [ +1.00] [ -1.00]
+ [% +7.2f]: [ +1.00] [ -1.00]
+ [%07.2f]: [0001.00] [-001.00]
+ [% 07.2f]: [ 001.00] [-001.00]
+ [%+07.2f]: [+001.00] [-001.00]
+ [% +07.2f]: [+001.00] [-001.00]
+
+ */
+ {"%.2f", 1.0, "1.00"},
+ {"%.2f", -1.0, "-1.00"},
+ {"% .2f", 1.0, " 1.00"},
+ {"% .2f", -1.0, "-1.00"},
+ {"%+.2f", 1.0, "+1.00"},
+ {"%+.2f", -1.0, "-1.00"},
+ {"%7.2f", 1.0, " 1.00"},
+ {"%7.2f", -1.0, " -1.00"},
+ {"% 7.2f", 1.0, " 1.00"},
+ {"% 7.2f", -1.0, " -1.00"},
+ {"%+7.2f", 1.0, " +1.00"},
+ {"%+7.2f", -1.0, " -1.00"},
+ {"% +7.2f", 1.0, " +1.00"},
+ {"% +7.2f", -1.0, " -1.00"},
+ {"%07.2f", 1.0, "0001.00"},
+ {"%07.2f", -1.0, "-001.00"},
+ {"% 07.2f", 1.0, " 001.00"},
+ {"% 07.2f", -1.0, "-001.00"},
+ {"%+07.2f", 1.0, "+001.00"},
+ {"%+07.2f", -1.0, "-001.00"},
+ {"% +07.2f", 1.0, "+001.00"},
+ {"% +07.2f", -1.0, "-001.00"},
+
+ // Complex numbers: exhaustively tested in TestComplexFormatting.
+ {"%7.2f", 1 + 2i, "( 1.00 +2.00i)"},
+ {"%+07.2f", -1 - 2i, "(-001.00-002.00i)"},
+
+ // Use spaces instead of zero if padding to the right.
+ {"%0-5s", "abc", "abc "},
+ {"%-05.1f", 1.0, "1.0 "},
+
+ // float and complex formatting should not change the padding width
+ // for other elements. See issue 14642.
+ {"%06v", []interface{}{+10.0, 10}, "[000010 000010]"},
+ {"%06v", []interface{}{-10.0, 10}, "[-00010 000010]"},
+ {"%06v", []interface{}{+10.0 + 10i, 10}, "[(000010+00010i) 000010]"},
+ {"%06v", []interface{}{-10.0 + 10i, 10}, "[(-00010+00010i) 000010]"},
+
+ // integer formatting should not alter padding for other elements.
+ {"%03.6v", []interface{}{1, 2.0, "x"}, "[000001 002 00x]"},
+ {"%03.0v", []interface{}{0, 2.0, "x"}, "[ 002 000]"},
+
+ // Complex fmt used to leave the plus flag set for future entries in the array
+ // causing +2+0i and +3+0i instead of 2+0i and 3+0i.
+ {"%v", []complex64{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"},
+ {"%v", []complex128{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"},
+
+ // Incomplete format specification caused crash.
+ {"%.", 3, "%!.(int=3)"},
+
+ // Padding for complex numbers. Has been bad, then fixed, then bad again.
+ {"%+10.2f", +104.66 + 440.51i, "( +104.66 +440.51i)"},
+ {"%+10.2f", -104.66 + 440.51i, "( -104.66 +440.51i)"},
+ {"%+10.2f", +104.66 - 440.51i, "( +104.66 -440.51i)"},
+ {"%+10.2f", -104.66 - 440.51i, "( -104.66 -440.51i)"},
+ {"%+010.2f", +104.66 + 440.51i, "(+000104.66+000440.51i)"},
+ {"%+010.2f", -104.66 + 440.51i, "(-000104.66+000440.51i)"},
+ {"%+010.2f", +104.66 - 440.51i, "(+000104.66-000440.51i)"},
+ {"%+010.2f", -104.66 - 440.51i, "(-000104.66-000440.51i)"},
+
+ // []T where type T is a byte with a Stringer method.
+ {"%v", byteStringerSlice, "[X X X X X]"},
+ {"%s", byteStringerSlice, "hello"},
+ {"%q", byteStringerSlice, "\"hello\""},
+ {"%x", byteStringerSlice, "68656c6c6f"},
+ {"%X", byteStringerSlice, "68656C6C6F"},
+ {"%#v", byteStringerSlice, "[]fmt_test.byteStringer{0x68, 0x65, 0x6c, 0x6c, 0x6f}"},
+
+ // And the same for Formatter.
+ {"%v", byteFormatterSlice, "[X X X X X]"},
+ {"%s", byteFormatterSlice, "hello"},
+ {"%q", byteFormatterSlice, "\"hello\""},
+ {"%x", byteFormatterSlice, "68656c6c6f"},
+ {"%X", byteFormatterSlice, "68656C6C6F"},
+ // This next case seems wrong, but the docs say the Formatter wins here.
+ {"%#v", byteFormatterSlice, "[]fmt_test.byteFormatter{X, X, X, X, X}"},
+
+ // pp.WriteString
+ {"%s", writeStringFormatter(""), "******"},
+ {"%s", writeStringFormatter("xyz"), "***xyz***"},
+ {"%s", writeStringFormatter("⌘/⌘"), "***⌘/⌘***"},
+
+ // reflect.Value handled specially in Go 1.5, making it possible to
+ // see inside non-exported fields (which cannot be accessed with Interface()).
+ // Issue 8965.
+ {"%v", reflect.ValueOf(A{}).Field(0).String(), "<int Value>"}, // Equivalent to the old way.
+ {"%v", reflect.ValueOf(A{}).Field(0), "0"}, // Sees inside the field.
+
+ // verbs apply to the extracted value too.
+ {"%s", reflect.ValueOf("hello"), "hello"},
+ {"%q", reflect.ValueOf("hello"), `"hello"`},
+ {"%#04x", reflect.ValueOf(256), "0x0100"},
+
+ // invalid reflect.Value doesn't crash.
+ {"%v", reflect.Value{}, "<invalid reflect.Value>"},
+ {"%v", &reflect.Value{}, "<invalid Value>"},
+ {"%v", SI{reflect.Value{}}, "{<invalid Value>}"},
+
+ // Tests to check that not supported verbs generate an error string.
+ {"%☠", nil, "%!☠(<nil>)"},
+ {"%☠", interface{}(nil), "%!☠(<nil>)"},
+ {"%☠", int(0), "%!☠(int=0)"},
+ {"%☠", uint(0), "%!☠(uint=0)"},
+ {"%☠", []byte{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"},
+ {"%☠", []uint8{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"},
+ {"%☠", [1]byte{0}, "[%!☠(uint8=0)]"},
+ {"%☠", [1]uint8{0}, "[%!☠(uint8=0)]"},
+ {"%☠", "hello", "%!☠(string=hello)"},
+ {"%☠", 1.2345678, "%!☠(float64=1.2345678)"},
+ {"%☠", float32(1.2345678), "%!☠(float32=1.2345678)"},
+ {"%☠", 1.2345678 + 1.2345678i, "%!☠(complex128=(1.2345678+1.2345678i))"},
+ {"%☠", complex64(1.2345678 + 1.2345678i), "%!☠(complex64=(1.2345678+1.2345678i))"},
+ {"%☠", &intVar, "%!☠(*int=0xPTR)"},
+ {"%☠", make(chan int), "%!☠(chan int=0xPTR)"},
+ {"%☠", func() {}, "%!☠(func()=0xPTR)"},
+ {"%☠", reflect.ValueOf(renamedInt(0)), "%!☠(fmt_test.renamedInt=0)"},
+ {"%☠", SI{renamedInt(0)}, "{%!☠(fmt_test.renamedInt=0)}"},
+ {"%☠", &[]interface{}{I(1), G(2)}, "&[%!☠(fmt_test.I=1) %!☠(fmt_test.G=2)]"},
+ {"%☠", SI{&[]interface{}{I(1), G(2)}}, "{%!☠(*[]interface {}=&[1 2])}"},
+ {"%☠", reflect.Value{}, "<invalid reflect.Value>"},
+ // {"%☠", map[float64]int{NaN: 1}, "map[%!☠(float64=NaN):%!☠(<nil>)]"},
+}
+
+// zeroFill generates zero-filled strings of the specified width. The length
+// of the suffix (but not the prefix) is compensated for in the width calculation.
+func zeroFill(prefix string, width int, suffix string) string {
+ return prefix + strings.Repeat("0", width-len(suffix)) + suffix
+}
+
+func TestSprintf(t *testing.T) {
+ for _, tt := range fmtTests {
+ s := Sprintf(tt.fmt, tt.val)
+ i := strings.Index(tt.out, "PTR")
+ if i >= 0 && i < len(s) {
+ var pattern, chars string
+ switch {
+ case strings.HasPrefix(tt.out[i:], "PTR_b"):
+ pattern = "PTR_b"
+ chars = "01"
+ case strings.HasPrefix(tt.out[i:], "PTR_o"):
+ pattern = "PTR_o"
+ chars = "01234567"
+ case strings.HasPrefix(tt.out[i:], "PTR_d"):
+ pattern = "PTR_d"
+ chars = "0123456789"
+ case strings.HasPrefix(tt.out[i:], "PTR_x"):
+ pattern = "PTR_x"
+ chars = "0123456789abcdef"
+ case strings.HasPrefix(tt.out[i:], "PTR_X"):
+ pattern = "PTR_X"
+ chars = "0123456789ABCDEF"
+ default:
+ pattern = "PTR"
+ chars = "0123456789abcdefABCDEF"
+ }
+ p := s[:i] + pattern
+ for j := i; j < len(s); j++ {
+ if !strings.ContainsRune(chars, rune(s[j])) {
+ p += s[j:]
+ break
+ }
+ }
+ s = p
+ }
+ if s != tt.out {
+ if _, ok := tt.val.(string); ok {
+ // Don't requote the already-quoted strings.
+ // It's too confusing to read the errors.
+ t.Errorf("Sprintf(%q, %q) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out)
+ } else {
+ t.Errorf("Sprintf(%q, %v) = %q want %q", tt.fmt, tt.val, s, tt.out)
+ }
+ }
+ }
+}
+
+// TestComplexFormatting checks that a complex always formats to the same
+// thing as if done by hand with two singleton prints.
+func TestComplexFormatting(t *testing.T) {
+ var yesNo = []bool{true, false}
+ var values = []float64{1, 0, -1, posInf, negInf, NaN}
+ for _, plus := range yesNo {
+ for _, zero := range yesNo {
+ for _, space := range yesNo {
+ for _, char := range "fFeEgG" {
+ realFmt := "%"
+ if zero {
+ realFmt += "0"
+ }
+ if space {
+ realFmt += " "
+ }
+ if plus {
+ realFmt += "+"
+ }
+ realFmt += "10.2"
+ realFmt += string(char)
+ // Imaginary part always has a sign, so force + and ignore space.
+ imagFmt := "%"
+ if zero {
+ imagFmt += "0"
+ }
+ imagFmt += "+"
+ imagFmt += "10.2"
+ imagFmt += string(char)
+ for _, realValue := range values {
+ for _, imagValue := range values {
+ one := Sprintf(realFmt, complex(realValue, imagValue))
+ two := Sprintf("("+realFmt+imagFmt+"i)", realValue, imagValue)
+ if one != two {
+ t.Error(f, one, two)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+type SE []interface{} // slice of empty; notational compactness.
+
+var reorderTests = []struct {
+ fmt string
+ val SE
+ out string
+}{
+ {"%[1]d", SE{1}, "1"},
+ {"%[2]d", SE{2, 1}, "1"},
+ {"%[2]d %[1]d", SE{1, 2}, "2 1"},
+ {"%[2]*[1]d", SE{2, 5}, " 2"},
+ {"%6.2f", SE{12.0}, " 12.00"}, // Explicit version of next line.
+ {"%[3]*.[2]*[1]f", SE{12.0, 2, 6}, " 12.00"},
+ {"%[1]*.[2]*[3]f", SE{6, 2, 12.0}, " 12.00"},
+ {"%10f", SE{12.0}, " 12.000000"},
+ {"%[1]*[3]f", SE{10, 99, 12.0}, " 12.000000"},
+ {"%.6f", SE{12.0}, "12.000000"}, // Explicit version of next line.
+ {"%.[1]*[3]f", SE{6, 99, 12.0}, "12.000000"},
+ {"%6.f", SE{12.0}, " 12"}, // // Explicit version of next line; empty precision means zero.
+ {"%[1]*.[3]f", SE{6, 3, 12.0}, " 12"},
+ // An actual use! Print the same arguments twice.
+ {"%d %d %d %#[1]o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015"},
+
+ // Erroneous cases.
+ {"%[d", SE{2, 1}, "%!d(BADINDEX)"},
+ {"%]d", SE{2, 1}, "%!](int=2)d%!(EXTRA int=1)"},
+ {"%[]d", SE{2, 1}, "%!d(BADINDEX)"},
+ {"%[-3]d", SE{2, 1}, "%!d(BADINDEX)"},
+ {"%[99]d", SE{2, 1}, "%!d(BADINDEX)"},
+ {"%[3]", SE{2, 1}, "%!(NOVERB)"},
+ {"%[1].2d", SE{5, 6}, "%!d(BADINDEX)"},
+ {"%[1]2d", SE{2, 1}, "%!d(BADINDEX)"},
+ {"%3.[2]d", SE{7}, "%!d(BADINDEX)"},
+ {"%.[2]d", SE{7}, "%!d(BADINDEX)"},
+ {"%d %d %d %#[1]o %#o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015 %!o(MISSING)"},
+ {"%[5]d %[2]d %d", SE{1, 2, 3}, "%!d(BADINDEX) 2 3"},
+ {"%d %[3]d %d", SE{1, 2}, "1 %!d(BADINDEX) 2"}, // Erroneous index does not affect sequence.
+ {"%.[]", SE{}, "%!](BADINDEX)"}, // Issue 10675
+ {"%.-3d", SE{42}, "%!-(int=42)3d"}, // TODO: Should this set return better error messages?
+ {"%2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"},
+ {"%-2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"},
+ {"%.2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"},
+}
+
+func TestReorder(t *testing.T) {
+ for _, tt := range reorderTests {
+ s := Sprintf(tt.fmt, tt.val...)
+ if s != tt.out {
+ t.Errorf("Sprintf(%q, %v) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out)
+ } else {
+ }
+ }
+}
+
+func BenchmarkSprintfPadding(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%16f", 1.0)
+ }
+ })
+}
+
+func BenchmarkSprintfEmpty(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("")
+ }
+ })
+}
+
+func BenchmarkSprintfString(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%s", "hello")
+ }
+ })
+}
+
+func BenchmarkSprintfTruncateString(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%.3s", "ζ—₯本θͺžζ—₯本θͺžζ—₯本θͺž")
+ }
+ })
+}
+
+func BenchmarkSprintfSlowParsingPath(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%.v", nil)
+ }
+ })
+}
+
+func BenchmarkSprintfQuoteString(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%q", "ζ—₯本θͺžζ—₯本θͺžζ—₯本θͺž")
+ }
+ })
+}
+
+func BenchmarkSprintfInt(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%d", 5)
+ }
+ })
+}
+
+func BenchmarkSprintfIntInt(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%d %d", 5, 6)
+ }
+ })
+}
+
+func BenchmarkSprintfPrefixedInt(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("This is some meaningless prefix text that needs to be scanned %d", 6)
+ }
+ })
+}
+
+func BenchmarkSprintfFloat(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%g", 5.23184)
+ }
+ })
+}
+
+func BenchmarkSprintfComplex(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%f", 5.23184+5.23184i)
+ }
+ })
+}
+
+func BenchmarkSprintfBoolean(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%t", true)
+ }
+ })
+}
+
+func BenchmarkSprintfHexString(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("% #x", "0123456789abcdef")
+ }
+ })
+}
+
+func BenchmarkSprintfHexBytes(b *testing.B) {
+ data := []byte("0123456789abcdef")
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("% #x", data)
+ }
+ })
+}
+
+func BenchmarkSprintfBytes(b *testing.B) {
+ data := []byte("0123456789abcdef")
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%v", data)
+ }
+ })
+}
+
+func BenchmarkSprintfStringer(b *testing.B) {
+ stringer := I(12345)
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%v", stringer)
+ }
+ })
+}
+
+func BenchmarkSprintfStructure(b *testing.B) {
+ s := &[]interface{}{SI{12345}, map[int]string{0: "hello"}}
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%#v", s)
+ }
+ })
+}
+
+func BenchmarkManyArgs(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ var buf bytes.Buffer
+ for pb.Next() {
+ buf.Reset()
+ Fprintf(&buf, "%2d/%2d/%2d %d:%d:%d %s %s\n", 3, 4, 5, 11, 12, 13, "hello", "world")
+ }
+ })
+}
+
+func BenchmarkFprintInt(b *testing.B) {
+ var buf bytes.Buffer
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ Fprint(&buf, 123456)
+ }
+}
+
+func BenchmarkFprintfBytes(b *testing.B) {
+ data := []byte(string("0123456789"))
+ var buf bytes.Buffer
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ Fprintf(&buf, "%s", data)
+ }
+}
+
+func BenchmarkFprintIntNoAlloc(b *testing.B) {
+ var x interface{} = 123456
+ var buf bytes.Buffer
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ Fprint(&buf, x)
+ }
+}
+
+var mallocBuf bytes.Buffer
+var mallocPointer *int // A pointer so we know the interface value won't allocate.
+
+var mallocTest = []struct {
+ count int
+ desc string
+ fn func()
+}{
+ {0, `Sprintf("")`, func() { Sprintf("") }},
+ {1, `Sprintf("xxx")`, func() { Sprintf("xxx") }},
+ {2, `Sprintf("%x")`, func() { Sprintf("%x", 7) }},
+ {2, `Sprintf("%s")`, func() { Sprintf("%s", "hello") }},
+ {3, `Sprintf("%x %x")`, func() { Sprintf("%x %x", 7, 112) }},
+ {2, `Sprintf("%g")`, func() { Sprintf("%g", float32(3.14159)) }}, // TODO: Can this be 1?
+ {1, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%s", "hello") }},
+ // If the interface value doesn't need to allocate, amortized allocation overhead should be zero.
+ {0, `Fprintf(buf, "%x %x %x")`, func() {
+ mallocBuf.Reset()
+ Fprintf(&mallocBuf, "%x %x %x", mallocPointer, mallocPointer, mallocPointer)
+ }},
+}
+
+var _ bytes.Buffer
+
+func TestCountMallocs(t *testing.T) {
+ switch {
+ case testing.Short():
+ t.Skip("skipping malloc count in short mode")
+ case runtime.GOMAXPROCS(0) > 1:
+ t.Skip("skipping; GOMAXPROCS>1")
+ }
+ for _, mt := range mallocTest {
+ mallocs := testing.AllocsPerRun(100, mt.fn)
+ if got, max := mallocs, float64(mt.count); got > max {
+ t.Errorf("%s: got %v allocs, want <=%v", mt.desc, got, max)
+ }
+ }
+}
+
+type flagPrinter struct{}
+
+func (flagPrinter) Format(f State, c rune) {
+ s := "%"
+ for i := 0; i < 128; i++ {
+ if f.Flag(i) {
+ s += string(rune(i))
+ }
+ }
+ if w, ok := f.Width(); ok {
+ s += Sprintf("%d", w)
+ }
+ if p, ok := f.Precision(); ok {
+ s += Sprintf(".%d", p)
+ }
+ s += string(c)
+ io.WriteString(f, "["+s+"]")
+}
+
+var flagtests = []struct {
+ in string
+ out string
+}{
+ {"%a", "[%a]"},
+ {"%-a", "[%-a]"},
+ {"%+a", "[%+a]"},
+ {"%#a", "[%#a]"},
+ {"% a", "[% a]"},
+ {"%0a", "[%0a]"},
+ {"%1.2a", "[%1.2a]"},
+ {"%-1.2a", "[%-1.2a]"},
+ {"%+1.2a", "[%+1.2a]"},
+ {"%-+1.2a", "[%+-1.2a]"},
+ {"%-+1.2abc", "[%+-1.2a]bc"},
+ {"%-1.2abc", "[%-1.2a]bc"},
+}
+
+func TestFlagParser(t *testing.T) {
+ var flagprinter flagPrinter
+ for _, tt := range flagtests {
+ s := Sprintf(tt.in, &flagprinter)
+ if s != tt.out {
+ t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)
+ }
+ }
+}
+
+func TestStructPrinter(t *testing.T) {
+ type T struct {
+ a string
+ b string
+ c int
+ }
+ var s T
+ s.a = "abc"
+ s.b = "def"
+ s.c = 123
+ var tests = []struct {
+ fmt string
+ out string
+ }{
+ {"%v", "{abc def 123}"},
+ {"%+v", "{a:abc b:def c:123}"},
+ {"%#v", `fmt_test.T{a:"abc", b:"def", c:123}`},
+ }
+ for _, tt := range tests {
+ out := Sprintf(tt.fmt, s)
+ if out != tt.out {
+ t.Errorf("Sprintf(%q, s) = %#q, want %#q", tt.fmt, out, tt.out)
+ }
+ // The same but with a pointer.
+ out = Sprintf(tt.fmt, &s)
+ if out != "&"+tt.out {
+ t.Errorf("Sprintf(%q, &s) = %#q, want %#q", tt.fmt, out, "&"+tt.out)
+ }
+ }
+}
+
+func TestSlicePrinter(t *testing.T) {
+ slice := []int{}
+ s := Sprint(slice)
+ if s != "[]" {
+ t.Errorf("empty slice printed as %q not %q", s, "[]")
+ }
+ slice = []int{1, 2, 3}
+ s = Sprint(slice)
+ if s != "[1 2 3]" {
+ t.Errorf("slice: got %q expected %q", s, "[1 2 3]")
+ }
+ s = Sprint(&slice)
+ if s != "&[1 2 3]" {
+ t.Errorf("&slice: got %q expected %q", s, "&[1 2 3]")
+ }
+}
+
+// presentInMap checks map printing using substrings so we don't depend on the
+// print order.
+func presentInMap(s string, a []string, t *testing.T) {
+ for i := 0; i < len(a); i++ {
+ loc := strings.Index(s, a[i])
+ if loc < 0 {
+ t.Errorf("map print: expected to find %q in %q", a[i], s)
+ }
+ // make sure the match ends here
+ loc += len(a[i])
+ if loc >= len(s) || (s[loc] != ' ' && s[loc] != ']') {
+ t.Errorf("map print: %q not properly terminated in %q", a[i], s)
+ }
+ }
+}
+
+func TestMapPrinter(t *testing.T) {
+ m0 := make(map[int]string)
+ s := Sprint(m0)
+ if s != "map[]" {
+ t.Errorf("empty map printed as %q not %q", s, "map[]")
+ }
+ m1 := map[int]string{1: "one", 2: "two", 3: "three"}
+ a := []string{"1:one", "2:two", "3:three"}
+ presentInMap(Sprintf("%v", m1), a, t)
+ presentInMap(Sprint(m1), a, t)
+ // Pointer to map prints the same but with initial &.
+ if !strings.HasPrefix(Sprint(&m1), "&") {
+ t.Errorf("no initial & for address of map")
+ }
+ presentInMap(Sprintf("%v", &m1), a, t)
+ presentInMap(Sprint(&m1), a, t)
+}
+
+func TestEmptyMap(t *testing.T) {
+ const emptyMapStr = "map[]"
+ var m map[string]int
+ s := Sprint(m)
+ if s != emptyMapStr {
+ t.Errorf("nil map printed as %q not %q", s, emptyMapStr)
+ }
+ m = make(map[string]int)
+ s = Sprint(m)
+ if s != emptyMapStr {
+ t.Errorf("empty map printed as %q not %q", s, emptyMapStr)
+ }
+}
+
+// TestBlank checks that Sprint (and hence Print, Fprint) puts spaces in the
+// right places, that is, between arg pairs in which neither is a string.
+func TestBlank(t *testing.T) {
+ got := Sprint("<", 1, ">:", 1, 2, 3, "!")
+ expect := "<1>:1 2 3!"
+ if got != expect {
+ t.Errorf("got %q expected %q", got, expect)
+ }
+}
+
+// TestBlankln checks that Sprintln (and hence Println, Fprintln) puts spaces in
+// the right places, that is, between all arg pairs.
+func TestBlankln(t *testing.T) {
+ got := Sprintln("<", 1, ">:", 1, 2, 3, "!")
+ expect := "< 1 >: 1 2 3 !\n"
+ if got != expect {
+ t.Errorf("got %q expected %q", got, expect)
+ }
+}
+
+// TestFormatterPrintln checks Formatter with Sprint, Sprintln, Sprintf.
+func TestFormatterPrintln(t *testing.T) {
+ f := F(1)
+ expect := "<v=F(1)>\n"
+ s := Sprint(f, "\n")
+ if s != expect {
+ t.Errorf("Sprint wrong with Formatter: expected %q got %q", expect, s)
+ }
+ s = Sprintln(f)
+ if s != expect {
+ t.Errorf("Sprintln wrong with Formatter: expected %q got %q", expect, s)
+ }
+ s = Sprintf("%v\n", f)
+ if s != expect {
+ t.Errorf("Sprintf wrong with Formatter: expected %q got %q", expect, s)
+ }
+}
+
+func args(a ...interface{}) []interface{} { return a }
+
+var startests = []struct {
+ fmt string
+ in []interface{}
+ out string
+}{
+ {"%*d", args(4, 42), " 42"},
+ {"%-*d", args(4, 42), "42 "},
+ {"%*d", args(-4, 42), "42 "},
+ {"%-*d", args(-4, 42), "42 "},
+ {"%.*d", args(4, 42), "0042"},
+ {"%*.*d", args(8, 4, 42), " 0042"},
+ {"%0*d", args(4, 42), "0042"},
+ // Some non-int types for width. (Issue 10732).
+ {"%0*d", args(uint(4), 42), "0042"},
+ {"%0*d", args(uint64(4), 42), "0042"},
+ {"%0*d", args('\x04', 42), "0042"},
+ {"%0*d", args(uintptr(4), 42), "0042"},
+
+ // erroneous
+ {"%*d", args(nil, 42), "%!(BADWIDTH)42"},
+ {"%*d", args(int(1e7), 42), "%!(BADWIDTH)42"},
+ {"%*d", args(int(-1e7), 42), "%!(BADWIDTH)42"},
+ {"%.*d", args(nil, 42), "%!(BADPREC)42"},
+ {"%.*d", args(-1, 42), "%!(BADPREC)42"},
+ {"%.*d", args(int(1e7), 42), "%!(BADPREC)42"},
+ {"%.*d", args(uint(1e7), 42), "%!(BADPREC)42"},
+ {"%.*d", args(uint64(1<<63), 42), "%!(BADPREC)42"}, // Huge negative (-inf).
+ {"%.*d", args(uint64(1<<64-1), 42), "%!(BADPREC)42"}, // Small negative (-1).
+ {"%*d", args(5, "foo"), "%!d(string= foo)"},
+ {"%*% %d", args(20, 5), "% 5"},
+ {"%*", args(4), "%!(NOVERB)"},
+}
+
+func TestWidthAndPrecision(t *testing.T) {
+ for i, tt := range startests {
+ s := Sprintf(tt.fmt, tt.in...)
+ if s != tt.out {
+ t.Errorf("#%d: %q: got %q expected %q", i, tt.fmt, s, tt.out)
+ }
+ }
+}
+
+// PanicS is a type that panics in String.
+type PanicS struct {
+ message interface{}
+}
+
+// Value receiver.
+func (p PanicS) String() string {
+ panic(p.message)
+}
+
+// PanicGo is a type that panics in GoString.
+type PanicGo struct {
+ message interface{}
+}
+
+// Value receiver.
+func (p PanicGo) GoString() string {
+ panic(p.message)
+}
+
+// PanicF is a type that panics in Format.
+type PanicF struct {
+ message interface{}
+}
+
+// Value receiver.
+func (p PanicF) Format(f State, c rune) {
+ panic(p.message)
+}
+
+var panictests = []struct {
+ fmt string
+ in interface{}
+ out string
+}{
+ // String
+ {"%s", (*PanicS)(nil), "<nil>"}, // nil pointer special case
+ {"%s", PanicS{io.ErrUnexpectedEOF}, "%!s(PANIC=unexpected EOF)"},
+ {"%s", PanicS{3}, "%!s(PANIC=3)"},
+ // GoString
+ {"%#v", (*PanicGo)(nil), "<nil>"}, // nil pointer special case
+ {"%#v", PanicGo{io.ErrUnexpectedEOF}, "%!v(PANIC=unexpected EOF)"},
+ {"%#v", PanicGo{3}, "%!v(PANIC=3)"},
+ // Issue 18282. catchPanic should not clear fmtFlags permanently.
+ {"%#v", []interface{}{PanicGo{3}, PanicGo{3}}, "[]interface {}{%!v(PANIC=3), %!v(PANIC=3)}"},
+ // Format
+ {"%s", (*PanicF)(nil), "<nil>"}, // nil pointer special case
+ {"%s", PanicF{io.ErrUnexpectedEOF}, "%!s(PANIC=unexpected EOF)"},
+ {"%s", PanicF{3}, "%!s(PANIC=3)"},
+}
+
+var stripMethod = strings.NewReplacer(
+ "PANIC=String method: ", "PANIC=",
+ "PANIC=GoString method: ", "PANIC=",
+ "PANIC=Format method: ", "PANIC=",
+)
+
+func TestPanics(t *testing.T) {
+ for i, tt := range panictests {
+ s := Sprintf(tt.fmt, tt.in)
+ s = stripMethod.Replace(s)
+ if s != tt.out {
+ t.Errorf("%d: %q: got %q expected %q", i, tt.fmt, s, tt.out)
+ }
+ }
+}
+
+// recurCount tests that erroneous String routine doesn't cause fatal recursion.
+var recurCount = 0
+
+type Recur struct {
+ i int
+ failed *bool
+}
+
+func (r *Recur) String() string {
+ if recurCount++; recurCount > 10 {
+ *r.failed = true
+ return "FAIL"
+ }
+ // This will call badVerb. Before the fix, that would cause us to recur into
+ // this routine to print %!p(value). Now we don't call the user's method
+ // during an error.
+ return Sprintf("recur@%p value: %d", r, r.i)
+}
+
+func TestBadVerbRecursion(t *testing.T) {
+ failed := false
+ r := &Recur{3, &failed}
+ Sprintf("recur@%p value: %d\n", &r, r.i)
+ if failed {
+ t.Error("fail with pointer")
+ }
+ failed = false
+ r = &Recur{4, &failed}
+ Sprintf("recur@%p, value: %d\n", r, r.i)
+ if failed {
+ t.Error("fail with value")
+ }
+}
+
+/*
+func TestIsSpace(t *testing.T) {
+ // This tests the internal isSpace function.
+ // IsSpace = isSpace is defined in export_test.go.
+ for i := rune(0); i <= unicode.MaxRune; i++ {
+ if IsSpace(i) != unicode.IsSpace(i) {
+ t.Errorf("isSpace(%U) = %v, want %v", i, IsSpace(i), unicode.IsSpace(i))
+ }
+ }
+}
+*/
+
+func hideFromVet(s string) string { return s }
+
+func TestNilDoesNotBecomeTyped(t *testing.T) {
+ type A struct{}
+ type B struct{}
+ var a *A = nil
+ var b B = B{}
+ got := Sprintf(hideFromVet("%s %s %s %s %s"), nil, a, nil, b, nil)
+ const expect = "%!s(<nil>) %!s(*fmt_test.A=<nil>) %!s(<nil>) {} %!s(<nil>)"
+ if got != expect {
+ t.Errorf("expected:\n\t%q\ngot:\n\t%q", expect, got)
+ }
+}
+
+var formatterFlagTests = []struct {
+ in string
+ val interface{}
+ out string
+}{
+ // scalar values with the (unused by fmt) 'a' verb.
+ {"%a", flagPrinter{}, "[%a]"},
+ {"%-a", flagPrinter{}, "[%-a]"},
+ {"%+a", flagPrinter{}, "[%+a]"},
+ {"%#a", flagPrinter{}, "[%#a]"},
+ {"% a", flagPrinter{}, "[% a]"},
+ {"%0a", flagPrinter{}, "[%0a]"},
+ {"%1.2a", flagPrinter{}, "[%1.2a]"},
+ {"%-1.2a", flagPrinter{}, "[%-1.2a]"},
+ {"%+1.2a", flagPrinter{}, "[%+1.2a]"},
+ {"%-+1.2a", flagPrinter{}, "[%+-1.2a]"},
+ {"%-+1.2abc", flagPrinter{}, "[%+-1.2a]bc"},
+ {"%-1.2abc", flagPrinter{}, "[%-1.2a]bc"},
+
+ // composite values with the 'a' verb
+ {"%a", [1]flagPrinter{}, "[[%a]]"},
+ {"%-a", [1]flagPrinter{}, "[[%-a]]"},
+ {"%+a", [1]flagPrinter{}, "[[%+a]]"},
+ {"%#a", [1]flagPrinter{}, "[[%#a]]"},
+ {"% a", [1]flagPrinter{}, "[[% a]]"},
+ {"%0a", [1]flagPrinter{}, "[[%0a]]"},
+ {"%1.2a", [1]flagPrinter{}, "[[%1.2a]]"},
+ {"%-1.2a", [1]flagPrinter{}, "[[%-1.2a]]"},
+ {"%+1.2a", [1]flagPrinter{}, "[[%+1.2a]]"},
+ {"%-+1.2a", [1]flagPrinter{}, "[[%+-1.2a]]"},
+ {"%-+1.2abc", [1]flagPrinter{}, "[[%+-1.2a]]bc"},
+ {"%-1.2abc", [1]flagPrinter{}, "[[%-1.2a]]bc"},
+
+ // simple values with the 'v' verb
+ {"%v", flagPrinter{}, "[%v]"},
+ {"%-v", flagPrinter{}, "[%-v]"},
+ {"%+v", flagPrinter{}, "[%+v]"},
+ {"%#v", flagPrinter{}, "[%#v]"},
+ {"% v", flagPrinter{}, "[% v]"},
+ {"%0v", flagPrinter{}, "[%0v]"},
+ {"%1.2v", flagPrinter{}, "[%1.2v]"},
+ {"%-1.2v", flagPrinter{}, "[%-1.2v]"},
+ {"%+1.2v", flagPrinter{}, "[%+1.2v]"},
+ {"%-+1.2v", flagPrinter{}, "[%+-1.2v]"},
+ {"%-+1.2vbc", flagPrinter{}, "[%+-1.2v]bc"},
+ {"%-1.2vbc", flagPrinter{}, "[%-1.2v]bc"},
+
+ // composite values with the 'v' verb.
+ {"%v", [1]flagPrinter{}, "[[%v]]"},
+ {"%-v", [1]flagPrinter{}, "[[%-v]]"},
+ {"%+v", [1]flagPrinter{}, "[[%+v]]"},
+ {"%#v", [1]flagPrinter{}, "[1]fmt_test.flagPrinter{[%#v]}"},
+ {"% v", [1]flagPrinter{}, "[[% v]]"},
+ {"%0v", [1]flagPrinter{}, "[[%0v]]"},
+ {"%1.2v", [1]flagPrinter{}, "[[%1.2v]]"},
+ {"%-1.2v", [1]flagPrinter{}, "[[%-1.2v]]"},
+ {"%+1.2v", [1]flagPrinter{}, "[[%+1.2v]]"},
+ {"%-+1.2v", [1]flagPrinter{}, "[[%+-1.2v]]"},
+ {"%-+1.2vbc", [1]flagPrinter{}, "[[%+-1.2v]]bc"},
+ {"%-1.2vbc", [1]flagPrinter{}, "[[%-1.2v]]bc"},
+}
+
+func TestFormatterFlags(t *testing.T) {
+ for _, tt := range formatterFlagTests {
+ s := Sprintf(tt.in, tt.val)
+ if s != tt.out {
+ t.Errorf("Sprintf(%q, %T) = %q, want %q", tt.in, tt.val, s, tt.out)
+ }
+ }
+}
+
+/*
+func TestParsenum(t *testing.T) {
+ testCases := []struct {
+ s string
+ start, end int
+ num int
+ isnum bool
+ newi int
+ }{
+ {"a123", 0, 4, 0, false, 0},
+ {"1234", 1, 1, 0, false, 1},
+ {"123a", 0, 4, 123, true, 3},
+ {"12a3", 0, 4, 12, true, 2},
+ {"1234", 0, 4, 1234, true, 4},
+ {"1a234", 1, 3, 0, false, 1},
+ }
+ for _, tt := range testCases {
+ num, isnum, newi := Parsenum(tt.s, tt.start, tt.end)
+ if num != tt.num || isnum != tt.isnum || newi != tt.newi {
+ t.Errorf("parsenum(%q, %d, %d) = %d, %v, %d, want %d, %v, %d", tt.s, tt.start, tt.end, num, isnum, newi, tt.num, tt.isnum, tt.newi)
+ }
+ }
+}
+*/
diff --git a/errors/fmt/format.go b/errors/fmt/format.go
new file mode 100644
index 0000000..0390f71
--- /dev/null
+++ b/errors/fmt/format.go
@@ -0,0 +1,542 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+import (
+ "strconv"
+ "unicode/utf8"
+)
+
+const (
+ ldigits = "0123456789abcdefx"
+ udigits = "0123456789ABCDEFX"
+)
+
+const (
+ signed = true
+ unsigned = false
+)
+
+// flags placed in a separate struct for easy clearing.
+type fmtFlags struct {
+ widPresent bool
+ precPresent bool
+ minus bool
+ plus bool
+ sharp bool
+ space bool
+ zero bool
+
+ // For the formats %+v %#v, we set the plusV/sharpV flags
+ // and clear the plus/sharp flags since %+v and %#v are in effect
+ // different, flagless formats set at the top level.
+ plusV bool
+ sharpV bool
+
+ // error-related flags.
+ inDetail bool
+ needNewline bool
+}
+
+// A fmt is the raw formatter used by Printf etc.
+// It prints into a buffer that must be set up separately.
+type fmt struct {
+ buf *buffer
+
+ fmtFlags
+
+ wid int // width
+ prec int // precision
+
+ // intbuf is large enough to store %b of an int64 with a sign and
+ // avoids padding at the end of the struct on 32 bit architectures.
+ intbuf [68]byte
+}
+
+func (f *fmt) clearflags() {
+ f.fmtFlags = fmtFlags{}
+}
+
+func (f *fmt) init(buf *buffer) {
+ f.buf = buf
+ f.clearflags()
+}
+
+// writePadding generates n bytes of padding.
+func (f *fmt) writePadding(n int) {
+ if n <= 0 { // No padding bytes needed.
+ return
+ }
+ buf := *f.buf
+ oldLen := len(buf)
+ newLen := oldLen + n
+ // Make enough room for padding.
+ if newLen > cap(buf) {
+ buf = make(buffer, cap(buf)*2+n)
+ copy(buf, *f.buf)
+ }
+ // Decide which byte the padding should be filled with.
+ padByte := byte(' ')
+ if f.zero {
+ padByte = byte('0')
+ }
+ // Fill padding with padByte.
+ padding := buf[oldLen:newLen]
+ for i := range padding {
+ padding[i] = padByte
+ }
+ *f.buf = buf[:newLen]
+}
+
+// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus).
+func (f *fmt) pad(b []byte) {
+ if !f.widPresent || f.wid == 0 {
+ f.buf.Write(b)
+ return
+ }
+ width := f.wid - utf8.RuneCount(b)
+ if !f.minus {
+ // left padding
+ f.writePadding(width)
+ f.buf.Write(b)
+ } else {
+ // right padding
+ f.buf.Write(b)
+ f.writePadding(width)
+ }
+}
+
+// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus).
+func (f *fmt) padString(s string) {
+ if !f.widPresent || f.wid == 0 {
+ f.buf.WriteString(s)
+ return
+ }
+ width := f.wid - utf8.RuneCountInString(s)
+ if !f.minus {
+ // left padding
+ f.writePadding(width)
+ f.buf.WriteString(s)
+ } else {
+ // right padding
+ f.buf.WriteString(s)
+ f.writePadding(width)
+ }
+}
+
+// fmtBoolean formats a boolean.
+func (f *fmt) fmtBoolean(v bool) {
+ if v {
+ f.padString("true")
+ } else {
+ f.padString("false")
+ }
+}
+
+// fmtUnicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'".
+func (f *fmt) fmtUnicode(u uint64) {
+ buf := f.intbuf[0:]
+
+ // With default precision set the maximum needed buf length is 18
+ // for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits
+ // into the already allocated intbuf with a capacity of 68 bytes.
+ prec := 4
+ if f.precPresent && f.prec > 4 {
+ prec = f.prec
+ // Compute space needed for "U+" , number, " '", character, "'".
+ width := 2 + prec + 2 + utf8.UTFMax + 1
+ if width > len(buf) {
+ buf = make([]byte, width)
+ }
+ }
+
+ // Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left.
+ i := len(buf)
+
+ // For %#U we want to add a space and a quoted character at the end of the buffer.
+ if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) {
+ i--
+ buf[i] = '\''
+ i -= utf8.RuneLen(rune(u))
+ utf8.EncodeRune(buf[i:], rune(u))
+ i--
+ buf[i] = '\''
+ i--
+ buf[i] = ' '
+ }
+ // Format the Unicode code point u as a hexadecimal number.
+ for u >= 16 {
+ i--
+ buf[i] = udigits[u&0xF]
+ prec--
+ u >>= 4
+ }
+ i--
+ buf[i] = udigits[u]
+ prec--
+ // Add zeros in front of the number until requested precision is reached.
+ for prec > 0 {
+ i--
+ buf[i] = '0'
+ prec--
+ }
+ // Add a leading "U+".
+ i--
+ buf[i] = '+'
+ i--
+ buf[i] = 'U'
+
+ oldZero := f.zero
+ f.zero = false
+ f.pad(buf[i:])
+ f.zero = oldZero
+}
+
+// fmtInteger formats signed and unsigned integers.
+func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, digits string) {
+ negative := isSigned && int64(u) < 0
+ if negative {
+ u = -u
+ }
+
+ buf := f.intbuf[0:]
+ // The already allocated f.intbuf with a capacity of 68 bytes
+ // is large enough for integer formatting when no precision or width is set.
+ if f.widPresent || f.precPresent {
+ // Account 3 extra bytes for possible addition of a sign and "0x".
+ width := 3 + f.wid + f.prec // wid and prec are always positive.
+ if width > len(buf) {
+ // We're going to need a bigger boat.
+ buf = make([]byte, width)
+ }
+ }
+
+ // Two ways to ask for extra leading zero digits: %.3d or %03d.
+ // If both are specified the f.zero flag is ignored and
+ // padding with spaces is used instead.
+ prec := 0
+ if f.precPresent {
+ prec = f.prec
+ // Precision of 0 and value of 0 means "print nothing" but padding.
+ if prec == 0 && u == 0 {
+ oldZero := f.zero
+ f.zero = false
+ f.writePadding(f.wid)
+ f.zero = oldZero
+ return
+ }
+ } else if f.zero && f.widPresent {
+ prec = f.wid
+ if negative || f.plus || f.space {
+ prec-- // leave room for sign
+ }
+ }
+
+ // Because printing is easier right-to-left: format u into buf, ending at buf[i].
+ // We could make things marginally faster by splitting the 32-bit case out
+ // into a separate block but it's not worth the duplication, so u has 64 bits.
+ i := len(buf)
+ // Use constants for the division and modulo for more efficient code.
+ // Switch cases ordered by popularity.
+ switch base {
+ case 10:
+ for u >= 10 {
+ i--
+ next := u / 10
+ buf[i] = byte('0' + u - next*10)
+ u = next
+ }
+ case 16:
+ for u >= 16 {
+ i--
+ buf[i] = digits[u&0xF]
+ u >>= 4
+ }
+ case 8:
+ for u >= 8 {
+ i--
+ buf[i] = byte('0' + u&7)
+ u >>= 3
+ }
+ case 2:
+ for u >= 2 {
+ i--
+ buf[i] = byte('0' + u&1)
+ u >>= 1
+ }
+ default:
+ panic("fmt: unknown base; can't happen")
+ }
+ i--
+ buf[i] = digits[u]
+ for i > 0 && prec > len(buf)-i {
+ i--
+ buf[i] = '0'
+ }
+
+ // Various prefixes: 0x, -, etc.
+ if f.sharp {
+ switch base {
+ case 8:
+ if buf[i] != '0' {
+ i--
+ buf[i] = '0'
+ }
+ case 16:
+ // Add a leading 0x or 0X.
+ i--
+ buf[i] = digits[16]
+ i--
+ buf[i] = '0'
+ }
+ }
+
+ if negative {
+ i--
+ buf[i] = '-'
+ } else if f.plus {
+ i--
+ buf[i] = '+'
+ } else if f.space {
+ i--
+ buf[i] = ' '
+ }
+
+ // Left padding with zeros has already been handled like precision earlier
+ // or the f.zero flag is ignored due to an explicitly set precision.
+ oldZero := f.zero
+ f.zero = false
+ f.pad(buf[i:])
+ f.zero = oldZero
+}
+
+// truncate truncates the string to the specified precision, if present.
+func (f *fmt) truncate(s string) string {
+ if f.precPresent {
+ n := f.prec
+ for i := range s {
+ n--
+ if n < 0 {
+ return s[:i]
+ }
+ }
+ }
+ return s
+}
+
+// fmtS formats a string.
+func (f *fmt) fmtS(s string) {
+ s = f.truncate(s)
+ f.padString(s)
+}
+
+// fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes.
+func (f *fmt) fmtSbx(s string, b []byte, digits string) {
+ length := len(b)
+ if b == nil {
+ // No byte slice present. Assume string s should be encoded.
+ length = len(s)
+ }
+ // Set length to not process more bytes than the precision demands.
+ if f.precPresent && f.prec < length {
+ length = f.prec
+ }
+ // Compute width of the encoding taking into account the f.sharp and f.space flag.
+ width := 2 * length
+ if width > 0 {
+ if f.space {
+ // Each element encoded by two hexadecimals will get a leading 0x or 0X.
+ if f.sharp {
+ width *= 2
+ }
+ // Elements will be separated by a space.
+ width += length - 1
+ } else if f.sharp {
+ // Only a leading 0x or 0X will be added for the whole string.
+ width += 2
+ }
+ } else { // The byte slice or string that should be encoded is empty.
+ if f.widPresent {
+ f.writePadding(f.wid)
+ }
+ return
+ }
+ // Handle padding to the left.
+ if f.widPresent && f.wid > width && !f.minus {
+ f.writePadding(f.wid - width)
+ }
+ // Write the encoding directly into the output buffer.
+ buf := *f.buf
+ if f.sharp {
+ // Add leading 0x or 0X.
+ buf = append(buf, '0', digits[16])
+ }
+ var c byte
+ for i := 0; i < length; i++ {
+ if f.space && i > 0 {
+ // Separate elements with a space.
+ buf = append(buf, ' ')
+ if f.sharp {
+ // Add leading 0x or 0X for each element.
+ buf = append(buf, '0', digits[16])
+ }
+ }
+ if b != nil {
+ c = b[i] // Take a byte from the input byte slice.
+ } else {
+ c = s[i] // Take a byte from the input string.
+ }
+ // Encode each byte as two hexadecimal digits.
+ buf = append(buf, digits[c>>4], digits[c&0xF])
+ }
+ *f.buf = buf
+ // Handle padding to the right.
+ if f.widPresent && f.wid > width && f.minus {
+ f.writePadding(f.wid - width)
+ }
+}
+
+// fmtSx formats a string as a hexadecimal encoding of its bytes.
+func (f *fmt) fmtSx(s, digits string) {
+ f.fmtSbx(s, nil, digits)
+}
+
+// fmtBx formats a byte slice as a hexadecimal encoding of its bytes.
+func (f *fmt) fmtBx(b []byte, digits string) {
+ f.fmtSbx("", b, digits)
+}
+
+// fmtQ formats a string as a double-quoted, escaped Go string constant.
+// If f.sharp is set a raw (backquoted) string may be returned instead
+// if the string does not contain any control characters other than tab.
+func (f *fmt) fmtQ(s string) {
+ s = f.truncate(s)
+ if f.sharp && strconv.CanBackquote(s) {
+ f.padString("`" + s + "`")
+ return
+ }
+ buf := f.intbuf[:0]
+ if f.plus {
+ f.pad(strconv.AppendQuoteToASCII(buf, s))
+ } else {
+ f.pad(strconv.AppendQuote(buf, s))
+ }
+}
+
+// fmtC formats an integer as a Unicode character.
+// If the character is not valid Unicode, it will print '\ufffd'.
+func (f *fmt) fmtC(c uint64) {
+ r := rune(c)
+ if c > utf8.MaxRune {
+ r = utf8.RuneError
+ }
+ buf := f.intbuf[:0]
+ w := utf8.EncodeRune(buf[:utf8.UTFMax], r)
+ f.pad(buf[:w])
+}
+
+// fmtQc formats an integer as a single-quoted, escaped Go character constant.
+// If the character is not valid Unicode, it will print '\ufffd'.
+func (f *fmt) fmtQc(c uint64) {
+ r := rune(c)
+ if c > utf8.MaxRune {
+ r = utf8.RuneError
+ }
+ buf := f.intbuf[:0]
+ if f.plus {
+ f.pad(strconv.AppendQuoteRuneToASCII(buf, r))
+ } else {
+ f.pad(strconv.AppendQuoteRune(buf, r))
+ }
+}
+
+// fmtFloat formats a float64. It assumes that verb is a valid format specifier
+// for strconv.AppendFloat and therefore fits into a byte.
+func (f *fmt) fmtFloat(v float64, size int, verb rune, prec int) {
+ // Explicit precision in format specifier overrules default precision.
+ if f.precPresent {
+ prec = f.prec
+ }
+ // Format number, reserving space for leading + sign if needed.
+ num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)
+ if num[1] == '-' || num[1] == '+' {
+ num = num[1:]
+ } else {
+ num[0] = '+'
+ }
+ // f.space means to add a leading space instead of a "+" sign unless
+ // the sign is explicitly asked for by f.plus.
+ if f.space && num[0] == '+' && !f.plus {
+ num[0] = ' '
+ }
+ // Special handling for infinities and NaN,
+ // which don't look like a number so shouldn't be padded with zeros.
+ if num[1] == 'I' || num[1] == 'N' {
+ oldZero := f.zero
+ f.zero = false
+ // Remove sign before NaN if not asked for.
+ if num[1] == 'N' && !f.space && !f.plus {
+ num = num[1:]
+ }
+ f.pad(num)
+ f.zero = oldZero
+ return
+ }
+ // The sharp flag forces printing a decimal point for non-binary formats
+ // and retains trailing zeros, which we may need to restore.
+ if f.sharp && verb != 'b' {
+ digits := 0
+ switch verb {
+ case 'v', 'g', 'G':
+ digits = prec
+ // If no precision is set explicitly use a precision of 6.
+ if digits == -1 {
+ digits = 6
+ }
+ }
+
+ // Buffer pre-allocated with enough room for
+ // exponent notations of the form "e+123".
+ var tailBuf [5]byte
+ tail := tailBuf[:0]
+
+ hasDecimalPoint := false
+ // Starting from i = 1 to skip sign at num[0].
+ for i := 1; i < len(num); i++ {
+ switch num[i] {
+ case '.':
+ hasDecimalPoint = true
+ case 'e', 'E':
+ tail = append(tail, num[i:]...)
+ num = num[:i]
+ default:
+ digits--
+ }
+ }
+ if !hasDecimalPoint {
+ num = append(num, '.')
+ }
+ for digits > 0 {
+ num = append(num, '0')
+ digits--
+ }
+ num = append(num, tail...)
+ }
+ // We want a sign if asked for and if the sign is not positive.
+ if f.plus || num[0] != '+' {
+ // If we're zero padding to the left we want the sign before the leading zeros.
+ // Achieve this by writing the sign out and then padding the unsigned number.
+ if f.zero && f.widPresent && f.wid > len(num) {
+ f.buf.WriteByte(num[0])
+ f.writePadding(f.wid - len(num))
+ f.buf.Write(num[1:])
+ return
+ }
+ f.pad(num)
+ return
+ }
+ // No sign to show and the number is positive; just print the unsigned number.
+ f.pad(num[1:])
+}
diff --git a/errors/fmt/format_example_test.go b/errors/fmt/format_example_test.go
new file mode 100644
index 0000000..3728fd4
--- /dev/null
+++ b/errors/fmt/format_example_test.go
@@ -0,0 +1,47 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+ "path/filepath"
+ "regexp"
+
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/fmt"
+)
+
+func baz() error { return errors.New("baz flopped") }
+func bar() error { return fmt.Errorf("bar(nameserver 139): %v", baz()) }
+func foo() error { return fmt.Errorf("foo: %s", bar()) }
+
+func Example_formatting() {
+ err := foo()
+ fmt.Println("Error:")
+ fmt.Printf("%v\n", err)
+ fmt.Println()
+ fmt.Println("Detailed error:")
+ fmt.Println(stripPath(fmt.Sprintf("%+v\n", err)))
+ // Output:
+ // Error:
+ // foo: bar(nameserver 139): baz flopped
+ //
+ // Detailed error:
+ // foo:
+ // golang.org/x/exp/errors/fmt_test.foo
+ // golang.org/x/exp/errors/fmt/format_example_test.go:17
+ // - bar(nameserver 139):
+ // golang.org/x/exp/errors/fmt_test.bar
+ // golang.org/x/exp/errors/fmt/format_example_test.go:16
+ // - baz flopped:
+ // golang.org/x/exp/errors/fmt_test.baz
+ // golang.org/x/exp/errors/fmt/format_example_test.go:15
+}
+
+func stripPath(s string) string {
+ rePath := regexp.MustCompile(`( [^ ]*)/fmt`)
+ s = rePath.ReplaceAllString(s, " golang.org/x/exp/errors/fmt")
+ s = filepath.ToSlash(s)
+ return s
+}
diff --git a/errors/fmt/print.go b/errors/fmt/print.go
new file mode 100644
index 0000000..4a7fa52
--- /dev/null
+++ b/errors/fmt/print.go
@@ -0,0 +1,1131 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+import (
+ gofmt "fmt"
+ "io"
+ "os"
+ "reflect"
+ "sync"
+ "unicode/utf8"
+
+ "golang.org/x/exp/errors"
+)
+
+// Strings for use with buffer.WriteString.
+// This is less overhead than using buffer.Write with byte arrays.
+const (
+ commaSpaceString = ", "
+ nilAngleString = "<nil>"
+ nilParenString = "(nil)"
+ nilString = "nil"
+ mapString = "map["
+ percentBangString = "%!"
+ missingString = "(MISSING)"
+ badIndexString = "(BADINDEX)"
+ panicString = "(PANIC="
+ extraString = "%!(EXTRA "
+ badWidthString = "%!(BADWIDTH)"
+ badPrecString = "%!(BADPREC)"
+ noVerbString = "%!(NOVERB)"
+ invReflectString = "<invalid reflect.Value>"
+)
+
+// These interface need to be aliases so that implementations can be used
+// for different implementations.
+
+// State represents the printer state passed to custom formatters.
+// It provides access to the io.Writer interface plus information about
+// the flags and options for the operand's format specifier.
+type State = gofmt.State
+
+// Formatter is the interface implemented by values with a custom formatter.
+// The implementation of Format may call Sprint(f) or Fprint(f) etc.
+// to generate its output.
+type Formatter = gofmt.Formatter
+
+// Stringer is implemented by any value that has a String method,
+// which defines the β€œnative” format for that value.
+// The String method is used to print values passed as an operand
+// to any format that accepts a string or to an unformatted printer
+// such as Print.
+type Stringer = gofmt.Stringer
+
+// GoStringer is implemented by any value that has a GoString method,
+// which defines the Go syntax for that value.
+// The GoString method is used to print values passed as an operand
+// to a %#v format.
+type GoStringer = gofmt.GoStringer
+
+// Use simple []byte instead of bytes.Buffer to avoid large dependency.
+type buffer []byte
+
+func (b *buffer) Write(p []byte) {
+ *b = append(*b, p...)
+}
+
+func (b *buffer) WriteString(s string) {
+ *b = append(*b, s...)
+}
+
+func (b *buffer) WriteByte(c byte) {
+ *b = append(*b, c)
+}
+
+func (bp *buffer) WriteRune(r rune) {
+ if r < utf8.RuneSelf {
+ *bp = append(*bp, byte(r))
+ return
+ }
+
+ b := *bp
+ n := len(b)
+ for n+utf8.UTFMax > cap(b) {
+ b = append(b, 0)
+ }
+ w := utf8.EncodeRune(b[n:n+utf8.UTFMax], r)
+ *bp = b[:n+w]
+}
+
+// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
+type pp struct {
+ buf buffer
+
+ // arg holds the current item, as an interface{}.
+ arg interface{}
+
+ // value is used instead of arg for reflect values.
+ value reflect.Value
+
+ // fmt is used to format basic items such as integers or strings.
+ fmt fmt
+
+ // reordered records whether the format string used argument reordering.
+ reordered bool
+ // goodArgNum records whether the most recent reordering directive was valid.
+ goodArgNum bool
+ // panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion.
+ panicking bool
+ // erroring is set when printing an error string to guard against calling handleMethods.
+ erroring bool
+}
+
+var ppFree = sync.Pool{
+ New: func() interface{} { return new(pp) },
+}
+
+// newPrinter allocates a new pp struct or grabs a cached one.
+func newPrinter() *pp {
+ p := ppFree.Get().(*pp)
+ p.panicking = false
+ p.erroring = false
+ p.fmt.init(&p.buf)
+ return p
+}
+
+// free saves used pp structs in ppFree; avoids an allocation per invocation.
+func (p *pp) free() {
+ p.buf = p.buf[:0]
+ p.arg = nil
+ p.value = reflect.Value{}
+ ppFree.Put(p)
+}
+
+func (p *pp) Width() (wid int, ok bool) { return p.fmt.wid, p.fmt.widPresent }
+
+func (p *pp) Precision() (prec int, ok bool) { return p.fmt.prec, p.fmt.precPresent }
+
+func (p *pp) Flag(b int) bool {
+ switch b {
+ case '-':
+ return p.fmt.minus
+ case '+':
+ return p.fmt.plus || p.fmt.plusV
+ case '#':
+ return p.fmt.sharp || p.fmt.sharpV
+ case ' ':
+ return p.fmt.space
+ case '0':
+ return p.fmt.zero
+ }
+ return false
+}
+
+// Implement Write so we can call Fprintf on a pp (through State), for
+// recursive use in custom verbs.
+func (p *pp) Write(b []byte) (ret int, err error) {
+ p.buf.Write(b)
+ return len(b), nil
+}
+
+// Implement WriteString so that we can call io.WriteString
+// on a pp (through state), for efficiency.
+func (p *pp) WriteString(s string) (ret int, err error) {
+ p.buf.WriteString(s)
+ return len(s), nil
+}
+
+// These routines end in 'f' and take a format string.
+
+// Fprintf formats according to a format specifier and writes to w.
+// It returns the number of bytes written and any write error encountered.
+func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
+ p := newPrinter()
+ p.doPrintf(format, a)
+ n, err = w.Write(p.buf)
+ p.free()
+ return
+}
+
+// Printf formats according to a format specifier and writes to standard output.
+// It returns the number of bytes written and any write error encountered.
+func Printf(format string, a ...interface{}) (n int, err error) {
+ return Fprintf(os.Stdout, format, a...)
+}
+
+// Sprintf formats according to a format specifier and returns the resulting string.
+func Sprintf(format string, a ...interface{}) string {
+ p := newPrinter()
+ p.doPrintf(format, a)
+ s := string(p.buf)
+ p.free()
+ return s
+}
+
+// These routines do not take a format string
+
+// Fprint formats using the default formats for its operands and writes to w.
+// Spaces are added between operands when neither is a string.
+// It returns the number of bytes written and any write error encountered.
+func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
+ p := newPrinter()
+ p.doPrint(a)
+ n, err = w.Write(p.buf)
+ p.free()
+ return
+}
+
+// Print formats using the default formats for its operands and writes to standard output.
+// Spaces are added between operands when neither is a string.
+// It returns the number of bytes written and any write error encountered.
+func Print(a ...interface{}) (n int, err error) {
+ return Fprint(os.Stdout, a...)
+}
+
+// Sprint formats using the default formats for its operands and returns the resulting string.
+// Spaces are added between operands when neither is a string.
+func Sprint(a ...interface{}) string {
+ p := newPrinter()
+ p.doPrint(a)
+ s := string(p.buf)
+ p.free()
+ return s
+}
+
+// These routines end in 'ln', do not take a format string,
+// always add spaces between operands, and add a newline
+// after the last operand.
+
+// Fprintln formats using the default formats for its operands and writes to w.
+// Spaces are always added between operands and a newline is appended.
+// It returns the number of bytes written and any write error encountered.
+func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
+ p := newPrinter()
+ p.doPrintln(a)
+ n, err = w.Write(p.buf)
+ p.free()
+ return
+}
+
+// Println formats using the default formats for its operands and writes to standard output.
+// Spaces are always added between operands and a newline is appended.
+// It returns the number of bytes written and any write error encountered.
+func Println(a ...interface{}) (n int, err error) {
+ return Fprintln(os.Stdout, a...)
+}
+
+// Sprintln formats using the default formats for its operands and returns the resulting string.
+// Spaces are always added between operands and a newline is appended.
+func Sprintln(a ...interface{}) string {
+ p := newPrinter()
+ p.doPrintln(a)
+ s := string(p.buf)
+ p.free()
+ return s
+}
+
+// getField gets the i'th field of the struct value.
+// If the field is itself is an interface, return a value for
+// the thing inside the interface, not the interface itself.
+func getField(v reflect.Value, i int) reflect.Value {
+ val := v.Field(i)
+ if val.Kind() == reflect.Interface && !val.IsNil() {
+ val = val.Elem()
+ }
+ return val
+}
+
+// tooLarge reports whether the magnitude of the integer is
+// too large to be used as a formatting width or precision.
+func tooLarge(x int) bool {
+ const max int = 1e6
+ return x > max || x < -max
+}
+
+// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present.
+func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
+ if start >= end {
+ return 0, false, end
+ }
+ for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ {
+ if tooLarge(num) {
+ return 0, false, end // Overflow; crazy long number most likely.
+ }
+ num = num*10 + int(s[newi]-'0')
+ isnum = true
+ }
+ return
+}
+
+func (p *pp) unknownType(v reflect.Value) {
+ if !v.IsValid() {
+ p.buf.WriteString(nilAngleString)
+ return
+ }
+ p.buf.WriteByte('?')
+ p.buf.WriteString(v.Type().String())
+ p.buf.WriteByte('?')
+}
+
+func (p *pp) badVerb(verb rune) {
+ p.erroring = true
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(verb)
+ p.buf.WriteByte('(')
+ switch {
+ case p.arg != nil:
+ p.buf.WriteString(reflect.TypeOf(p.arg).String())
+ p.buf.WriteByte('=')
+ p.printArg(p.arg, 'v')
+ case p.value.IsValid():
+ p.buf.WriteString(p.value.Type().String())
+ p.buf.WriteByte('=')
+ p.printValue(p.value, 'v', 0)
+ default:
+ p.buf.WriteString(nilAngleString)
+ }
+ p.buf.WriteByte(')')
+ p.erroring = false
+}
+
+func (p *pp) fmtBool(v bool, verb rune) {
+ switch verb {
+ case 't', 'v':
+ p.fmt.fmtBoolean(v)
+ default:
+ p.badVerb(verb)
+ }
+}
+
+// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or
+// not, as requested, by temporarily setting the sharp flag.
+func (p *pp) fmt0x64(v uint64, leading0x bool) {
+ sharp := p.fmt.sharp
+ p.fmt.sharp = leading0x
+ p.fmt.fmtInteger(v, 16, unsigned, ldigits)
+ p.fmt.sharp = sharp
+}
+
+// fmtInteger formats a signed or unsigned integer.
+func (p *pp) fmtInteger(v uint64, isSigned bool, verb rune) {
+ switch verb {
+ case 'v':
+ if p.fmt.sharpV && !isSigned {
+ p.fmt0x64(v, true)
+ } else {
+ p.fmt.fmtInteger(v, 10, isSigned, ldigits)
+ }
+ case 'd':
+ p.fmt.fmtInteger(v, 10, isSigned, ldigits)
+ case 'b':
+ p.fmt.fmtInteger(v, 2, isSigned, ldigits)
+ case 'o':
+ p.fmt.fmtInteger(v, 8, isSigned, ldigits)
+ case 'x':
+ p.fmt.fmtInteger(v, 16, isSigned, ldigits)
+ case 'X':
+ p.fmt.fmtInteger(v, 16, isSigned, udigits)
+ case 'c':
+ p.fmt.fmtC(v)
+ case 'q':
+ if v <= utf8.MaxRune {
+ p.fmt.fmtQc(v)
+ } else {
+ p.badVerb(verb)
+ }
+ case 'U':
+ p.fmt.fmtUnicode(v)
+ default:
+ p.badVerb(verb)
+ }
+}
+
+// fmtFloat formats a float. The default precision for each verb
+// is specified as last argument in the call to fmt_float.
+func (p *pp) fmtFloat(v float64, size int, verb rune) {
+ switch verb {
+ case 'v':
+ p.fmt.fmtFloat(v, size, 'g', -1)
+ case 'b', 'g', 'G':
+ p.fmt.fmtFloat(v, size, verb, -1)
+ case 'f', 'e', 'E':
+ p.fmt.fmtFloat(v, size, verb, 6)
+ case 'F':
+ p.fmt.fmtFloat(v, size, 'f', 6)
+ default:
+ p.badVerb(verb)
+ }
+}
+
+// fmtComplex formats a complex number v with
+// r = real(v) and j = imag(v) as (r+ji) using
+// fmtFloat for r and j formatting.
+func (p *pp) fmtComplex(v complex128, size int, verb rune) {
+ // Make sure any unsupported verbs are found before the
+ // calls to fmtFloat to not generate an incorrect error string.
+ switch verb {
+ case 'v', 'b', 'g', 'G', 'f', 'F', 'e', 'E':
+ oldPlus := p.fmt.plus
+ p.buf.WriteByte('(')
+ p.fmtFloat(real(v), size/2, verb)
+ // Imaginary part always has a sign.
+ p.fmt.plus = true
+ p.fmtFloat(imag(v), size/2, verb)
+ p.buf.WriteString("i)")
+ p.fmt.plus = oldPlus
+ default:
+ p.badVerb(verb)
+ }
+}
+
+func (p *pp) fmtString(v string, verb rune) {
+ switch verb {
+ case 'v':
+ if p.fmt.sharpV {
+ p.fmt.fmtQ(v)
+ } else {
+ p.fmt.fmtS(v)
+ }
+ case 's':
+ p.fmt.fmtS(v)
+ case 'x':
+ p.fmt.fmtSx(v, ldigits)
+ case 'X':
+ p.fmt.fmtSx(v, udigits)
+ case 'q':
+ p.fmt.fmtQ(v)
+ default:
+ p.badVerb(verb)
+ }
+}
+
+func (p *pp) fmtBytes(v []byte, verb rune, typeString string) {
+ switch verb {
+ case 'v', 'd':
+ if p.fmt.sharpV {
+ p.buf.WriteString(typeString)
+ if v == nil {
+ p.buf.WriteString(nilParenString)
+ return
+ }
+ p.buf.WriteByte('{')
+ for i, c := range v {
+ if i > 0 {
+ p.buf.WriteString(commaSpaceString)
+ }
+ p.fmt0x64(uint64(c), true)
+ }
+ p.buf.WriteByte('}')
+ } else {
+ p.buf.WriteByte('[')
+ for i, c := range v {
+ if i > 0 {
+ p.buf.WriteByte(' ')
+ }
+ p.fmt.fmtInteger(uint64(c), 10, unsigned, ldigits)
+ }
+ p.buf.WriteByte(']')
+ }
+ case 's':
+ p.fmt.fmtS(string(v))
+ case 'x':
+ p.fmt.fmtBx(v, ldigits)
+ case 'X':
+ p.fmt.fmtBx(v, udigits)
+ case 'q':
+ p.fmt.fmtQ(string(v))
+ default:
+ p.printValue(reflect.ValueOf(v), verb, 0)
+ }
+}
+
+func (p *pp) fmtPointer(value reflect.Value, verb rune) {
+ var u uintptr
+ switch value.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
+ u = value.Pointer()
+ default:
+ p.badVerb(verb)
+ return
+ }
+
+ switch verb {
+ case 'v':
+ if p.fmt.sharpV {
+ p.buf.WriteByte('(')
+ p.buf.WriteString(value.Type().String())
+ p.buf.WriteString(")(")
+ if u == 0 {
+ p.buf.WriteString(nilString)
+ } else {
+ p.fmt0x64(uint64(u), true)
+ }
+ p.buf.WriteByte(')')
+ } else {
+ if u == 0 {
+ p.fmt.padString(nilAngleString)
+ } else {
+ p.fmt0x64(uint64(u), !p.fmt.sharp)
+ }
+ }
+ case 'p':
+ p.fmt0x64(uint64(u), !p.fmt.sharp)
+ case 'b', 'o', 'd', 'x', 'X':
+ p.fmtInteger(uint64(u), unsigned, verb)
+ default:
+ p.badVerb(verb)
+ }
+}
+
+func (p *pp) catchPanic(arg interface{}, verb rune) {
+ if err := recover(); err != nil {
+ // If it's a nil pointer, just say "<nil>". The likeliest causes are a
+ // Stringer that fails to guard against nil or a nil pointer for a
+ // value receiver, and in either case, "<nil>" is a nice result.
+ if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() {
+ p.buf.WriteString(nilAngleString)
+ return
+ }
+ // Otherwise print a concise panic message. Most of the time the panic
+ // value will print itself nicely.
+ if p.panicking {
+ // Nested panics; the recursion in printArg cannot succeed.
+ panic(err)
+ }
+
+ oldFlags := p.fmt.fmtFlags
+ // For this output we want default behavior.
+ p.fmt.clearflags()
+
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(verb)
+ p.buf.WriteString(panicString)
+ p.panicking = true
+ p.printArg(err, 'v')
+ p.panicking = false
+ p.buf.WriteByte(')')
+
+ p.fmt.fmtFlags = oldFlags
+ }
+}
+
+func (p *pp) handleMethods(verb rune) (handled bool) {
+ if p.erroring {
+ return
+ }
+ switch x := p.arg.(type) {
+ case errors.Formatter:
+ handled = true
+ defer p.catchPanic(p.arg, verb)
+ return fmtError(p, verb, x)
+
+ case Formatter:
+ handled = true
+ defer p.catchPanic(p.arg, verb)
+ x.Format(p, verb)
+ return
+
+ case error:
+ handled = true
+ defer p.catchPanic(p.arg, verb)
+ return fmtError(p, verb, x)
+ }
+
+ // If we're doing Go syntax and the argument knows how to supply it, take care of it now.
+ if p.fmt.sharpV {
+ if stringer, ok := p.arg.(GoStringer); ok {
+ handled = true
+ defer p.catchPanic(p.arg, verb)
+ // Print the result of GoString unadorned.
+ p.fmt.fmtS(stringer.GoString())
+ return
+ }
+ } else {
+ // If a string is acceptable according to the format, see if
+ // the value satisfies one of the string-valued interfaces.
+ // Println etc. set verb to %v, which is "stringable".
+ switch verb {
+ case 'v', 's', 'x', 'X', 'q':
+ if v, ok := p.arg.(Stringer); ok {
+ handled = true
+ defer p.catchPanic(p.arg, verb)
+ p.fmtString(v.String(), verb)
+ return
+ }
+ }
+ }
+ return false
+}
+
+func (p *pp) printArg(arg interface{}, verb rune) {
+ p.arg = arg
+ p.value = reflect.Value{}
+
+ if arg == nil {
+ switch verb {
+ case 'T', 'v':
+ p.fmt.padString(nilAngleString)
+ default:
+ p.badVerb(verb)
+ }
+ return
+ }
+
+ // Special processing considerations.
+ // %T (the value's type) and %p (its address) are special; we always do them first.
+ switch verb {
+ case 'T':
+ p.fmt.fmtS(reflect.TypeOf(arg).String())
+ return
+ case 'p':
+ p.fmtPointer(reflect.ValueOf(arg), 'p')
+ return
+ }
+
+ // Some types can be done without reflection.
+ switch f := arg.(type) {
+ case bool:
+ p.fmtBool(f, verb)
+ case float32:
+ p.fmtFloat(float64(f), 32, verb)
+ case float64:
+ p.fmtFloat(f, 64, verb)
+ case complex64:
+ p.fmtComplex(complex128(f), 64, verb)
+ case complex128:
+ p.fmtComplex(f, 128, verb)
+ case int:
+ p.fmtInteger(uint64(f), signed, verb)
+ case int8:
+ p.fmtInteger(uint64(f), signed, verb)
+ case int16:
+ p.fmtInteger(uint64(f), signed, verb)
+ case int32:
+ p.fmtInteger(uint64(f), signed, verb)
+ case int64:
+ p.fmtInteger(uint64(f), signed, verb)
+ case uint:
+ p.fmtInteger(uint64(f), unsigned, verb)
+ case uint8:
+ p.fmtInteger(uint64(f), unsigned, verb)
+ case uint16:
+ p.fmtInteger(uint64(f), unsigned, verb)
+ case uint32:
+ p.fmtInteger(uint64(f), unsigned, verb)
+ case uint64:
+ p.fmtInteger(f, unsigned, verb)
+ case uintptr:
+ p.fmtInteger(uint64(f), unsigned, verb)
+ case string:
+ p.fmtString(f, verb)
+ case []byte:
+ p.fmtBytes(f, verb, "[]byte")
+ case reflect.Value:
+ // Handle extractable values with special methods
+ // since printValue does not handle them at depth 0.
+ if f.IsValid() && f.CanInterface() {
+ p.arg = f.Interface()
+ if p.handleMethods(verb) {
+ return
+ }
+ }
+ p.printValue(f, verb, 0)
+ default:
+ // If the type is not simple, it might have methods.
+ if !p.handleMethods(verb) {
+ // Need to use reflection, since the type had no
+ // interface methods that could be used for formatting.
+ p.printValue(reflect.ValueOf(f), verb, 0)
+ }
+ }
+}
+
+// printValue is similar to printArg but starts with a reflect value, not an interface{} value.
+// It does not handle 'p' and 'T' verbs because these should have been already handled by printArg.
+func (p *pp) printValue(value reflect.Value, verb rune, depth int) {
+ // Handle values with special methods if not already handled by printArg (depth == 0).
+ if depth > 0 && value.IsValid() && value.CanInterface() {
+ p.arg = value.Interface()
+ if p.handleMethods(verb) {
+ return
+ }
+ }
+ p.arg = nil
+ p.value = value
+
+ switch f := value; value.Kind() {
+ case reflect.Invalid:
+ if depth == 0 {
+ p.buf.WriteString(invReflectString)
+ } else {
+ switch verb {
+ case 'v':
+ p.buf.WriteString(nilAngleString)
+ default:
+ p.badVerb(verb)
+ }
+ }
+ case reflect.Bool:
+ p.fmtBool(f.Bool(), verb)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ p.fmtInteger(uint64(f.Int()), signed, verb)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ p.fmtInteger(f.Uint(), unsigned, verb)
+ case reflect.Float32:
+ p.fmtFloat(f.Float(), 32, verb)
+ case reflect.Float64:
+ p.fmtFloat(f.Float(), 64, verb)
+ case reflect.Complex64:
+ p.fmtComplex(f.Complex(), 64, verb)
+ case reflect.Complex128:
+ p.fmtComplex(f.Complex(), 128, verb)
+ case reflect.String:
+ p.fmtString(f.String(), verb)
+ case reflect.Map:
+ if p.fmt.sharpV {
+ p.buf.WriteString(f.Type().String())
+ if f.IsNil() {
+ p.buf.WriteString(nilParenString)
+ return
+ }
+ p.buf.WriteByte('{')
+ } else {
+ p.buf.WriteString(mapString)
+ }
+ keys := f.MapKeys()
+ for i, key := range keys {
+ if i > 0 {
+ if p.fmt.sharpV {
+ p.buf.WriteString(commaSpaceString)
+ } else {
+ p.buf.WriteByte(' ')
+ }
+ }
+ p.printValue(key, verb, depth+1)
+ p.buf.WriteByte(':')
+ p.printValue(f.MapIndex(key), verb, depth+1)
+ }
+ if p.fmt.sharpV {
+ p.buf.WriteByte('}')
+ } else {
+ p.buf.WriteByte(']')
+ }
+ case reflect.Struct:
+ if p.fmt.sharpV {
+ p.buf.WriteString(f.Type().String())
+ }
+ p.buf.WriteByte('{')
+ for i := 0; i < f.NumField(); i++ {
+ if i > 0 {
+ if p.fmt.sharpV {
+ p.buf.WriteString(commaSpaceString)
+ } else {
+ p.buf.WriteByte(' ')
+ }
+ }
+ if p.fmt.plusV || p.fmt.sharpV {
+ if name := f.Type().Field(i).Name; name != "" {
+ p.buf.WriteString(name)
+ p.buf.WriteByte(':')
+ }
+ }
+ p.printValue(getField(f, i), verb, depth+1)
+ }
+ p.buf.WriteByte('}')
+ case reflect.Interface:
+ value := f.Elem()
+ if !value.IsValid() {
+ if p.fmt.sharpV {
+ p.buf.WriteString(f.Type().String())
+ p.buf.WriteString(nilParenString)
+ } else {
+ p.buf.WriteString(nilAngleString)
+ }
+ } else {
+ p.printValue(value, verb, depth+1)
+ }
+ case reflect.Array, reflect.Slice:
+ switch verb {
+ case 's', 'q', 'x', 'X':
+ // Handle byte and uint8 slices and arrays special for the above verbs.
+ t := f.Type()
+ if t.Elem().Kind() == reflect.Uint8 {
+ var bytes []byte
+ if f.Kind() == reflect.Slice {
+ bytes = f.Bytes()
+ } else if f.CanAddr() {
+ bytes = f.Slice(0, f.Len()).Bytes()
+ } else {
+ // We have an array, but we cannot Slice() a non-addressable array,
+ // so we build a slice by hand. This is a rare case but it would be nice
+ // if reflection could help a little more.
+ bytes = make([]byte, f.Len())
+ for i := range bytes {
+ bytes[i] = byte(f.Index(i).Uint())
+ }
+ }
+ p.fmtBytes(bytes, verb, t.String())
+ return
+ }
+ }
+ if p.fmt.sharpV {
+ p.buf.WriteString(f.Type().String())
+ if f.Kind() == reflect.Slice && f.IsNil() {
+ p.buf.WriteString(nilParenString)
+ return
+ }
+ p.buf.WriteByte('{')
+ for i := 0; i < f.Len(); i++ {
+ if i > 0 {
+ p.buf.WriteString(commaSpaceString)
+ }
+ p.printValue(f.Index(i), verb, depth+1)
+ }
+ p.buf.WriteByte('}')
+ } else {
+ p.buf.WriteByte('[')
+ for i := 0; i < f.Len(); i++ {
+ if i > 0 {
+ p.buf.WriteByte(' ')
+ }
+ p.printValue(f.Index(i), verb, depth+1)
+ }
+ p.buf.WriteByte(']')
+ }
+ case reflect.Ptr:
+ // pointer to array or slice or struct? ok at top level
+ // but not embedded (avoid loops)
+ if depth == 0 && f.Pointer() != 0 {
+ switch a := f.Elem(); a.Kind() {
+ case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
+ p.buf.WriteByte('&')
+ p.printValue(a, verb, depth+1)
+ return
+ }
+ }
+ fallthrough
+ case reflect.Chan, reflect.Func, reflect.UnsafePointer:
+ p.fmtPointer(f, verb)
+ default:
+ p.unknownType(f)
+ }
+}
+
+// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type.
+func intFromArg(a []interface{}, argNum int) (num int, isInt bool, newArgNum int) {
+ newArgNum = argNum
+ if argNum < len(a) {
+ num, isInt = a[argNum].(int) // Almost always OK.
+ if !isInt {
+ // Work harder.
+ switch v := reflect.ValueOf(a[argNum]); v.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ n := v.Int()
+ if int64(int(n)) == n {
+ num = int(n)
+ isInt = true
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ n := v.Uint()
+ if int64(n) >= 0 && uint64(int(n)) == n {
+ num = int(n)
+ isInt = true
+ }
+ default:
+ // Already 0, false.
+ }
+ }
+ newArgNum = argNum + 1
+ if tooLarge(num) {
+ num = 0
+ isInt = false
+ }
+ }
+ return
+}
+
+// parseArgNumber returns the value of the bracketed number, minus 1
+// (explicit argument numbers are one-indexed but we want zero-indexed).
+// The opening bracket is known to be present at format[0].
+// The returned values are the index, the number of bytes to consume
+// up to the closing paren, if present, and whether the number parsed
+// ok. The bytes to consume will be 1 if no closing paren is present.
+func parseArgNumber(format string) (index int, wid int, ok bool) {
+ // There must be at least 3 bytes: [n].
+ if len(format) < 3 {
+ return 0, 1, false
+ }
+
+ // Find closing bracket.
+ for i := 1; i < len(format); i++ {
+ if format[i] == ']' {
+ width, ok, newi := parsenum(format, 1, i)
+ if !ok || newi != i {
+ return 0, i + 1, false
+ }
+ return width - 1, i + 1, true // arg numbers are one-indexed and skip paren.
+ }
+ }
+ return 0, 1, false
+}
+
+// argNumber returns the next argument to evaluate, which is either the value of the passed-in
+// argNum or the value of the bracketed integer that begins format[i:]. It also returns
+// the new value of i, that is, the index of the next byte of the format to process.
+func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) {
+ if len(format) <= i || format[i] != '[' {
+ return argNum, i, false
+ }
+ p.reordered = true
+ index, wid, ok := parseArgNumber(format[i:])
+ if ok && 0 <= index && index < numArgs {
+ return index, i + wid, true
+ }
+ p.goodArgNum = false
+ return argNum, i + wid, ok
+}
+
+func (p *pp) badArgNum(verb rune) {
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(verb)
+ p.buf.WriteString(badIndexString)
+}
+
+func (p *pp) missingArg(verb rune) {
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(verb)
+ p.buf.WriteString(missingString)
+}
+
+func (p *pp) doPrintf(format string, a []interface{}) {
+ end := len(format)
+ argNum := 0 // we process one argument per non-trivial format
+ afterIndex := false // previous item in format was an index like [3].
+ p.reordered = false
+formatLoop:
+ for i := 0; i < end; {
+ p.goodArgNum = true
+ lasti := i
+ for i < end && format[i] != '%' {
+ i++
+ }
+ if i > lasti {
+ p.buf.WriteString(format[lasti:i])
+ }
+ if i >= end {
+ // done processing format string
+ break
+ }
+
+ // Process one verb
+ i++
+
+ // Do we have flags?
+ p.fmt.clearflags()
+ simpleFormat:
+ for ; i < end; i++ {
+ c := format[i]
+ switch c {
+ case '#':
+ p.fmt.sharp = true
+ case '0':
+ p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
+ case '+':
+ p.fmt.plus = true
+ case '-':
+ p.fmt.minus = true
+ p.fmt.zero = false // Do not pad with zeros to the right.
+ case ' ':
+ p.fmt.space = true
+ default:
+ // Fast path for common case of ascii lower case simple verbs
+ // without precision or width or argument indices.
+ if 'a' <= c && c <= 'z' && argNum < len(a) {
+ if c == 'v' {
+ // Go syntax
+ p.fmt.sharpV = p.fmt.sharp
+ p.fmt.sharp = false
+ // Struct-field syntax
+ p.fmt.plusV = p.fmt.plus
+ p.fmt.plus = false
+ }
+ p.printArg(a[argNum], rune(c))
+ argNum++
+ i++
+ continue formatLoop
+ }
+ // Format is more complex than simple flags and a verb or is malformed.
+ break simpleFormat
+ }
+ }
+
+ // Do we have an explicit argument index?
+ argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
+
+ // Do we have width?
+ if i < end && format[i] == '*' {
+ i++
+ p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum)
+
+ if !p.fmt.widPresent {
+ p.buf.WriteString(badWidthString)
+ }
+
+ // We have a negative width, so take its value and ensure
+ // that the minus flag is set
+ if p.fmt.wid < 0 {
+ p.fmt.wid = -p.fmt.wid
+ p.fmt.minus = true
+ p.fmt.zero = false // Do not pad with zeros to the right.
+ }
+ afterIndex = false
+ } else {
+ p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end)
+ if afterIndex && p.fmt.widPresent { // "%[3]2d"
+ p.goodArgNum = false
+ }
+ }
+
+ // Do we have precision?
+ if i+1 < end && format[i] == '.' {
+ i++
+ if afterIndex { // "%[3].2d"
+ p.goodArgNum = false
+ }
+ argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
+ if i < end && format[i] == '*' {
+ i++
+ p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum)
+ // Negative precision arguments don't make sense
+ if p.fmt.prec < 0 {
+ p.fmt.prec = 0
+ p.fmt.precPresent = false
+ }
+ if !p.fmt.precPresent {
+ p.buf.WriteString(badPrecString)
+ }
+ afterIndex = false
+ } else {
+ p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end)
+ if !p.fmt.precPresent {
+ p.fmt.prec = 0
+ p.fmt.precPresent = true
+ }
+ }
+ }
+
+ if !afterIndex {
+ argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
+ }
+
+ if i >= end {
+ p.buf.WriteString(noVerbString)
+ break
+ }
+
+ verb, size := rune(format[i]), 1
+ if verb >= utf8.RuneSelf {
+ verb, size = utf8.DecodeRuneInString(format[i:])
+ }
+ i += size
+
+ switch {
+ case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec.
+ p.buf.WriteByte('%')
+ case !p.goodArgNum:
+ p.badArgNum(verb)
+ case argNum >= len(a): // No argument left over to print for the current verb.
+ p.missingArg(verb)
+ case verb == 'v':
+ // Go syntax
+ p.fmt.sharpV = p.fmt.sharp
+ p.fmt.sharp = false
+ // Struct-field syntax
+ p.fmt.plusV = p.fmt.plus
+ p.fmt.plus = false
+ fallthrough
+ default:
+ p.printArg(a[argNum], verb)
+ argNum++
+ }
+ }
+
+ // Check for extra arguments unless the call accessed the arguments
+ // out of order, in which case it's too expensive to detect if they've all
+ // been used and arguably OK if they're not.
+ if !p.reordered && argNum < len(a) {
+ p.fmt.clearflags()
+ p.buf.WriteString(extraString)
+ for i, arg := range a[argNum:] {
+ if i > 0 {
+ p.buf.WriteString(commaSpaceString)
+ }
+ if arg == nil {
+ p.buf.WriteString(nilAngleString)
+ } else {
+ p.buf.WriteString(reflect.TypeOf(arg).String())
+ p.buf.WriteByte('=')
+ p.printArg(arg, 'v')
+ }
+ }
+ p.buf.WriteByte(')')
+ }
+}
+
+func (p *pp) doPrint(a []interface{}) {
+ prevString := false
+ for argNum, arg := range a {
+ isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String
+ // Add a space between two non-string arguments.
+ if argNum > 0 && !isString && !prevString {
+ p.buf.WriteByte(' ')
+ }
+ p.printArg(arg, 'v')
+ prevString = isString
+ }
+}
+
+// doPrintln is like doPrint but always adds a space between arguments
+// and a newline after the last argument.
+func (p *pp) doPrintln(a []interface{}) {
+ for argNum, arg := range a {
+ if argNum > 0 {
+ p.buf.WriteByte(' ')
+ }
+ p.printArg(arg, 'v')
+ }
+ p.buf.WriteByte('\n')
+}
diff --git a/errors/fmt/scan.go b/errors/fmt/scan.go
new file mode 100644
index 0000000..461124b
--- /dev/null
+++ b/errors/fmt/scan.go
@@ -0,0 +1,1201 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt
+
+import (
+ "errors"
+ "io"
+ "math"
+ "os"
+ "reflect"
+ "strconv"
+ "sync"
+ "unicode/utf8"
+)
+
+// ScanState represents the scanner state passed to custom scanners.
+// Scanners may do rune-at-a-time scanning or ask the ScanState
+// to discover the next space-delimited token.
+type ScanState interface {
+ // ReadRune reads the next rune (Unicode code point) from the input.
+ // If invoked during Scanln, Fscanln, or Sscanln, ReadRune() will
+ // return EOF after returning the first '\n' or when reading beyond
+ // the specified width.
+ ReadRune() (r rune, size int, err error)
+ // UnreadRune causes the next call to ReadRune to return the same rune.
+ UnreadRune() error
+ // SkipSpace skips space in the input. Newlines are treated appropriately
+ // for the operation being performed; see the package documentation
+ // for more information.
+ SkipSpace()
+ // Token skips space in the input if skipSpace is true, then returns the
+ // run of Unicode code points c satisfying f(c). If f is nil,
+ // !unicode.IsSpace(c) is used; that is, the token will hold non-space
+ // characters. Newlines are treated appropriately for the operation being
+ // performed; see the package documentation for more information.
+ // The returned slice points to shared data that may be overwritten
+ // by the next call to Token, a call to a Scan function using the ScanState
+ // as input, or when the calling Scan method returns.
+ Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
+ // Width returns the value of the width option and whether it has been set.
+ // The unit is Unicode code points.
+ Width() (wid int, ok bool)
+ // Because ReadRune is implemented by the interface, Read should never be
+ // called by the scanning routines and a valid implementation of
+ // ScanState may choose always to return an error from Read.
+ Read(buf []byte) (n int, err error)
+}
+
+// Scanner is implemented by any value that has a Scan method, which scans
+// the input for the representation of a value and stores the result in the
+// receiver, which must be a pointer to be useful. The Scan method is called
+// for any argument to Scan, Scanf, or Scanln that implements it.
+type Scanner interface {
+ Scan(state ScanState, verb rune) error
+}
+
+// Scan scans text read from standard input, storing successive
+// space-separated values into successive arguments. Newlines count
+// as space. It returns the number of items successfully scanned.
+// If that is less than the number of arguments, err will report why.
+func Scan(a ...interface{}) (n int, err error) {
+ return Fscan(os.Stdin, a...)
+}
+
+// Scanln is similar to Scan, but stops scanning at a newline and
+// after the final item there must be a newline or EOF.
+func Scanln(a ...interface{}) (n int, err error) {
+ return Fscanln(os.Stdin, a...)
+}
+
+// Scanf scans text read from standard input, storing successive
+// space-separated values into successive arguments as determined by
+// the format. It returns the number of items successfully scanned.
+// If that is less than the number of arguments, err will report why.
+// Newlines in the input must match newlines in the format.
+// The one exception: the verb %c always scans the next rune in the
+// input, even if it is a space (or tab etc.) or newline.
+func Scanf(format string, a ...interface{}) (n int, err error) {
+ return Fscanf(os.Stdin, format, a...)
+}
+
+type stringReader string
+
+func (r *stringReader) Read(b []byte) (n int, err error) {
+ n = copy(b, *r)
+ *r = (*r)[n:]
+ if n == 0 {
+ err = io.EOF
+ }
+ return
+}
+
+// Sscan scans the argument string, storing successive space-separated
+// values into successive arguments. Newlines count as space. It
+// returns the number of items successfully scanned. If that is less
+// than the number of arguments, err will report why.
+func Sscan(str string, a ...interface{}) (n int, err error) {
+ return Fscan((*stringReader)(&str), a...)
+}
+
+// Sscanln is similar to Sscan, but stops scanning at a newline and
+// after the final item there must be a newline or EOF.
+func Sscanln(str string, a ...interface{}) (n int, err error) {
+ return Fscanln((*stringReader)(&str), a...)
+}
+
+// Sscanf scans the argument string, storing successive space-separated
+// values into successive arguments as determined by the format. It
+// returns the number of items successfully parsed.
+// Newlines in the input must match newlines in the format.
+func Sscanf(str string, format string, a ...interface{}) (n int, err error) {
+ return Fscanf((*stringReader)(&str), format, a...)
+}
+
+// Fscan scans text read from r, storing successive space-separated
+// values into successive arguments. Newlines count as space. It
+// returns the number of items successfully scanned. If that is less
+// than the number of arguments, err will report why.
+func Fscan(r io.Reader, a ...interface{}) (n int, err error) {
+ s, old := newScanState(r, true, false)
+ n, err = s.doScan(a)
+ s.free(old)
+ return
+}
+
+// Fscanln is similar to Fscan, but stops scanning at a newline and
+// after the final item there must be a newline or EOF.
+func Fscanln(r io.Reader, a ...interface{}) (n int, err error) {
+ s, old := newScanState(r, false, true)
+ n, err = s.doScan(a)
+ s.free(old)
+ return
+}
+
+// Fscanf scans text read from r, storing successive space-separated
+// values into successive arguments as determined by the format. It
+// returns the number of items successfully parsed.
+// Newlines in the input must match newlines in the format.
+func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) {
+ s, old := newScanState(r, false, false)
+ n, err = s.doScanf(format, a)
+ s.free(old)
+ return
+}
+
+// scanError represents an error generated by the scanning software.
+// It's used as a unique signature to identify such errors when recovering.
+type scanError struct {
+ err error
+}
+
+const eof = -1
+
+// ss is the internal implementation of ScanState.
+type ss struct {
+ rs io.RuneScanner // where to read input
+ buf buffer // token accumulator
+ count int // runes consumed so far.
+ atEOF bool // already read EOF
+ ssave
+}
+
+// ssave holds the parts of ss that need to be
+// saved and restored on recursive scans.
+type ssave struct {
+ validSave bool // is or was a part of an actual ss.
+ nlIsEnd bool // whether newline terminates scan
+ nlIsSpace bool // whether newline counts as white space
+ argLimit int // max value of ss.count for this arg; argLimit <= limit
+ limit int // max value of ss.count.
+ maxWid int // width of this arg.
+}
+
+// The Read method is only in ScanState so that ScanState
+// satisfies io.Reader. It will never be called when used as
+// intended, so there is no need to make it actually work.
+func (s *ss) Read(buf []byte) (n int, err error) {
+ return 0, errors.New("ScanState's Read should not be called. Use ReadRune")
+}
+
+func (s *ss) ReadRune() (r rune, size int, err error) {
+ if s.atEOF || s.count >= s.argLimit {
+ err = io.EOF
+ return
+ }
+
+ r, size, err = s.rs.ReadRune()
+ if err == nil {
+ s.count++
+ if s.nlIsEnd && r == '\n' {
+ s.atEOF = true
+ }
+ } else if err == io.EOF {
+ s.atEOF = true
+ }
+ return
+}
+
+func (s *ss) Width() (wid int, ok bool) {
+ if s.maxWid == hugeWid {
+ return 0, false
+ }
+ return s.maxWid, true
+}
+
+// The public method returns an error; this private one panics.
+// If getRune reaches EOF, the return value is EOF (-1).
+func (s *ss) getRune() (r rune) {
+ r, _, err := s.ReadRune()
+ if err != nil {
+ if err == io.EOF {
+ return eof
+ }
+ s.error(err)
+ }
+ return
+}
+
+// mustReadRune turns io.EOF into a panic(io.ErrUnexpectedEOF).
+// It is called in cases such as string scanning where an EOF is a
+// syntax error.
+func (s *ss) mustReadRune() (r rune) {
+ r = s.getRune()
+ if r == eof {
+ s.error(io.ErrUnexpectedEOF)
+ }
+ return
+}
+
+func (s *ss) UnreadRune() error {
+ s.rs.UnreadRune()
+ s.atEOF = false
+ s.count--
+ return nil
+}
+
+func (s *ss) error(err error) {
+ panic(scanError{err})
+}
+
+func (s *ss) errorString(err string) {
+ panic(scanError{errors.New(err)})
+}
+
+func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
+ defer func() {
+ if e := recover(); e != nil {
+ if se, ok := e.(scanError); ok {
+ err = se.err
+ } else {
+ panic(e)
+ }
+ }
+ }()
+ if f == nil {
+ f = notSpace
+ }
+ s.buf = s.buf[:0]
+ tok = s.token(skipSpace, f)
+ return
+}
+
+// space is a copy of the unicode.White_Space ranges,
+// to avoid depending on package unicode.
+var space = [][2]uint16{
+ {0x0009, 0x000d},
+ {0x0020, 0x0020},
+ {0x0085, 0x0085},
+ {0x00a0, 0x00a0},
+ {0x1680, 0x1680},
+ {0x2000, 0x200a},
+ {0x2028, 0x2029},
+ {0x202f, 0x202f},
+ {0x205f, 0x205f},
+ {0x3000, 0x3000},
+}
+
+func isSpace(r rune) bool {
+ if r >= 1<<16 {
+ return false
+ }
+ rx := uint16(r)
+ for _, rng := range space {
+ if rx < rng[0] {
+ return false
+ }
+ if rx <= rng[1] {
+ return true
+ }
+ }
+ return false
+}
+
+// notSpace is the default scanning function used in Token.
+func notSpace(r rune) bool {
+ return !isSpace(r)
+}
+
+// readRune is a structure to enable reading UTF-8 encoded code points
+// from an io.Reader. It is used if the Reader given to the scanner does
+// not already implement io.RuneScanner.
+type readRune struct {
+ reader io.Reader
+ buf [utf8.UTFMax]byte // used only inside ReadRune
+ pending int // number of bytes in pendBuf; only >0 for bad UTF-8
+ pendBuf [utf8.UTFMax]byte // bytes left over
+ peekRune rune // if >=0 next rune; when <0 is ^(previous Rune)
+}
+
+// readByte returns the next byte from the input, which may be
+// left over from a previous read if the UTF-8 was ill-formed.
+func (r *readRune) readByte() (b byte, err error) {
+ if r.pending > 0 {
+ b = r.pendBuf[0]
+ copy(r.pendBuf[0:], r.pendBuf[1:])
+ r.pending--
+ return
+ }
+ n, err := io.ReadFull(r.reader, r.pendBuf[:1])
+ if n != 1 {
+ return 0, err
+ }
+ return r.pendBuf[0], err
+}
+
+// ReadRune returns the next UTF-8 encoded code point from the
+// io.Reader inside r.
+func (r *readRune) ReadRune() (rr rune, size int, err error) {
+ if r.peekRune >= 0 {
+ rr = r.peekRune
+ r.peekRune = ^r.peekRune
+ size = utf8.RuneLen(rr)
+ return
+ }
+ r.buf[0], err = r.readByte()
+ if err != nil {
+ return
+ }
+ if r.buf[0] < utf8.RuneSelf { // fast check for common ASCII case
+ rr = rune(r.buf[0])
+ size = 1 // Known to be 1.
+ // Flip the bits of the rune so it's available to UnreadRune.
+ r.peekRune = ^rr
+ return
+ }
+ var n int
+ for n = 1; !utf8.FullRune(r.buf[:n]); n++ {
+ r.buf[n], err = r.readByte()
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ break
+ }
+ return
+ }
+ }
+ rr, size = utf8.DecodeRune(r.buf[:n])
+ if size < n { // an error, save the bytes for the next read
+ copy(r.pendBuf[r.pending:], r.buf[size:n])
+ r.pending += n - size
+ }
+ // Flip the bits of the rune so it's available to UnreadRune.
+ r.peekRune = ^rr
+ return
+}
+
+func (r *readRune) UnreadRune() error {
+ if r.peekRune >= 0 {
+ return errors.New("fmt: scanning called UnreadRune with no rune available")
+ }
+ // Reverse bit flip of previously read rune to obtain valid >=0 state.
+ r.peekRune = ^r.peekRune
+ return nil
+}
+
+var ssFree = sync.Pool{
+ New: func() interface{} { return new(ss) },
+}
+
+// newScanState allocates a new ss struct or grab a cached one.
+func newScanState(r io.Reader, nlIsSpace, nlIsEnd bool) (s *ss, old ssave) {
+ s = ssFree.Get().(*ss)
+ if rs, ok := r.(io.RuneScanner); ok {
+ s.rs = rs
+ } else {
+ s.rs = &readRune{reader: r, peekRune: -1}
+ }
+ s.nlIsSpace = nlIsSpace
+ s.nlIsEnd = nlIsEnd
+ s.atEOF = false
+ s.limit = hugeWid
+ s.argLimit = hugeWid
+ s.maxWid = hugeWid
+ s.validSave = true
+ s.count = 0
+ return
+}
+
+// free saves used ss structs in ssFree; avoid an allocation per invocation.
+func (s *ss) free(old ssave) {
+ // If it was used recursively, just restore the old state.
+ if old.validSave {
+ s.ssave = old
+ return
+ }
+ // Don't hold on to ss structs with large buffers.
+ if cap(s.buf) > 1024 {
+ return
+ }
+ s.buf = s.buf[:0]
+ s.rs = nil
+ ssFree.Put(s)
+}
+
+// SkipSpace provides Scan methods the ability to skip space and newline
+// characters in keeping with the current scanning mode set by format strings
+// and Scan/Scanln.
+func (s *ss) SkipSpace() {
+ for {
+ r := s.getRune()
+ if r == eof {
+ return
+ }
+ if r == '\r' && s.peek("\n") {
+ continue
+ }
+ if r == '\n' {
+ if s.nlIsSpace {
+ continue
+ }
+ s.errorString("unexpected newline")
+ return
+ }
+ if !isSpace(r) {
+ s.UnreadRune()
+ break
+ }
+ }
+}
+
+// token returns the next space-delimited string from the input. It
+// skips white space. For Scanln, it stops at newlines. For Scan,
+// newlines are treated as spaces.
+func (s *ss) token(skipSpace bool, f func(rune) bool) []byte {
+ if skipSpace {
+ s.SkipSpace()
+ }
+ // read until white space or newline
+ for {
+ r := s.getRune()
+ if r == eof {
+ break
+ }
+ if !f(r) {
+ s.UnreadRune()
+ break
+ }
+ s.buf.WriteRune(r)
+ }
+ return s.buf
+}
+
+var complexError = errors.New("syntax error scanning complex number")
+var boolError = errors.New("syntax error scanning boolean")
+
+func indexRune(s string, r rune) int {
+ for i, c := range s {
+ if c == r {
+ return i
+ }
+ }
+ return -1
+}
+
+// consume reads the next rune in the input and reports whether it is in the ok string.
+// If accept is true, it puts the character into the input token.
+func (s *ss) consume(ok string, accept bool) bool {
+ r := s.getRune()
+ if r == eof {
+ return false
+ }
+ if indexRune(ok, r) >= 0 {
+ if accept {
+ s.buf.WriteRune(r)
+ }
+ return true
+ }
+ if r != eof && accept {
+ s.UnreadRune()
+ }
+ return false
+}
+
+// peek reports whether the next character is in the ok string, without consuming it.
+func (s *ss) peek(ok string) bool {
+ r := s.getRune()
+ if r != eof {
+ s.UnreadRune()
+ }
+ return indexRune(ok, r) >= 0
+}
+
+func (s *ss) notEOF() {
+ // Guarantee there is data to be read.
+ if r := s.getRune(); r == eof {
+ panic(io.EOF)
+ }
+ s.UnreadRune()
+}
+
+// accept checks the next rune in the input. If it's a byte (sic) in the string, it puts it in the
+// buffer and returns true. Otherwise it return false.
+func (s *ss) accept(ok string) bool {
+ return s.consume(ok, true)
+}
+
+// okVerb verifies that the verb is present in the list, setting s.err appropriately if not.
+func (s *ss) okVerb(verb rune, okVerbs, typ string) bool {
+ for _, v := range okVerbs {
+ if v == verb {
+ return true
+ }
+ }
+ s.errorString("bad verb '%" + string(verb) + "' for " + typ)
+ return false
+}
+
+// scanBool returns the value of the boolean represented by the next token.
+func (s *ss) scanBool(verb rune) bool {
+ s.SkipSpace()
+ s.notEOF()
+ if !s.okVerb(verb, "tv", "boolean") {
+ return false
+ }
+ // Syntax-checking a boolean is annoying. We're not fastidious about case.
+ switch s.getRune() {
+ case '0':
+ return false
+ case '1':
+ return true
+ case 't', 'T':
+ if s.accept("rR") && (!s.accept("uU") || !s.accept("eE")) {
+ s.error(boolError)
+ }
+ return true
+ case 'f', 'F':
+ if s.accept("aA") && (!s.accept("lL") || !s.accept("sS") || !s.accept("eE")) {
+ s.error(boolError)
+ }
+ return false
+ }
+ return false
+}
+
+// Numerical elements
+const (
+ binaryDigits = "01"
+ octalDigits = "01234567"
+ decimalDigits = "0123456789"
+ hexadecimalDigits = "0123456789aAbBcCdDeEfF"
+ sign = "+-"
+ period = "."
+ exponent = "eEp"
+)
+
+// getBase returns the numeric base represented by the verb and its digit string.
+func (s *ss) getBase(verb rune) (base int, digits string) {
+ s.okVerb(verb, "bdoUxXv", "integer") // sets s.err
+ base = 10
+ digits = decimalDigits
+ switch verb {
+ case 'b':
+ base = 2
+ digits = binaryDigits
+ case 'o':
+ base = 8
+ digits = octalDigits
+ case 'x', 'X', 'U':
+ base = 16
+ digits = hexadecimalDigits
+ }
+ return
+}
+
+// scanNumber returns the numerical string with specified digits starting here.
+func (s *ss) scanNumber(digits string, haveDigits bool) string {
+ if !haveDigits {
+ s.notEOF()
+ if !s.accept(digits) {
+ s.errorString("expected integer")
+ }
+ }
+ for s.accept(digits) {
+ }
+ return string(s.buf)
+}
+
+// scanRune returns the next rune value in the input.
+func (s *ss) scanRune(bitSize int) int64 {
+ s.notEOF()
+ r := s.getRune()
+ n := uint(bitSize)
+ x := (int64(r) << (64 - n)) >> (64 - n)
+ if x != int64(r) {
+ s.errorString("overflow on character value " + string(r))
+ }
+ return int64(r)
+}
+
+// scanBasePrefix reports whether the integer begins with a 0 or 0x,
+// and returns the base, digit string, and whether a zero was found.
+// It is called only if the verb is %v.
+func (s *ss) scanBasePrefix() (base int, digits string, found bool) {
+ if !s.peek("0") {
+ return 10, decimalDigits, false
+ }
+ s.accept("0")
+ found = true // We've put a digit into the token buffer.
+ // Special cases for '0' && '0x'
+ base, digits = 8, octalDigits
+ if s.peek("xX") {
+ s.consume("xX", false)
+ base, digits = 16, hexadecimalDigits
+ }
+ return
+}
+
+// scanInt returns the value of the integer represented by the next
+// token, checking for overflow. Any error is stored in s.err.
+func (s *ss) scanInt(verb rune, bitSize int) int64 {
+ if verb == 'c' {
+ return s.scanRune(bitSize)
+ }
+ s.SkipSpace()
+ s.notEOF()
+ base, digits := s.getBase(verb)
+ haveDigits := false
+ if verb == 'U' {
+ if !s.consume("U", false) || !s.consume("+", false) {
+ s.errorString("bad unicode format ")
+ }
+ } else {
+ s.accept(sign) // If there's a sign, it will be left in the token buffer.
+ if verb == 'v' {
+ base, digits, haveDigits = s.scanBasePrefix()
+ }
+ }
+ tok := s.scanNumber(digits, haveDigits)
+ i, err := strconv.ParseInt(tok, base, 64)
+ if err != nil {
+ s.error(err)
+ }
+ n := uint(bitSize)
+ x := (i << (64 - n)) >> (64 - n)
+ if x != i {
+ s.errorString("integer overflow on token " + tok)
+ }
+ return i
+}
+
+// scanUint returns the value of the unsigned integer represented
+// by the next token, checking for overflow. Any error is stored in s.err.
+func (s *ss) scanUint(verb rune, bitSize int) uint64 {
+ if verb == 'c' {
+ return uint64(s.scanRune(bitSize))
+ }
+ s.SkipSpace()
+ s.notEOF()
+ base, digits := s.getBase(verb)
+ haveDigits := false
+ if verb == 'U' {
+ if !s.consume("U", false) || !s.consume("+", false) {
+ s.errorString("bad unicode format ")
+ }
+ } else if verb == 'v' {
+ base, digits, haveDigits = s.scanBasePrefix()
+ }
+ tok := s.scanNumber(digits, haveDigits)
+ i, err := strconv.ParseUint(tok, base, 64)
+ if err != nil {
+ s.error(err)
+ }
+ n := uint(bitSize)
+ x := (i << (64 - n)) >> (64 - n)
+ if x != i {
+ s.errorString("unsigned integer overflow on token " + tok)
+ }
+ return i
+}
+
+// floatToken returns the floating-point number starting here, no longer than swid
+// if the width is specified. It's not rigorous about syntax because it doesn't check that
+// we have at least some digits, but Atof will do that.
+func (s *ss) floatToken() string {
+ s.buf = s.buf[:0]
+ // NaN?
+ if s.accept("nN") && s.accept("aA") && s.accept("nN") {
+ return string(s.buf)
+ }
+ // leading sign?
+ s.accept(sign)
+ // Inf?
+ if s.accept("iI") && s.accept("nN") && s.accept("fF") {
+ return string(s.buf)
+ }
+ // digits?
+ for s.accept(decimalDigits) {
+ }
+ // decimal point?
+ if s.accept(period) {
+ // fraction?
+ for s.accept(decimalDigits) {
+ }
+ }
+ // exponent?
+ if s.accept(exponent) {
+ // leading sign?
+ s.accept(sign)
+ // digits?
+ for s.accept(decimalDigits) {
+ }
+ }
+ return string(s.buf)
+}
+
+// complexTokens returns the real and imaginary parts of the complex number starting here.
+// The number might be parenthesized and has the format (N+Ni) where N is a floating-point
+// number and there are no spaces within.
+func (s *ss) complexTokens() (real, imag string) {
+ // TODO: accept N and Ni independently?
+ parens := s.accept("(")
+ real = s.floatToken()
+ s.buf = s.buf[:0]
+ // Must now have a sign.
+ if !s.accept("+-") {
+ s.error(complexError)
+ }
+ // Sign is now in buffer
+ imagSign := string(s.buf)
+ imag = s.floatToken()
+ if !s.accept("i") {
+ s.error(complexError)
+ }
+ if parens && !s.accept(")") {
+ s.error(complexError)
+ }
+ return real, imagSign + imag
+}
+
+// convertFloat converts the string to a float64value.
+func (s *ss) convertFloat(str string, n int) float64 {
+ if p := indexRune(str, 'p'); p >= 0 {
+ // Atof doesn't handle power-of-2 exponents,
+ // but they're easy to evaluate.
+ f, err := strconv.ParseFloat(str[:p], n)
+ if err != nil {
+ // Put full string into error.
+ if e, ok := err.(*strconv.NumError); ok {
+ e.Num = str
+ }
+ s.error(err)
+ }
+ m, err := strconv.Atoi(str[p+1:])
+ if err != nil {
+ // Put full string into error.
+ if e, ok := err.(*strconv.NumError); ok {
+ e.Num = str
+ }
+ s.error(err)
+ }
+ return math.Ldexp(f, m)
+ }
+ f, err := strconv.ParseFloat(str, n)
+ if err != nil {
+ s.error(err)
+ }
+ return f
+}
+
+// scanComplex converts the next token to a complex128 value.
+// The atof argument is a type-specific reader for the underlying type.
+// If we're reading complex64, atof will parse float32s and convert them
+// to float64's to avoid reproducing this code for each complex type.
+func (s *ss) scanComplex(verb rune, n int) complex128 {
+ if !s.okVerb(verb, floatVerbs, "complex") {
+ return 0
+ }
+ s.SkipSpace()
+ s.notEOF()
+ sreal, simag := s.complexTokens()
+ real := s.convertFloat(sreal, n/2)
+ imag := s.convertFloat(simag, n/2)
+ return complex(real, imag)
+}
+
+// convertString returns the string represented by the next input characters.
+// The format of the input is determined by the verb.
+func (s *ss) convertString(verb rune) (str string) {
+ if !s.okVerb(verb, "svqxX", "string") {
+ return ""
+ }
+ s.SkipSpace()
+ s.notEOF()
+ switch verb {
+ case 'q':
+ str = s.quotedString()
+ case 'x', 'X':
+ str = s.hexString()
+ default:
+ str = string(s.token(true, notSpace)) // %s and %v just return the next word
+ }
+ return
+}
+
+// quotedString returns the double- or back-quoted string represented by the next input characters.
+func (s *ss) quotedString() string {
+ s.notEOF()
+ quote := s.getRune()
+ switch quote {
+ case '`':
+ // Back-quoted: Anything goes until EOF or back quote.
+ for {
+ r := s.mustReadRune()
+ if r == quote {
+ break
+ }
+ s.buf.WriteRune(r)
+ }
+ return string(s.buf)
+ case '"':
+ // Double-quoted: Include the quotes and let strconv.Unquote do the backslash escapes.
+ s.buf.WriteByte('"')
+ for {
+ r := s.mustReadRune()
+ s.buf.WriteRune(r)
+ if r == '\\' {
+ // In a legal backslash escape, no matter how long, only the character
+ // immediately after the escape can itself be a backslash or quote.
+ // Thus we only need to protect the first character after the backslash.
+ s.buf.WriteRune(s.mustReadRune())
+ } else if r == '"' {
+ break
+ }
+ }
+ result, err := strconv.Unquote(string(s.buf))
+ if err != nil {
+ s.error(err)
+ }
+ return result
+ default:
+ s.errorString("expected quoted string")
+ }
+ return ""
+}
+
+// hexDigit returns the value of the hexadecimal digit.
+func hexDigit(d rune) (int, bool) {
+ digit := int(d)
+ switch digit {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ return digit - '0', true
+ case 'a', 'b', 'c', 'd', 'e', 'f':
+ return 10 + digit - 'a', true
+ case 'A', 'B', 'C', 'D', 'E', 'F':
+ return 10 + digit - 'A', true
+ }
+ return -1, false
+}
+
+// hexByte returns the next hex-encoded (two-character) byte from the input.
+// It returns ok==false if the next bytes in the input do not encode a hex byte.
+// If the first byte is hex and the second is not, processing stops.
+func (s *ss) hexByte() (b byte, ok bool) {
+ rune1 := s.getRune()
+ if rune1 == eof {
+ return
+ }
+ value1, ok := hexDigit(rune1)
+ if !ok {
+ s.UnreadRune()
+ return
+ }
+ value2, ok := hexDigit(s.mustReadRune())
+ if !ok {
+ s.errorString("illegal hex digit")
+ return
+ }
+ return byte(value1<<4 | value2), true
+}
+
+// hexString returns the space-delimited hexpair-encoded string.
+func (s *ss) hexString() string {
+ s.notEOF()
+ for {
+ b, ok := s.hexByte()
+ if !ok {
+ break
+ }
+ s.buf.WriteByte(b)
+ }
+ if len(s.buf) == 0 {
+ s.errorString("no hex data for %x string")
+ return ""
+ }
+ return string(s.buf)
+}
+
+const (
+ floatVerbs = "beEfFgGv"
+
+ hugeWid = 1 << 30
+
+ intBits = 32 << (^uint(0) >> 63)
+ uintptrBits = 32 << (^uintptr(0) >> 63)
+)
+
+// scanOne scans a single value, deriving the scanner from the type of the argument.
+func (s *ss) scanOne(verb rune, arg interface{}) {
+ s.buf = s.buf[:0]
+ var err error
+ // If the parameter has its own Scan method, use that.
+ if v, ok := arg.(Scanner); ok {
+ err = v.Scan(s, verb)
+ if err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ s.error(err)
+ }
+ return
+ }
+
+ switch v := arg.(type) {
+ case *bool:
+ *v = s.scanBool(verb)
+ case *complex64:
+ *v = complex64(s.scanComplex(verb, 64))
+ case *complex128:
+ *v = s.scanComplex(verb, 128)
+ case *int:
+ *v = int(s.scanInt(verb, intBits))
+ case *int8:
+ *v = int8(s.scanInt(verb, 8))
+ case *int16:
+ *v = int16(s.scanInt(verb, 16))
+ case *int32:
+ *v = int32(s.scanInt(verb, 32))
+ case *int64:
+ *v = s.scanInt(verb, 64)
+ case *uint:
+ *v = uint(s.scanUint(verb, intBits))
+ case *uint8:
+ *v = uint8(s.scanUint(verb, 8))
+ case *uint16:
+ *v = uint16(s.scanUint(verb, 16))
+ case *uint32:
+ *v = uint32(s.scanUint(verb, 32))
+ case *uint64:
+ *v = s.scanUint(verb, 64)
+ case *uintptr:
+ *v = uintptr(s.scanUint(verb, uintptrBits))
+ // Floats are tricky because you want to scan in the precision of the result, not
+ // scan in high precision and convert, in order to preserve the correct error condition.
+ case *float32:
+ if s.okVerb(verb, floatVerbs, "float32") {
+ s.SkipSpace()
+ s.notEOF()
+ *v = float32(s.convertFloat(s.floatToken(), 32))
+ }
+ case *float64:
+ if s.okVerb(verb, floatVerbs, "float64") {
+ s.SkipSpace()
+ s.notEOF()
+ *v = s.convertFloat(s.floatToken(), 64)
+ }
+ case *string:
+ *v = s.convertString(verb)
+ case *[]byte:
+ // We scan to string and convert so we get a copy of the data.
+ // If we scanned to bytes, the slice would point at the buffer.
+ *v = []byte(s.convertString(verb))
+ default:
+ val := reflect.ValueOf(v)
+ ptr := val
+ if ptr.Kind() != reflect.Ptr {
+ s.errorString("type not a pointer: " + val.Type().String())
+ return
+ }
+ switch v := ptr.Elem(); v.Kind() {
+ case reflect.Bool:
+ v.SetBool(s.scanBool(verb))
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ v.SetInt(s.scanInt(verb, v.Type().Bits()))
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ v.SetUint(s.scanUint(verb, v.Type().Bits()))
+ case reflect.String:
+ v.SetString(s.convertString(verb))
+ case reflect.Slice:
+ // For now, can only handle (renamed) []byte.
+ typ := v.Type()
+ if typ.Elem().Kind() != reflect.Uint8 {
+ s.errorString("can't scan type: " + val.Type().String())
+ }
+ str := s.convertString(verb)
+ v.Set(reflect.MakeSlice(typ, len(str), len(str)))
+ for i := 0; i < len(str); i++ {
+ v.Index(i).SetUint(uint64(str[i]))
+ }
+ case reflect.Float32, reflect.Float64:
+ s.SkipSpace()
+ s.notEOF()
+ v.SetFloat(s.convertFloat(s.floatToken(), v.Type().Bits()))
+ case reflect.Complex64, reflect.Complex128:
+ v.SetComplex(s.scanComplex(verb, v.Type().Bits()))
+ default:
+ s.errorString("can't scan type: " + val.Type().String())
+ }
+ }
+}
+
+// errorHandler turns local panics into error returns.
+func errorHandler(errp *error) {
+ if e := recover(); e != nil {
+ if se, ok := e.(scanError); ok { // catch local error
+ *errp = se.err
+ } else if eof, ok := e.(error); ok && eof == io.EOF { // out of input
+ *errp = eof
+ } else {
+ panic(e)
+ }
+ }
+}
+
+// doScan does the real work for scanning without a format string.
+func (s *ss) doScan(a []interface{}) (numProcessed int, err error) {
+ defer errorHandler(&err)
+ for _, arg := range a {
+ s.scanOne('v', arg)
+ numProcessed++
+ }
+ // Check for newline (or EOF) if required (Scanln etc.).
+ if s.nlIsEnd {
+ for {
+ r := s.getRune()
+ if r == '\n' || r == eof {
+ break
+ }
+ if !isSpace(r) {
+ s.errorString("expected newline")
+ break
+ }
+ }
+ }
+ return
+}
+
+// advance determines whether the next characters in the input match
+// those of the format. It returns the number of bytes (sic) consumed
+// in the format. All runs of space characters in either input or
+// format behave as a single space. Newlines are special, though:
+// newlines in the format must match those in the input and vice versa.
+// This routine also handles the %% case. If the return value is zero,
+// either format starts with a % (with no following %) or the input
+// is empty. If it is negative, the input did not match the string.
+func (s *ss) advance(format string) (i int) {
+ for i < len(format) {
+ fmtc, w := utf8.DecodeRuneInString(format[i:])
+
+ // Space processing.
+ // In the rest of this comment "space" means spaces other than newline.
+ // Newline in the format matches input of zero or more spaces and then newline or end-of-input.
+ // Spaces in the format before the newline are collapsed into the newline.
+ // Spaces in the format after the newline match zero or more spaces after the corresponding input newline.
+ // Other spaces in the format match input of one or more spaces or end-of-input.
+ if isSpace(fmtc) {
+ newlines := 0
+ trailingSpace := false
+ for isSpace(fmtc) && i < len(format) {
+ if fmtc == '\n' {
+ newlines++
+ trailingSpace = false
+ } else {
+ trailingSpace = true
+ }
+ i += w
+ fmtc, w = utf8.DecodeRuneInString(format[i:])
+ }
+ for j := 0; j < newlines; j++ {
+ inputc := s.getRune()
+ for isSpace(inputc) && inputc != '\n' {
+ inputc = s.getRune()
+ }
+ if inputc != '\n' && inputc != eof {
+ s.errorString("newline in format does not match input")
+ }
+ }
+ if trailingSpace {
+ inputc := s.getRune()
+ if newlines == 0 {
+ // If the trailing space stood alone (did not follow a newline),
+ // it must find at least one space to consume.
+ if !isSpace(inputc) && inputc != eof {
+ s.errorString("expected space in input to match format")
+ }
+ if inputc == '\n' {
+ s.errorString("newline in input does not match format")
+ }
+ }
+ for isSpace(inputc) && inputc != '\n' {
+ inputc = s.getRune()
+ }
+ if inputc != eof {
+ s.UnreadRune()
+ }
+ }
+ continue
+ }
+
+ // Verbs.
+ if fmtc == '%' {
+ // % at end of string is an error.
+ if i+w == len(format) {
+ s.errorString("missing verb: % at end of format string")
+ }
+ // %% acts like a real percent
+ nextc, _ := utf8.DecodeRuneInString(format[i+w:]) // will not match % if string is empty
+ if nextc != '%' {
+ return
+ }
+ i += w // skip the first %
+ }
+
+ // Literals.
+ inputc := s.mustReadRune()
+ if fmtc != inputc {
+ s.UnreadRune()
+ return -1
+ }
+ i += w
+ }
+ return
+}
+
+// doScanf does the real work when scanning with a format string.
+// At the moment, it handles only pointers to basic types.
+func (s *ss) doScanf(format string, a []interface{}) (numProcessed int, err error) {
+ defer errorHandler(&err)
+ end := len(format) - 1
+ // We process one item per non-trivial format
+ for i := 0; i <= end; {
+ w := s.advance(format[i:])
+ if w > 0 {
+ i += w
+ continue
+ }
+ // Either we failed to advance, we have a percent character, or we ran out of input.
+ if format[i] != '%' {
+ // Can't advance format. Why not?
+ if w < 0 {
+ s.errorString("input does not match format")
+ }
+ // Otherwise at EOF; "too many operands" error handled below
+ break
+ }
+ i++ // % is one byte
+
+ // do we have 20 (width)?
+ var widPresent bool
+ s.maxWid, widPresent, i = parsenum(format, i, end)
+ if !widPresent {
+ s.maxWid = hugeWid
+ }
+
+ c, w := utf8.DecodeRuneInString(format[i:])
+ i += w
+
+ if c != 'c' {
+ s.SkipSpace()
+ }
+ s.argLimit = s.limit
+ if f := s.count + s.maxWid; f < s.argLimit {
+ s.argLimit = f
+ }
+
+ if numProcessed >= len(a) { // out of operands
+ s.errorString("too few operands for format '%" + format[i-w:] + "'")
+ break
+ }
+ arg := a[numProcessed]
+
+ s.scanOne(c, arg)
+ numProcessed++
+ s.argLimit = s.limit
+ }
+ if numProcessed < len(a) {
+ s.errorString("too many operands")
+ }
+ return
+}
diff --git a/errors/fmt/scan_test.go b/errors/fmt/scan_test.go
new file mode 100644
index 0000000..d7019d9
--- /dev/null
+++ b/errors/fmt/scan_test.go
@@ -0,0 +1,1292 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ . "fmt"
+ "io"
+ "math"
+ "reflect"
+ "regexp"
+ "strings"
+ "testing"
+ "testing/iotest"
+ "unicode/utf8"
+)
+
+type ScanTest struct {
+ text string
+ in interface{}
+ out interface{}
+}
+
+type ScanfTest struct {
+ format string
+ text string
+ in interface{}
+ out interface{}
+}
+
+type ScanfMultiTest struct {
+ format string
+ text string
+ in []interface{}
+ out []interface{}
+ err string
+}
+
+var (
+ boolVal bool
+ intVal int
+ int8Val int8
+ int16Val int16
+ int32Val int32
+ int64Val int64
+ uintVal uint
+ uint8Val uint8
+ uint16Val uint16
+ uint32Val uint32
+ uint64Val uint64
+ float32Val float32
+ float64Val float64
+ stringVal string
+ bytesVal []byte
+ runeVal rune
+ complex64Val complex64
+ complex128Val complex128
+ renamedBoolVal renamedBool
+ renamedIntVal renamedInt
+ renamedInt8Val renamedInt8
+ renamedInt16Val renamedInt16
+ renamedInt32Val renamedInt32
+ renamedInt64Val renamedInt64
+ renamedUintVal renamedUint
+ renamedUint8Val renamedUint8
+ renamedUint16Val renamedUint16
+ renamedUint32Val renamedUint32
+ renamedUint64Val renamedUint64
+ renamedUintptrVal renamedUintptr
+ renamedStringVal renamedString
+ renamedBytesVal renamedBytes
+ renamedFloat32Val renamedFloat32
+ renamedFloat64Val renamedFloat64
+ renamedComplex64Val renamedComplex64
+ renamedComplex128Val renamedComplex128
+)
+
+// Xs accepts any non-empty run of the verb character
+type Xs string
+
+func (x *Xs) Scan(state ScanState, verb rune) error {
+ tok, err := state.Token(true, func(r rune) bool { return r == verb })
+ if err != nil {
+ return err
+ }
+ s := string(tok)
+ if !regexp.MustCompile("^" + string(verb) + "+$").MatchString(s) {
+ return errors.New("syntax error for xs")
+ }
+ *x = Xs(s)
+ return nil
+}
+
+var xVal Xs
+
+// IntString accepts an integer followed immediately by a string.
+// It tests the embedding of a scan within a scan.
+type IntString struct {
+ i int
+ s string
+}
+
+func (s *IntString) Scan(state ScanState, verb rune) error {
+ if _, err := Fscan(state, &s.i); err != nil {
+ return err
+ }
+
+ tok, err := state.Token(true, nil)
+ if err != nil {
+ return err
+ }
+ s.s = string(tok)
+ return nil
+}
+
+var intStringVal IntString
+
+var scanTests = []ScanTest{
+ // Basic types
+ {"T\n", &boolVal, true}, // boolean test vals toggle to be sure they are written
+ {"F\n", &boolVal, false}, // restored to zero value
+ {"21\n", &intVal, 21},
+ {"0\n", &intVal, 0},
+ {"000\n", &intVal, 0},
+ {"0x10\n", &intVal, 0x10},
+ {"-0x10\n", &intVal, -0x10},
+ {"0377\n", &intVal, 0377},
+ {"-0377\n", &intVal, -0377},
+ {"0\n", &uintVal, uint(0)},
+ {"000\n", &uintVal, uint(0)},
+ {"0x10\n", &uintVal, uint(0x10)},
+ {"0377\n", &uintVal, uint(0377)},
+ {"22\n", &int8Val, int8(22)},
+ {"23\n", &int16Val, int16(23)},
+ {"24\n", &int32Val, int32(24)},
+ {"25\n", &int64Val, int64(25)},
+ {"127\n", &int8Val, int8(127)},
+ {"-21\n", &intVal, -21},
+ {"-22\n", &int8Val, int8(-22)},
+ {"-23\n", &int16Val, int16(-23)},
+ {"-24\n", &int32Val, int32(-24)},
+ {"-25\n", &int64Val, int64(-25)},
+ {"-128\n", &int8Val, int8(-128)},
+ {"+21\n", &intVal, +21},
+ {"+22\n", &int8Val, int8(+22)},
+ {"+23\n", &int16Val, int16(+23)},
+ {"+24\n", &int32Val, int32(+24)},
+ {"+25\n", &int64Val, int64(+25)},
+ {"+127\n", &int8Val, int8(+127)},
+ {"26\n", &uintVal, uint(26)},
+ {"27\n", &uint8Val, uint8(27)},
+ {"28\n", &uint16Val, uint16(28)},
+ {"29\n", &uint32Val, uint32(29)},
+ {"30\n", &uint64Val, uint64(30)},
+ {"255\n", &uint8Val, uint8(255)},
+ {"32767\n", &int16Val, int16(32767)},
+ {"2.3\n", &float64Val, 2.3},
+ {"2.3e1\n", &float32Val, float32(2.3e1)},
+ {"2.3e2\n", &float64Val, 2.3e2},
+ {"2.3p2\n", &float64Val, 2.3 * 4},
+ {"2.3p+2\n", &float64Val, 2.3 * 4},
+ {"2.3p+66\n", &float64Val, 2.3 * (1 << 32) * (1 << 32) * 4},
+ {"2.3p-66\n", &float64Val, 2.3 / ((1 << 32) * (1 << 32) * 4)},
+ {"2.35\n", &stringVal, "2.35"},
+ {"2345678\n", &bytesVal, []byte("2345678")},
+ {"(3.4e1-2i)\n", &complex128Val, 3.4e1 - 2i},
+ {"-3.45e1-3i\n", &complex64Val, complex64(-3.45e1 - 3i)},
+ {"-.45e1-1e2i\n", &complex128Val, complex128(-.45e1 - 100i)},
+ {"hello\n", &stringVal, "hello"},
+
+ // Carriage-return followed by newline. (We treat \r\n as \n always.)
+ {"hello\r\n", &stringVal, "hello"},
+ {"27\r\n", &uint8Val, uint8(27)},
+
+ // Renamed types
+ {"true\n", &renamedBoolVal, renamedBool(true)},
+ {"F\n", &renamedBoolVal, renamedBool(false)},
+ {"101\n", &renamedIntVal, renamedInt(101)},
+ {"102\n", &renamedIntVal, renamedInt(102)},
+ {"103\n", &renamedUintVal, renamedUint(103)},
+ {"104\n", &renamedUintVal, renamedUint(104)},
+ {"105\n", &renamedInt8Val, renamedInt8(105)},
+ {"106\n", &renamedInt16Val, renamedInt16(106)},
+ {"107\n", &renamedInt32Val, renamedInt32(107)},
+ {"108\n", &renamedInt64Val, renamedInt64(108)},
+ {"109\n", &renamedUint8Val, renamedUint8(109)},
+ {"110\n", &renamedUint16Val, renamedUint16(110)},
+ {"111\n", &renamedUint32Val, renamedUint32(111)},
+ {"112\n", &renamedUint64Val, renamedUint64(112)},
+ {"113\n", &renamedUintptrVal, renamedUintptr(113)},
+ {"114\n", &renamedStringVal, renamedString("114")},
+ {"115\n", &renamedBytesVal, renamedBytes([]byte("115"))},
+
+ // Custom scanners.
+ {" vvv ", &xVal, Xs("vvv")},
+ {" 1234hello", &intStringVal, IntString{1234, "hello"}},
+
+ // Fixed bugs
+ {"2147483648\n", &int64Val, int64(2147483648)}, // was: integer overflow
+}
+
+var scanfTests = []ScanfTest{
+ {"%v", "TRUE\n", &boolVal, true},
+ {"%t", "false\n", &boolVal, false},
+ {"%v", "-71\n", &intVal, -71},
+ {"%v", "0377\n", &intVal, 0377},
+ {"%v", "0x44\n", &intVal, 0x44},
+ {"%d", "72\n", &intVal, 72},
+ {"%c", "a\n", &runeVal, 'a'},
+ {"%c", "\u5072\n", &runeVal, '\u5072'},
+ {"%c", "\u1234\n", &runeVal, '\u1234'},
+ {"%d", "73\n", &int8Val, int8(73)},
+ {"%d", "+74\n", &int16Val, int16(74)},
+ {"%d", "75\n", &int32Val, int32(75)},
+ {"%d", "76\n", &int64Val, int64(76)},
+ {"%b", "1001001\n", &intVal, 73},
+ {"%o", "075\n", &intVal, 075},
+ {"%x", "a75\n", &intVal, 0xa75},
+ {"%v", "71\n", &uintVal, uint(71)},
+ {"%d", "72\n", &uintVal, uint(72)},
+ {"%d", "73\n", &uint8Val, uint8(73)},
+ {"%d", "74\n", &uint16Val, uint16(74)},
+ {"%d", "75\n", &uint32Val, uint32(75)},
+ {"%d", "76\n", &uint64Val, uint64(76)},
+ {"%b", "1001001\n", &uintVal, uint(73)},
+ {"%o", "075\n", &uintVal, uint(075)},
+ {"%x", "a75\n", &uintVal, uint(0xa75)},
+ {"%x", "A75\n", &uintVal, uint(0xa75)},
+ {"%U", "U+1234\n", &intVal, int(0x1234)},
+ {"%U", "U+4567\n", &uintVal, uint(0x4567)},
+
+ // Strings
+ {"%s", "using-%s\n", &stringVal, "using-%s"},
+ {"%x", "7573696e672d2578\n", &stringVal, "using-%x"},
+ {"%X", "7573696E672D2558\n", &stringVal, "using-%X"},
+ {"%q", `"quoted\twith\\do\u0075bl\x65s"` + "\n", &stringVal, "quoted\twith\\doubles"},
+ {"%q", "`quoted with backs`\n", &stringVal, "quoted with backs"},
+
+ // Byte slices
+ {"%s", "bytes-%s\n", &bytesVal, []byte("bytes-%s")},
+ {"%x", "62797465732d2578\n", &bytesVal, []byte("bytes-%x")},
+ {"%X", "62797465732D2558\n", &bytesVal, []byte("bytes-%X")},
+ {"%q", `"bytes\rwith\vdo\u0075bl\x65s"` + "\n", &bytesVal, []byte("bytes\rwith\vdoubles")},
+ {"%q", "`bytes with backs`\n", &bytesVal, []byte("bytes with backs")},
+
+ // Renamed types
+ {"%v\n", "true\n", &renamedBoolVal, renamedBool(true)},
+ {"%t\n", "F\n", &renamedBoolVal, renamedBool(false)},
+ {"%v", "101\n", &renamedIntVal, renamedInt(101)},
+ {"%c", "\u0101\n", &renamedIntVal, renamedInt('\u0101')},
+ {"%o", "0146\n", &renamedIntVal, renamedInt(102)},
+ {"%v", "103\n", &renamedUintVal, renamedUint(103)},
+ {"%d", "104\n", &renamedUintVal, renamedUint(104)},
+ {"%d", "105\n", &renamedInt8Val, renamedInt8(105)},
+ {"%d", "106\n", &renamedInt16Val, renamedInt16(106)},
+ {"%d", "107\n", &renamedInt32Val, renamedInt32(107)},
+ {"%d", "108\n", &renamedInt64Val, renamedInt64(108)},
+ {"%x", "6D\n", &renamedUint8Val, renamedUint8(109)},
+ {"%o", "0156\n", &renamedUint16Val, renamedUint16(110)},
+ {"%d", "111\n", &renamedUint32Val, renamedUint32(111)},
+ {"%d", "112\n", &renamedUint64Val, renamedUint64(112)},
+ {"%d", "113\n", &renamedUintptrVal, renamedUintptr(113)},
+ {"%s", "114\n", &renamedStringVal, renamedString("114")},
+ {"%q", "\"1155\"\n", &renamedBytesVal, renamedBytes([]byte("1155"))},
+ {"%g", "116e1\n", &renamedFloat32Val, renamedFloat32(116e1)},
+ {"%g", "-11.7e+1", &renamedFloat64Val, renamedFloat64(-11.7e+1)},
+ {"%g", "11+6e1i\n", &renamedComplex64Val, renamedComplex64(11 + 6e1i)},
+ {"%g", "-11.+7e+1i", &renamedComplex128Val, renamedComplex128(-11. + 7e+1i)},
+
+ // Interesting formats
+ {"here is\tthe value:%d", "here is the\tvalue:118\n", &intVal, 118},
+ {"%% %%:%d", "% %:119\n", &intVal, 119},
+ {"%d%%", "42%", &intVal, 42}, // %% at end of string.
+
+ // Corner cases
+ {"%x", "FFFFFFFF\n", &uint32Val, uint32(0xFFFFFFFF)},
+
+ // Custom scanner.
+ {"%s", " sss ", &xVal, Xs("sss")},
+ {"%2s", "sssss", &xVal, Xs("ss")},
+
+ // Fixed bugs
+ {"%d\n", "27\n", &intVal, 27}, // ok
+ {"%d\n", "28 \n", &intVal, 28}, // was: "unexpected newline"
+ {"%v", "0", &intVal, 0}, // was: "EOF"; 0 was taken as base prefix and not counted.
+ {"%v", "0", &uintVal, uint(0)}, // was: "EOF"; 0 was taken as base prefix and not counted.
+ {"%c", " ", &uintVal, uint(' ')}, // %c must accept a blank.
+ {"%c", "\t", &uintVal, uint('\t')}, // %c must accept any space.
+ {"%c", "\n", &uintVal, uint('\n')}, // %c must accept any space.
+
+ // space handling
+ {"%d", "27", &intVal, 27},
+ {"%d", "27 ", &intVal, 27},
+ {"%d", " 27", &intVal, 27},
+ {"%d", " 27 ", &intVal, 27},
+
+ {"X%d", "X27", &intVal, 27},
+ {"X%d", "X27 ", &intVal, 27},
+ {"X%d", "X 27", &intVal, 27},
+ {"X%d", "X 27 ", &intVal, 27},
+
+ {"X %d", "X27", &intVal, nil}, // expected space in input to match format
+ {"X %d", "X27 ", &intVal, nil}, // expected space in input to match format
+ {"X %d", "X 27", &intVal, 27},
+ {"X %d", "X 27 ", &intVal, 27},
+
+ {"%dX", "27X", &intVal, 27},
+ {"%dX", "27 X", &intVal, nil}, // input does not match format
+ {"%dX", " 27X", &intVal, 27},
+ {"%dX", " 27 X", &intVal, nil}, // input does not match format
+
+ {"%d X", "27X", &intVal, nil}, // expected space in input to match format
+ {"%d X", "27 X", &intVal, 27},
+ {"%d X", " 27X", &intVal, nil}, // expected space in input to match format
+ {"%d X", " 27 X", &intVal, 27},
+
+ {"X %d X", "X27X", &intVal, nil}, // expected space in input to match format
+ {"X %d X", "X27 X", &intVal, nil}, // expected space in input to match format
+ {"X %d X", "X 27X", &intVal, nil}, // expected space in input to match format
+ {"X %d X", "X 27 X", &intVal, 27},
+
+ {"X %s X", "X27X", &stringVal, nil}, // expected space in input to match format
+ {"X %s X", "X27 X", &stringVal, nil}, // expected space in input to match format
+ {"X %s X", "X 27X", &stringVal, nil}, // unexpected EOF
+ {"X %s X", "X 27 X", &stringVal, "27"},
+
+ {"X%sX", "X27X", &stringVal, nil}, // unexpected EOF
+ {"X%sX", "X27 X", &stringVal, nil}, // input does not match format
+ {"X%sX", "X 27X", &stringVal, nil}, // unexpected EOF
+ {"X%sX", "X 27 X", &stringVal, nil}, // input does not match format
+
+ {"X%s", "X27", &stringVal, "27"},
+ {"X%s", "X27 ", &stringVal, "27"},
+ {"X%s", "X 27", &stringVal, "27"},
+ {"X%s", "X 27 ", &stringVal, "27"},
+
+ {"X%dX", "X27X", &intVal, 27},
+ {"X%dX", "X27 X", &intVal, nil}, // input does not match format
+ {"X%dX", "X 27X", &intVal, 27},
+ {"X%dX", "X 27 X", &intVal, nil}, // input does not match format
+
+ {"X%dX", "X27X", &intVal, 27},
+ {"X%dX", "X27X ", &intVal, 27},
+ {"X%dX", " X27X", &intVal, nil}, // input does not match format
+ {"X%dX", " X27X ", &intVal, nil}, // input does not match format
+
+ {"X%dX\n", "X27X", &intVal, 27},
+ {"X%dX \n", "X27X ", &intVal, 27},
+ {"X%dX\n", "X27X\n", &intVal, 27},
+ {"X%dX\n", "X27X \n", &intVal, 27},
+
+ {"X%dX \n", "X27X", &intVal, 27},
+ {"X%dX \n", "X27X ", &intVal, 27},
+ {"X%dX \n", "X27X\n", &intVal, 27},
+ {"X%dX \n", "X27X \n", &intVal, 27},
+
+ {"X%c", "X\n", &runeVal, '\n'},
+ {"X%c", "X \n", &runeVal, ' '},
+ {"X %c", "X!", &runeVal, nil}, // expected space in input to match format
+ {"X %c", "X\n", &runeVal, nil}, // newline in input does not match format
+ {"X %c", "X !", &runeVal, '!'},
+ {"X %c", "X \n", &runeVal, '\n'},
+
+ {" X%dX", "X27X", &intVal, nil}, // expected space in input to match format
+ {" X%dX", "X27X ", &intVal, nil}, // expected space in input to match format
+ {" X%dX", " X27X", &intVal, 27},
+ {" X%dX", " X27X ", &intVal, 27},
+
+ {"X%dX ", "X27X", &intVal, 27},
+ {"X%dX ", "X27X ", &intVal, 27},
+ {"X%dX ", " X27X", &intVal, nil}, // input does not match format
+ {"X%dX ", " X27X ", &intVal, nil}, // input does not match format
+
+ {" X%dX ", "X27X", &intVal, nil}, // expected space in input to match format
+ {" X%dX ", "X27X ", &intVal, nil}, // expected space in input to match format
+ {" X%dX ", " X27X", &intVal, 27},
+ {" X%dX ", " X27X ", &intVal, 27},
+
+ {"%d\nX", "27\nX", &intVal, 27},
+ {"%dX\n X", "27X\n X", &intVal, 27},
+}
+
+var overflowTests = []ScanTest{
+ {"128", &int8Val, 0},
+ {"32768", &int16Val, 0},
+ {"-129", &int8Val, 0},
+ {"-32769", &int16Val, 0},
+ {"256", &uint8Val, 0},
+ {"65536", &uint16Val, 0},
+ {"1e100", &float32Val, 0},
+ {"1e500", &float64Val, 0},
+ {"(1e100+0i)", &complex64Val, 0},
+ {"(1+1e100i)", &complex64Val, 0},
+ {"(1-1e500i)", &complex128Val, 0},
+}
+
+var truth bool
+var i, j, k int
+var f float64
+var s, t string
+var c complex128
+var x, y Xs
+var z IntString
+var r1, r2, r3 rune
+
+var multiTests = []ScanfMultiTest{
+ {"", "", []interface{}{}, []interface{}{}, ""},
+ {"%d", "23", args(&i), args(23), ""},
+ {"%2s%3s", "22333", args(&s, &t), args("22", "333"), ""},
+ {"%2d%3d", "44555", args(&i, &j), args(44, 555), ""},
+ {"%2d.%3d", "66.777", args(&i, &j), args(66, 777), ""},
+ {"%d, %d", "23, 18", args(&i, &j), args(23, 18), ""},
+ {"%3d22%3d", "33322333", args(&i, &j), args(333, 333), ""},
+ {"%6vX=%3fY", "3+2iX=2.5Y", args(&c, &f), args((3 + 2i), 2.5), ""},
+ {"%d%s", "123abc", args(&i, &s), args(123, "abc"), ""},
+ {"%c%c%c", "2\u50c2X", args(&r1, &r2, &r3), args('2', '\u50c2', 'X'), ""},
+ {"%5s%d", " 1234567 ", args(&s, &i), args("12345", 67), ""},
+ {"%5s%d", " 12 34 567 ", args(&s, &i), args("12", 34), ""},
+
+ // Custom scanners.
+ {"%e%f", "eefffff", args(&x, &y), args(Xs("ee"), Xs("fffff")), ""},
+ {"%4v%s", "12abcd", args(&z, &s), args(IntString{12, "ab"}, "cd"), ""},
+
+ // Errors
+ {"%t", "23 18", args(&i), nil, "bad verb"},
+ {"%d %d %d", "23 18", args(&i, &j), args(23, 18), "too few operands"},
+ {"%d %d", "23 18 27", args(&i, &j, &k), args(23, 18), "too many operands"},
+ {"%c", "\u0100", args(&int8Val), nil, "overflow"},
+ {"X%d", "10X", args(&intVal), nil, "input does not match format"},
+ {"%d%", "42%", args(&intVal), args(42), "missing verb: % at end of format string"},
+ {"%d% ", "42%", args(&intVal), args(42), "too few operands for format '% '"}, // Slightly odd error, but correct.
+
+ // Bad UTF-8: should see every byte.
+ {"%c%c%c", "\xc2X\xc2", args(&r1, &r2, &r3), args(utf8.RuneError, 'X', utf8.RuneError), ""},
+
+ // Fixed bugs
+ {"%v%v", "FALSE23", args(&truth, &i), args(false, 23), ""},
+}
+
+var readers = []struct {
+ name string
+ f func(string) io.Reader
+}{
+ {"StringReader", func(s string) io.Reader {
+ return strings.NewReader(s)
+ }},
+ {"ReaderOnly", func(s string) io.Reader {
+ return struct{ io.Reader }{strings.NewReader(s)}
+ }},
+ {"OneByteReader", func(s string) io.Reader {
+ return iotest.OneByteReader(strings.NewReader(s))
+ }},
+ {"DataErrReader", func(s string) io.Reader {
+ return iotest.DataErrReader(strings.NewReader(s))
+ }},
+}
+
+func testScan(t *testing.T, f func(string) io.Reader, scan func(r io.Reader, a ...interface{}) (int, error)) {
+ for _, test := range scanTests {
+ r := f(test.text)
+ n, err := scan(r, test.in)
+ if err != nil {
+ m := ""
+ if n > 0 {
+ m = Sprintf(" (%d fields ok)", n)
+ }
+ t.Errorf("got error scanning %q: %s%s", test.text, err, m)
+ continue
+ }
+ if n != 1 {
+ t.Errorf("count error on entry %q: got %d", test.text, n)
+ continue
+ }
+ // The incoming value may be a pointer
+ v := reflect.ValueOf(test.in)
+ if p := v; p.Kind() == reflect.Ptr {
+ v = p.Elem()
+ }
+ val := v.Interface()
+ if !reflect.DeepEqual(val, test.out) {
+ t.Errorf("scanning %q: expected %#v got %#v, type %T", test.text, test.out, val, val)
+ }
+ }
+}
+
+func TestScan(t *testing.T) {
+ for _, r := range readers {
+ t.Run(r.name, func(t *testing.T) {
+ testScan(t, r.f, Fscan)
+ })
+ }
+}
+
+func TestScanln(t *testing.T) {
+ for _, r := range readers {
+ t.Run(r.name, func(t *testing.T) {
+ testScan(t, r.f, Fscanln)
+ })
+ }
+}
+
+func TestScanf(t *testing.T) {
+ for _, test := range scanfTests {
+ n, err := Sscanf(test.text, test.format, test.in)
+ if err != nil {
+ if test.out != nil {
+ t.Errorf("Sscanf(%q, %q): unexpected error: %v", test.text, test.format, err)
+ }
+ continue
+ }
+ if test.out == nil {
+ t.Errorf("Sscanf(%q, %q): unexpected success", test.text, test.format)
+ continue
+ }
+ if n != 1 {
+ t.Errorf("Sscanf(%q, %q): parsed %d field, want 1", test.text, test.format, n)
+ continue
+ }
+ // The incoming value may be a pointer
+ v := reflect.ValueOf(test.in)
+ if p := v; p.Kind() == reflect.Ptr {
+ v = p.Elem()
+ }
+ val := v.Interface()
+ if !reflect.DeepEqual(val, test.out) {
+ t.Errorf("Sscanf(%q, %q): parsed value %T(%#v), want %T(%#v)", test.text, test.format, val, val, test.out, test.out)
+ }
+ }
+}
+
+func TestScanOverflow(t *testing.T) {
+ // different machines and different types report errors with different strings.
+ re := regexp.MustCompile("overflow|too large|out of range|not representable")
+ for _, test := range overflowTests {
+ _, err := Sscan(test.text, test.in)
+ if err == nil {
+ t.Errorf("expected overflow scanning %q", test.text)
+ continue
+ }
+ if !re.MatchString(err.Error()) {
+ t.Errorf("expected overflow error scanning %q: %s", test.text, err)
+ }
+ }
+}
+
+func verifyNaN(str string, t *testing.T) {
+ var f float64
+ var f32 float32
+ var f64 float64
+ text := str + " " + str + " " + str
+ n, err := Fscan(strings.NewReader(text), &f, &f32, &f64)
+ if err != nil {
+ t.Errorf("got error scanning %q: %s", text, err)
+ }
+ if n != 3 {
+ t.Errorf("count error scanning %q: got %d", text, n)
+ }
+ if !math.IsNaN(float64(f)) || !math.IsNaN(float64(f32)) || !math.IsNaN(f64) {
+ t.Errorf("didn't get NaNs scanning %q: got %g %g %g", text, f, f32, f64)
+ }
+}
+
+func TestNaN(t *testing.T) {
+ for _, s := range []string{"nan", "NAN", "NaN"} {
+ verifyNaN(s, t)
+ }
+}
+
+func verifyInf(str string, t *testing.T) {
+ var f float64
+ var f32 float32
+ var f64 float64
+ text := str + " " + str + " " + str
+ n, err := Fscan(strings.NewReader(text), &f, &f32, &f64)
+ if err != nil {
+ t.Errorf("got error scanning %q: %s", text, err)
+ }
+ if n != 3 {
+ t.Errorf("count error scanning %q: got %d", text, n)
+ }
+ sign := 1
+ if str[0] == '-' {
+ sign = -1
+ }
+ if !math.IsInf(float64(f), sign) || !math.IsInf(float64(f32), sign) || !math.IsInf(f64, sign) {
+ t.Errorf("didn't get right Infs scanning %q: got %g %g %g", text, f, f32, f64)
+ }
+}
+
+func TestInf(t *testing.T) {
+ for _, s := range []string{"inf", "+inf", "-inf", "INF", "-INF", "+INF", "Inf", "-Inf", "+Inf"} {
+ verifyInf(s, t)
+ }
+}
+
+func testScanfMulti(t *testing.T, f func(string) io.Reader) {
+ sliceType := reflect.TypeOf(make([]interface{}, 1))
+ for _, test := range multiTests {
+ r := f(test.text)
+ n, err := Fscanf(r, test.format, test.in...)
+ if err != nil {
+ if test.err == "" {
+ t.Errorf("got error scanning (%q, %q): %q", test.format, test.text, err)
+ } else if !strings.Contains(err.Error(), test.err) {
+ t.Errorf("got wrong error scanning (%q, %q): %q; expected %q", test.format, test.text, err, test.err)
+ }
+ continue
+ }
+ if test.err != "" {
+ t.Errorf("expected error %q error scanning (%q, %q)", test.err, test.format, test.text)
+ }
+ if n != len(test.out) {
+ t.Errorf("count error on entry (%q, %q): expected %d got %d", test.format, test.text, len(test.out), n)
+ continue
+ }
+ // Convert the slice of pointers into a slice of values
+ resultVal := reflect.MakeSlice(sliceType, n, n)
+ for i := 0; i < n; i++ {
+ v := reflect.ValueOf(test.in[i]).Elem()
+ resultVal.Index(i).Set(v)
+ }
+ result := resultVal.Interface()
+ if !reflect.DeepEqual(result, test.out) {
+ t.Errorf("scanning (%q, %q): expected %#v got %#v", test.format, test.text, test.out, result)
+ }
+ }
+}
+
+func TestScanfMulti(t *testing.T) {
+ for _, r := range readers {
+ t.Run(r.name, func(t *testing.T) {
+ testScanfMulti(t, r.f)
+ })
+ }
+}
+
+func TestScanMultiple(t *testing.T) {
+ var a int
+ var s string
+ n, err := Sscan("123abc", &a, &s)
+ if n != 2 {
+ t.Errorf("Sscan count error: expected 2: got %d", n)
+ }
+ if err != nil {
+ t.Errorf("Sscan expected no error; got %s", err)
+ }
+ if a != 123 || s != "abc" {
+ t.Errorf("Sscan wrong values: got (%d %q) expected (123 \"abc\")", a, s)
+ }
+ n, err = Sscan("asdf", &s, &a)
+ if n != 1 {
+ t.Errorf("Sscan count error: expected 1: got %d", n)
+ }
+ if err == nil {
+ t.Errorf("Sscan expected error; got none: %s", err)
+ }
+ if s != "asdf" {
+ t.Errorf("Sscan wrong values: got %q expected \"asdf\"", s)
+ }
+}
+
+// Empty strings are not valid input when scanning a string.
+func TestScanEmpty(t *testing.T) {
+ var s1, s2 string
+ n, err := Sscan("abc", &s1, &s2)
+ if n != 1 {
+ t.Errorf("Sscan count error: expected 1: got %d", n)
+ }
+ if err == nil {
+ t.Error("Sscan <one item> expected error; got none")
+ }
+ if s1 != "abc" {
+ t.Errorf("Sscan wrong values: got %q expected \"abc\"", s1)
+ }
+ n, err = Sscan("", &s1, &s2)
+ if n != 0 {
+ t.Errorf("Sscan count error: expected 0: got %d", n)
+ }
+ if err == nil {
+ t.Error("Sscan <empty> expected error; got none")
+ }
+ // Quoted empty string is OK.
+ n, err = Sscanf(`""`, "%q", &s1)
+ if n != 1 {
+ t.Errorf("Sscanf count error: expected 1: got %d", n)
+ }
+ if err != nil {
+ t.Errorf("Sscanf <empty> expected no error with quoted string; got %s", err)
+ }
+}
+
+func TestScanNotPointer(t *testing.T) {
+ r := strings.NewReader("1")
+ var a int
+ _, err := Fscan(r, a)
+ if err == nil {
+ t.Error("expected error scanning non-pointer")
+ } else if !strings.Contains(err.Error(), "pointer") {
+ t.Errorf("expected pointer error scanning non-pointer, got: %s", err)
+ }
+}
+
+func TestScanlnNoNewline(t *testing.T) {
+ var a int
+ _, err := Sscanln("1 x\n", &a)
+ if err == nil {
+ t.Error("expected error scanning string missing newline")
+ } else if !strings.Contains(err.Error(), "newline") {
+ t.Errorf("expected newline error scanning string missing newline, got: %s", err)
+ }
+}
+
+func TestScanlnWithMiddleNewline(t *testing.T) {
+ r := strings.NewReader("123\n456\n")
+ var a, b int
+ _, err := Fscanln(r, &a, &b)
+ if err == nil {
+ t.Error("expected error scanning string with extra newline")
+ } else if !strings.Contains(err.Error(), "newline") {
+ t.Errorf("expected newline error scanning string with extra newline, got: %s", err)
+ }
+}
+
+// eofCounter is a special Reader that counts reads at end of file.
+type eofCounter struct {
+ reader *strings.Reader
+ eofCount int
+}
+
+func (ec *eofCounter) Read(b []byte) (n int, err error) {
+ n, err = ec.reader.Read(b)
+ if n == 0 {
+ ec.eofCount++
+ }
+ return
+}
+
+// TestEOF verifies that when we scan, we see at most EOF once per call to a
+// Scan function, and then only when it's really an EOF.
+func TestEOF(t *testing.T) {
+ ec := &eofCounter{strings.NewReader("123\n"), 0}
+ var a int
+ n, err := Fscanln(ec, &a)
+ if err != nil {
+ t.Error("unexpected error", err)
+ }
+ if n != 1 {
+ t.Error("expected to scan one item, got", n)
+ }
+ if ec.eofCount != 0 {
+ t.Error("expected zero EOFs", ec.eofCount)
+ ec.eofCount = 0 // reset for next test
+ }
+ n, err = Fscanln(ec, &a)
+ if err == nil {
+ t.Error("expected error scanning empty string")
+ }
+ if n != 0 {
+ t.Error("expected to scan zero items, got", n)
+ }
+ if ec.eofCount != 1 {
+ t.Error("expected one EOF, got", ec.eofCount)
+ }
+}
+
+// TestEOFAtEndOfInput verifies that we see an EOF error if we run out of input.
+// This was a buglet: we used to get "expected integer".
+func TestEOFAtEndOfInput(t *testing.T) {
+ var i, j int
+ n, err := Sscanf("23", "%d %d", &i, &j)
+ if n != 1 || i != 23 {
+ t.Errorf("Sscanf expected one value of 23; got %d %d", n, i)
+ }
+ if err != io.EOF {
+ t.Errorf("Sscanf expected EOF; got %q", err)
+ }
+ n, err = Sscan("234", &i, &j)
+ if n != 1 || i != 234 {
+ t.Errorf("Sscan expected one value of 234; got %d %d", n, i)
+ }
+ if err != io.EOF {
+ t.Errorf("Sscan expected EOF; got %q", err)
+ }
+ // Trailing space is tougher.
+ n, err = Sscan("234 ", &i, &j)
+ if n != 1 || i != 234 {
+ t.Errorf("Sscan expected one value of 234; got %d %d", n, i)
+ }
+ if err != io.EOF {
+ t.Errorf("Sscan expected EOF; got %q", err)
+ }
+}
+
+var eofTests = []struct {
+ format string
+ v interface{}
+}{
+ {"%s", &stringVal},
+ {"%q", &stringVal},
+ {"%x", &stringVal},
+ {"%v", &stringVal},
+ {"%v", &bytesVal},
+ {"%v", &intVal},
+ {"%v", &uintVal},
+ {"%v", &boolVal},
+ {"%v", &float32Val},
+ {"%v", &complex64Val},
+ {"%v", &renamedStringVal},
+ {"%v", &renamedBytesVal},
+ {"%v", &renamedIntVal},
+ {"%v", &renamedUintVal},
+ {"%v", &renamedBoolVal},
+ {"%v", &renamedFloat32Val},
+ {"%v", &renamedComplex64Val},
+}
+
+func TestEOFAllTypes(t *testing.T) {
+ for i, test := range eofTests {
+ if _, err := Sscanf("", test.format, test.v); err != io.EOF {
+ t.Errorf("#%d: %s %T not eof on empty string: %s", i, test.format, test.v, err)
+ }
+ if _, err := Sscanf(" ", test.format, test.v); err != io.EOF {
+ t.Errorf("#%d: %s %T not eof on trailing blanks: %s", i, test.format, test.v, err)
+ }
+ }
+}
+
+// TestUnreadRuneWithBufio verifies that, at least when using bufio, successive
+// calls to Fscan do not lose runes.
+func TestUnreadRuneWithBufio(t *testing.T) {
+ r := bufio.NewReader(strings.NewReader("123Ξ±b"))
+ var i int
+ var a string
+ n, err := Fscanf(r, "%d", &i)
+ if n != 1 || err != nil {
+ t.Errorf("reading int expected one item, no errors; got %d %q", n, err)
+ }
+ if i != 123 {
+ t.Errorf("expected 123; got %d", i)
+ }
+ n, err = Fscanf(r, "%s", &a)
+ if n != 1 || err != nil {
+ t.Errorf("reading string expected one item, no errors; got %d %q", n, err)
+ }
+ if a != "Ξ±b" {
+ t.Errorf("expected Ξ±b; got %q", a)
+ }
+}
+
+type TwoLines string
+
+// Scan attempts to read two lines into the object. Scanln should prevent this
+// because it stops at newline; Scan and Scanf should be fine.
+func (t *TwoLines) Scan(state ScanState, verb rune) error {
+ chars := make([]rune, 0, 100)
+ for nlCount := 0; nlCount < 2; {
+ c, _, err := state.ReadRune()
+ if err != nil {
+ return err
+ }
+ chars = append(chars, c)
+ if c == '\n' {
+ nlCount++
+ }
+ }
+ *t = TwoLines(string(chars))
+ return nil
+}
+
+func TestMultiLine(t *testing.T) {
+ input := "abc\ndef\n"
+ // Sscan should work
+ var tscan TwoLines
+ n, err := Sscan(input, &tscan)
+ if n != 1 {
+ t.Errorf("Sscan: expected 1 item; got %d", n)
+ }
+ if err != nil {
+ t.Errorf("Sscan: expected no error; got %s", err)
+ }
+ if string(tscan) != input {
+ t.Errorf("Sscan: expected %q; got %q", input, tscan)
+ }
+ // Sscanf should work
+ var tscanf TwoLines
+ n, err = Sscanf(input, "%s", &tscanf)
+ if n != 1 {
+ t.Errorf("Sscanf: expected 1 item; got %d", n)
+ }
+ if err != nil {
+ t.Errorf("Sscanf: expected no error; got %s", err)
+ }
+ if string(tscanf) != input {
+ t.Errorf("Sscanf: expected %q; got %q", input, tscanf)
+ }
+ // Sscanln should not work
+ var tscanln TwoLines
+ n, err = Sscanln(input, &tscanln)
+ if n != 0 {
+ t.Errorf("Sscanln: expected 0 items; got %d: %q", n, tscanln)
+ }
+ if err == nil {
+ t.Error("Sscanln: expected error; got none")
+ } else if err != io.ErrUnexpectedEOF {
+ t.Errorf("Sscanln: expected io.ErrUnexpectedEOF (ha!); got %s", err)
+ }
+}
+
+// TestLineByLineFscanf tests that Fscanf does not read past newline. Issue
+// 3481.
+func TestLineByLineFscanf(t *testing.T) {
+ r := struct{ io.Reader }{strings.NewReader("1\n2\n")}
+ var i, j int
+ n, err := Fscanf(r, "%v\n", &i)
+ if n != 1 || err != nil {
+ t.Fatalf("first read: %d %q", n, err)
+ }
+ n, err = Fscanf(r, "%v\n", &j)
+ if n != 1 || err != nil {
+ t.Fatalf("second read: %d %q", n, err)
+ }
+ if i != 1 || j != 2 {
+ t.Errorf("wrong values; wanted 1 2 got %d %d", i, j)
+ }
+}
+
+// TestScanStateCount verifies the correct byte count is returned. Issue 8512.
+
+// runeScanner implements the Scanner interface for TestScanStateCount.
+type runeScanner struct {
+ rune rune
+ size int
+}
+
+func (rs *runeScanner) Scan(state ScanState, verb rune) error {
+ r, size, err := state.ReadRune()
+ rs.rune = r
+ rs.size = size
+ return err
+}
+
+func TestScanStateCount(t *testing.T) {
+ var a, b, c runeScanner
+ n, err := Sscanf("12βž‚", "%c%c%c", &a, &b, &c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != 3 {
+ t.Fatalf("expected 3 items consumed, got %d", n)
+ }
+ if a.rune != '1' || b.rune != '2' || c.rune != 'βž‚' {
+ t.Errorf("bad scan rune: %q %q %q should be '1' '2' 'βž‚'", a.rune, b.rune, c.rune)
+ }
+ if a.size != 1 || b.size != 1 || c.size != 3 {
+ t.Errorf("bad scan size: %q %q %q should be 1 1 3", a.size, b.size, c.size)
+ }
+}
+
+// RecursiveInt accepts a string matching %d.%d.%d....
+// and parses it into a linked list.
+// It allows us to benchmark recursive descent style scanners.
+type RecursiveInt struct {
+ i int
+ next *RecursiveInt
+}
+
+func (r *RecursiveInt) Scan(state ScanState, verb rune) (err error) {
+ _, err = Fscan(state, &r.i)
+ if err != nil {
+ return
+ }
+ next := new(RecursiveInt)
+ _, err = Fscanf(state, ".%v", next)
+ if err != nil {
+ if err == io.ErrUnexpectedEOF {
+ err = nil
+ }
+ return
+ }
+ r.next = next
+ return
+}
+
+// scanInts performs the same scanning task as RecursiveInt.Scan
+// but without recurring through scanner, so we can compare
+// performance more directly.
+func scanInts(r *RecursiveInt, b *bytes.Buffer) (err error) {
+ r.next = nil
+ _, err = Fscan(b, &r.i)
+ if err != nil {
+ return
+ }
+ c, _, err := b.ReadRune()
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ return
+ }
+ if c != '.' {
+ return
+ }
+ next := new(RecursiveInt)
+ err = scanInts(next, b)
+ if err == nil {
+ r.next = next
+ }
+ return
+}
+
+func makeInts(n int) []byte {
+ var buf bytes.Buffer
+ Fprintf(&buf, "1")
+ for i := 1; i < n; i++ {
+ Fprintf(&buf, ".%d", i+1)
+ }
+ return buf.Bytes()
+}
+
+func TestScanInts(t *testing.T) {
+ testScanInts(t, scanInts)
+ testScanInts(t, func(r *RecursiveInt, b *bytes.Buffer) (err error) {
+ _, err = Fscan(b, r)
+ return
+ })
+}
+
+// 800 is small enough to not overflow the stack when using gccgo on a
+// platform that does not support split stack.
+const intCount = 800
+
+func testScanInts(t *testing.T, scan func(*RecursiveInt, *bytes.Buffer) error) {
+ r := new(RecursiveInt)
+ ints := makeInts(intCount)
+ buf := bytes.NewBuffer(ints)
+ err := scan(r, buf)
+ if err != nil {
+ t.Error("unexpected error", err)
+ }
+ i := 1
+ for ; r != nil; r = r.next {
+ if r.i != i {
+ t.Fatalf("bad scan: expected %d got %d", i, r.i)
+ }
+ i++
+ }
+ if i-1 != intCount {
+ t.Fatalf("bad scan count: expected %d got %d", intCount, i-1)
+ }
+}
+
+func BenchmarkScanInts(b *testing.B) {
+ b.ResetTimer()
+ ints := makeInts(intCount)
+ var r RecursiveInt
+ for i := b.N - 1; i >= 0; i-- {
+ buf := bytes.NewBuffer(ints)
+ b.StartTimer()
+ scanInts(&r, buf)
+ b.StopTimer()
+ }
+}
+
+func BenchmarkScanRecursiveInt(b *testing.B) {
+ b.ResetTimer()
+ ints := makeInts(intCount)
+ var r RecursiveInt
+ for i := b.N - 1; i >= 0; i-- {
+ buf := bytes.NewBuffer(ints)
+ b.StartTimer()
+ Fscan(buf, &r)
+ b.StopTimer()
+ }
+}
+
+func BenchmarkScanRecursiveIntReaderWrapper(b *testing.B) {
+ b.ResetTimer()
+ ints := makeInts(intCount)
+ var r RecursiveInt
+ for i := b.N - 1; i >= 0; i-- {
+ buf := struct{ io.Reader }{strings.NewReader(string(ints))}
+ b.StartTimer()
+ Fscan(buf, &r)
+ b.StopTimer()
+ }
+}
+
+// Issue 9124.
+// %x on bytes couldn't handle non-space bytes terminating the scan.
+func TestHexBytes(t *testing.T) {
+ var a, b []byte
+ n, err := Sscanf("00010203", "%x", &a)
+ if n != 1 || err != nil {
+ t.Errorf("simple: got count, err = %d, %v; expected 1, nil", n, err)
+ }
+ check := func(msg string, x []byte) {
+ if len(x) != 4 {
+ t.Errorf("%s: bad length %d", msg, len(x))
+ }
+ for i, b := range x {
+ if int(b) != i {
+ t.Errorf("%s: bad x[%d] = %x", msg, i, x[i])
+ }
+ }
+ }
+ check("simple", a)
+ a = nil
+
+ n, err = Sscanf("00010203 00010203", "%x %x", &a, &b)
+ if n != 2 || err != nil {
+ t.Errorf("simple pair: got count, err = %d, %v; expected 2, nil", n, err)
+ }
+ check("simple pair a", a)
+ check("simple pair b", b)
+ a = nil
+ b = nil
+
+ n, err = Sscanf("00010203:", "%x", &a)
+ if n != 1 || err != nil {
+ t.Errorf("colon: got count, err = %d, %v; expected 1, nil", n, err)
+ }
+ check("colon", a)
+ a = nil
+
+ n, err = Sscanf("00010203:00010203", "%x:%x", &a, &b)
+ if n != 2 || err != nil {
+ t.Errorf("colon pair: got count, err = %d, %v; expected 2, nil", n, err)
+ }
+ check("colon pair a", a)
+ check("colon pair b", b)
+ a = nil
+ b = nil
+
+ // This one fails because there is a hex byte after the data,
+ // that is, an odd number of hex input bytes.
+ n, err = Sscanf("000102034:", "%x", &a)
+ if n != 0 || err == nil {
+ t.Errorf("odd count: got count, err = %d, %v; expected 0, error", n, err)
+ }
+}
+
+func TestScanNewlinesAreSpaces(t *testing.T) {
+ var a, b int
+ var tests = []struct {
+ name string
+ text string
+ count int
+ }{
+ {"newlines", "1\n2\n", 2},
+ {"no final newline", "1\n2", 2},
+ {"newlines with spaces ", "1 \n 2 \n", 2},
+ {"no final newline with spaces", "1 \n 2", 2},
+ }
+ for _, test := range tests {
+ n, err := Sscan(test.text, &a, &b)
+ if n != test.count {
+ t.Errorf("%s: expected to scan %d item(s), scanned %d", test.name, test.count, n)
+ }
+ if err != nil {
+ t.Errorf("%s: unexpected error: %s", test.name, err)
+ }
+ }
+}
+
+func TestScanlnNewlinesTerminate(t *testing.T) {
+ var a, b int
+ var tests = []struct {
+ name string
+ text string
+ count int
+ ok bool
+ }{
+ {"one line one item", "1\n", 1, false},
+ {"one line two items with spaces ", " 1 2 \n", 2, true},
+ {"one line two items no newline", " 1 2", 2, true},
+ {"two lines two items", "1\n2\n", 1, false},
+ }
+ for _, test := range tests {
+ n, err := Sscanln(test.text, &a, &b)
+ if n != test.count {
+ t.Errorf("%s: expected to scan %d item(s), scanned %d", test.name, test.count, n)
+ }
+ if test.ok && err != nil {
+ t.Errorf("%s: unexpected error: %s", test.name, err)
+ }
+ if !test.ok && err == nil {
+ t.Errorf("%s: expected error; got none", test.name)
+ }
+ }
+}
+
+func TestScanfNewlineMatchFormat(t *testing.T) {
+ var a, b int
+ var tests = []struct {
+ name string
+ text string
+ format string
+ count int
+ ok bool
+ }{
+ {"newline in both", "1\n2", "%d\n%d\n", 2, true},
+ {"newline in input", "1\n2", "%d %d", 1, false},
+ {"space-newline in input", "1 \n2", "%d %d", 1, false},
+ {"newline in format", "1 2", "%d\n%d", 1, false},
+ {"space-newline in format", "1 2", "%d \n%d", 1, false},
+ {"space-newline in both", "1 \n2", "%d \n%d", 2, true},
+ {"extra space in format", "1\n2", "%d\n %d", 2, true},
+ {"two extra spaces in format", "1\n2", "%d \n %d", 2, true},
+ {"space vs newline 0000", "1\n2", "%d\n%d", 2, true},
+ {"space vs newline 0001", "1\n2", "%d\n %d", 2, true},
+ {"space vs newline 0010", "1\n2", "%d \n%d", 2, true},
+ {"space vs newline 0011", "1\n2", "%d \n %d", 2, true},
+ {"space vs newline 0100", "1\n 2", "%d\n%d", 2, true},
+ {"space vs newline 0101", "1\n 2", "%d\n%d ", 2, true},
+ {"space vs newline 0110", "1\n 2", "%d \n%d", 2, true},
+ {"space vs newline 0111", "1\n 2", "%d \n %d", 2, true},
+ {"space vs newline 1000", "1 \n2", "%d\n%d", 2, true},
+ {"space vs newline 1001", "1 \n2", "%d\n %d", 2, true},
+ {"space vs newline 1010", "1 \n2", "%d \n%d", 2, true},
+ {"space vs newline 1011", "1 \n2", "%d \n %d", 2, true},
+ {"space vs newline 1100", "1 \n 2", "%d\n%d", 2, true},
+ {"space vs newline 1101", "1 \n 2", "%d\n %d", 2, true},
+ {"space vs newline 1110", "1 \n 2", "%d \n%d", 2, true},
+ {"space vs newline 1111", "1 \n 2", "%d \n %d", 2, true},
+ {"space vs newline no-percent 0000", "1\n2", "1\n2", 0, true},
+ {"space vs newline no-percent 0001", "1\n2", "1\n 2", 0, true},
+ {"space vs newline no-percent 0010", "1\n2", "1 \n2", 0, true},
+ {"space vs newline no-percent 0011", "1\n2", "1 \n 2", 0, true},
+ {"space vs newline no-percent 0100", "1\n 2", "1\n2", 0, false}, // fails: space after nl in input but not pattern
+ {"space vs newline no-percent 0101", "1\n 2", "1\n2 ", 0, false}, // fails: space after nl in input but not pattern
+ {"space vs newline no-percent 0110", "1\n 2", "1 \n2", 0, false}, // fails: space after nl in input but not pattern
+ {"space vs newline no-percent 0111", "1\n 2", "1 \n 2", 0, true},
+ {"space vs newline no-percent 1000", "1 \n2", "1\n2", 0, true},
+ {"space vs newline no-percent 1001", "1 \n2", "1\n 2", 0, true},
+ {"space vs newline no-percent 1010", "1 \n2", "1 \n2", 0, true},
+ {"space vs newline no-percent 1011", "1 \n2", "1 \n 2", 0, true},
+ {"space vs newline no-percent 1100", "1 \n 2", "1\n2", 0, false}, // fails: space after nl in input but not pattern
+ {"space vs newline no-percent 1101", "1 \n 2", "1\n 2", 0, true},
+ {"space vs newline no-percent 1110", "1 \n 2", "1 \n2", 0, false}, // fails: space after nl in input but not pattern
+ {"space vs newline no-percent 1111", "1 \n 2", "1 \n 2", 0, true},
+ }
+ for _, test := range tests {
+ var n int
+ var err error
+ if strings.Contains(test.format, "%") {
+ n, err = Sscanf(test.text, test.format, &a, &b)
+ } else {
+ n, err = Sscanf(test.text, test.format)
+ }
+ if n != test.count {
+ t.Errorf("%s: expected to scan %d item(s), scanned %d", test.name, test.count, n)
+ }
+ if test.ok && err != nil {
+ t.Errorf("%s: unexpected error: %s", test.name, err)
+ }
+ if !test.ok && err == nil {
+ t.Errorf("%s: expected error; got none", test.name)
+ }
+ }
+}
+
+// Test for issue 12090: Was unreading at EOF, double-scanning a byte.
+
+type hexBytes [2]byte
+
+func (h *hexBytes) Scan(ss ScanState, verb rune) error {
+ var b []byte
+ _, err := Fscanf(ss, "%4x", &b)
+ if err != nil {
+ panic(err) // Really shouldn't happen.
+ }
+ copy((*h)[:], b)
+ return err
+}
+
+func TestHexByte(t *testing.T) {
+ var h hexBytes
+ n, err := Sscanln("0123\n", &h)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != 1 {
+ t.Fatalf("expected 1 item; scanned %d", n)
+ }
+ if h[0] != 0x01 || h[1] != 0x23 {
+ t.Fatalf("expected 0123 got %x", h)
+ }
+}
diff --git a/errors/fmt/stringer_test.go b/errors/fmt/stringer_test.go
new file mode 100644
index 0000000..0ca3f52
--- /dev/null
+++ b/errors/fmt/stringer_test.go
@@ -0,0 +1,61 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmt_test
+
+import (
+ . "fmt"
+ "testing"
+)
+
+type TI int
+type TI8 int8
+type TI16 int16
+type TI32 int32
+type TI64 int64
+type TU uint
+type TU8 uint8
+type TU16 uint16
+type TU32 uint32
+type TU64 uint64
+type TUI uintptr
+type TF float64
+type TF32 float32
+type TF64 float64
+type TB bool
+type TS string
+
+func (v TI) String() string { return Sprintf("I: %d", int(v)) }
+func (v TI8) String() string { return Sprintf("I8: %d", int8(v)) }
+func (v TI16) String() string { return Sprintf("I16: %d", int16(v)) }
+func (v TI32) String() string { return Sprintf("I32: %d", int32(v)) }
+func (v TI64) String() string { return Sprintf("I64: %d", int64(v)) }
+func (v TU) String() string { return Sprintf("U: %d", uint(v)) }
+func (v TU8) String() string { return Sprintf("U8: %d", uint8(v)) }
+func (v TU16) String() string { return Sprintf("U16: %d", uint16(v)) }
+func (v TU32) String() string { return Sprintf("U32: %d", uint32(v)) }
+func (v TU64) String() string { return Sprintf("U64: %d", uint64(v)) }
+func (v TUI) String() string { return Sprintf("UI: %d", uintptr(v)) }
+func (v TF) String() string { return Sprintf("F: %f", float64(v)) }
+func (v TF32) String() string { return Sprintf("F32: %f", float32(v)) }
+func (v TF64) String() string { return Sprintf("F64: %f", float64(v)) }
+func (v TB) String() string { return Sprintf("B: %t", bool(v)) }
+func (v TS) String() string { return Sprintf("S: %q", string(v)) }
+
+func check(t *testing.T, got, want string) {
+ if got != want {
+ t.Error(got, "!=", want)
+ }
+}
+
+func TestStringer(t *testing.T) {
+ s := Sprintf("%v %v %v %v %v", TI(0), TI8(1), TI16(2), TI32(3), TI64(4))
+ check(t, s, "I: 0 I8: 1 I16: 2 I32: 3 I64: 4")
+ s = Sprintf("%v %v %v %v %v %v", TU(5), TU8(6), TU16(7), TU32(8), TU64(9), TUI(10))
+ check(t, s, "U: 5 U8: 6 U16: 7 U32: 8 U64: 9 UI: 10")
+ s = Sprintf("%v %v %v", TF(1.0), TF32(2.0), TF64(3.0))
+ check(t, s, "F: 1.000000 F32: 2.000000 F64: 3.000000")
+ s = Sprintf("%v %v", TB(true), TS("x"))
+ check(t, s, "B: true S: \"x\"")
+}
diff --git a/errors/format.go b/errors/format.go
new file mode 100644
index 0000000..12deed3
--- /dev/null
+++ b/errors/format.go
@@ -0,0 +1,34 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors
+
+// A Formatter formats error messages.
+type Formatter interface {
+ error
+
+ // FormatError prints the receiver's first error and returns the next error in
+ // the error chain, if any.
+ FormatError(p Printer) (next error)
+}
+
+// A Printer formats error messages.
+//
+// The most common implementation of Printer is the one provided by package fmt
+// during Printf. Localization packages such as golang.org/x/text/message
+// typically provide their own implementations.
+type Printer interface {
+ // Print appends args to the message output.
+ Print(args ...interface{})
+
+ // Printf writes a formatted string.
+ Printf(format string, args ...interface{})
+
+ // Detail reports whether error detail is requested.
+ // After the first call to Detail, all text written to the Printer
+ // is formatted as additional detail, or ignored when
+ // detail has not been requested.
+ // If Detail returns false, the caller can avoid printing the detail at all.
+ Detail() bool
+}
diff --git a/errors/frame.go b/errors/frame.go
new file mode 100644
index 0000000..a5369e5
--- /dev/null
+++ b/errors/frame.go
@@ -0,0 +1,56 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors
+
+import (
+ "runtime"
+)
+
+// A Frame contains part of a call stack.
+type Frame struct {
+ // Make room for three PCs: the one we were asked for, what it called,
+ // and possibly a PC for skipPleaseUseCallersFrames. See:
+ // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
+ frames [3]uintptr
+}
+
+// Caller returns a Frame that describes a frame on the caller's stack.
+// The argument skip is the number of frames to skip over.
+// Caller(0) returns the frame for the caller of Caller.
+func Caller(skip int) Frame {
+ var s Frame
+ runtime.Callers(skip+1, s.frames[:])
+ return s
+}
+
+// location reports the file, line, and function of a frame.
+//
+// The returned function may be "" even if file and line are not.
+func (f Frame) location() (function, file string, line int) {
+ frames := runtime.CallersFrames(f.frames[:])
+ if _, ok := frames.Next(); !ok {
+ return "", "", 0
+ }
+ fr, ok := frames.Next()
+ if !ok {
+ return "", "", 0
+ }
+ return fr.Function, fr.File, fr.Line
+}
+
+// Format prints the stack as error detail.
+// It should be called from an error's Format implementation,
+// before printing any other error detail.
+func (f Frame) Format(p Printer) {
+ if p.Detail() {
+ function, file, line := f.location()
+ if function != "" {
+ p.Printf("%s\n ", function)
+ }
+ if file != "" {
+ p.Printf("%s:%d\n", file, line)
+ }
+ }
+}
diff --git a/errors/go.mod b/errors/go.mod
new file mode 100644
index 0000000..c2e2976
--- /dev/null
+++ b/errors/go.mod
@@ -0,0 +1 @@
+module golang.org/x/exp/errors
diff --git a/errors/internal/internal.go b/errors/internal/internal.go
new file mode 100644
index 0000000..89f4eca
--- /dev/null
+++ b/errors/internal/internal.go
@@ -0,0 +1,8 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// EnableTrace indicates whether stack information should be recorded in errors.
+var EnableTrace = true
diff --git a/errors/stack_test.go b/errors/stack_test.go
new file mode 100644
index 0000000..3d11769
--- /dev/null
+++ b/errors/stack_test.go
@@ -0,0 +1,61 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+ "bytes"
+ fmtcore "fmt"
+ "math/big"
+ "testing"
+
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/fmt"
+ "golang.org/x/exp/errors/internal"
+)
+
+type myType struct{}
+
+func (myType) Format(s fmt.State, v rune) {
+ s.Write(bytes.Repeat([]byte("Hi! "), 10))
+}
+
+func BenchmarkErrorf(b *testing.B) {
+ err := errors.New("foo")
+ // pi := big.NewFloat(3.14) // Something expensive.
+ num := big.NewInt(5)
+ args := func(a ...interface{}) []interface{} { return a }
+ benchCases := []struct {
+ name string
+ format string
+ args []interface{}
+ }{
+ {"no_format", "msg: %v", args(err)},
+ {"with_format", "failed %d times: %v", args(5, err)},
+ {"method: mytype", "pi: %v", args("myfile.go", myType{}, err)},
+ {"method: number", "pi: %v", args("myfile.go", num, err)},
+ }
+ for _, bc := range benchCases {
+ b.Run(bc.name, func(b *testing.B) {
+ b.Run("ExpWithTrace", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fmt.Errorf(bc.format, bc.args...)
+ }
+ })
+ b.Run("ExpNoTrace", func(b *testing.B) {
+ internal.EnableTrace = false
+ defer func() { internal.EnableTrace = true }()
+
+ for i := 0; i < b.N; i++ {
+ fmt.Errorf(bc.format, bc.args...)
+ }
+ })
+ b.Run("Core", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fmtcore.Errorf(bc.format, bc.args...)
+ }
+ })
+ })
+ }
+}
diff --git a/errors/wrap.go b/errors/wrap.go
new file mode 100644
index 0000000..c936d97
--- /dev/null
+++ b/errors/wrap.go
@@ -0,0 +1,98 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors
+
+import (
+ "reflect"
+)
+
+// An Wrapper provides context around another error.
+type Wrapper interface {
+ // Unwrap returns the next error in the error chain.
+ // If there is no next error, Unwrap returns nil.
+ Unwrap() error
+}
+
+// Opaque returns an error with the same error formatting as err
+// but that does not match err and cannot be unwrapped.
+func Opaque(err error) error {
+ return noWrapper{err}
+}
+
+type noWrapper struct {
+ error
+}
+
+func (e noWrapper) FormatError(p Printer) (next error) {
+ if f, ok := e.error.(Formatter); ok {
+ return f.FormatError(p)
+ }
+ p.Print(e.error)
+ return nil
+}
+
+// Unwrap returns the next error in err's chain.
+// If there is no next error, Unwrap returns nil.
+func Unwrap(err error) error {
+ u, ok := err.(Wrapper)
+ if !ok {
+ return nil
+ }
+ return u.Unwrap()
+}
+
+// Is returns true if any error in err's chain matches target.
+//
+// An error is considered to match a target if it is equal to that target or if
+// it implements an Is method such that Is(target) returns true.
+func Is(err, target error) bool {
+ if target == nil {
+ return err == target
+ }
+ for {
+ if err == target {
+ return true
+ }
+ if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
+ return true
+ }
+ // TODO: consider supporing target.Is(err). This would allow
+ // user-definable predicates, but also may allow for coping with sloppy
+ // APIs, thereby making it easier to get away with them.
+ if err = Unwrap(err); err == nil {
+ return false
+ }
+ }
+}
+
+// As finds the first error in err's chain that matches a type to which target
+// points, and if so, sets the target to its value and reports success. An error
+// matches a type if it is of the same type, or if it has an As method such that
+// As(target) returns true. As will panic if target is nil or not a pointer.
+//
+// The As method should set the target to its value and report success if err
+// matches the type to which target points and report success.
+func As(err error, target interface{}) bool {
+ if target == nil {
+ panic("errors: target cannot be nil")
+ }
+ typ := reflect.TypeOf(target)
+ if typ.Kind() != reflect.Ptr {
+ panic("errors: target must be a pointer")
+ }
+ targetType := typ.Elem()
+ for {
+ if reflect.TypeOf(err) == targetType {
+ reflect.ValueOf(target).Elem().Set(reflect.ValueOf(err))
+ return true
+ }
+ if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
+ return true
+ }
+ if err = Unwrap(err); err == nil {
+ return false
+ }
+ }
+}
diff --git a/errors/wrap_test.go b/errors/wrap_test.go
new file mode 100644
index 0000000..f72425d
--- /dev/null
+++ b/errors/wrap_test.go
@@ -0,0 +1,203 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+ "os"
+ "testing"
+
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/fmt"
+)
+
+func TestIs(t *testing.T) {
+ err1 := errors.New("1")
+ erra := fmt.Errorf("wrap 2: %w", err1)
+ errb := fmt.Errorf("wrap 3: %w", erra)
+ erro := errors.Opaque(err1)
+ errco := fmt.Errorf("opaque: %w", erro)
+
+ err3 := errors.New("3")
+
+ poser := &poser{"either 1 or 3", func(err error) bool {
+ return err == err1 || err == err3
+ }}
+
+ testCases := []struct {
+ err error
+ target error
+ match bool
+ }{
+ {nil, nil, true},
+ {err1, nil, false},
+ {err1, err1, true},
+ {erra, err1, true},
+ {errb, err1, true},
+ {errco, erro, true},
+ {errco, err1, false},
+ {erro, erro, true},
+ {err1, err3, false},
+ {erra, err3, false},
+ {errb, err3, false},
+ {poser, err1, true},
+ {poser, err3, true},
+ {poser, erra, false},
+ {poser, errb, false},
+ {poser, erro, false},
+ {poser, errco, false},
+ }
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ if got := errors.Is(tc.err, tc.target); got != tc.match {
+ t.Errorf("Is(%v, %v) = %v, want %v", tc.err, tc.target, got, tc.match)
+ }
+ })
+ }
+}
+
+type poser struct {
+ msg string
+ f func(error) bool
+}
+
+func (p *poser) Error() string { return p.msg }
+func (p *poser) Is(err error) bool { return p.f(err) }
+func (p *poser) As(err interface{}) bool {
+ switch x := err.(type) {
+ case **poser:
+ *x = p
+ case *errorT:
+ *x = errorT{}
+ case **os.PathError:
+ *x = &os.PathError{}
+ default:
+ return false
+ }
+ return true
+}
+
+func TestAs(t *testing.T) {
+ var errT errorT
+ var errP *os.PathError
+ var p *poser
+ _, errF := os.Open("non-existing")
+
+ testCases := []struct {
+ err error
+ target interface{}
+ match bool
+ }{{
+ fmt.Errorf("pittied the fool: %w", errorT{}),
+ &errT,
+ true,
+ }, {
+ errF,
+ &errP,
+ true,
+ }, {
+ errors.Opaque(errT),
+ &errT,
+ false,
+ }, {
+ errorT{},
+ &errP,
+ false,
+ }, {
+ wrapped{nil},
+ &errT,
+ false,
+ }, {
+ &poser{"error", nil},
+ &errT,
+ true,
+ }, {
+ &poser{"path", nil},
+ &errP,
+ true,
+ }, {
+ &poser{"oh no", nil},
+ &p,
+ true,
+ }, {
+ &poser{"oo", nil},
+ &errF,
+ false,
+ }}
+ for _, tc := range testCases {
+ name := fmt.Sprintf("As(Errorf(..., %v), %v)", tc.err, tc.target)
+ t.Run(name, func(t *testing.T) {
+ match := errors.As(tc.err, tc.target)
+ if match != tc.match {
+ t.Fatalf("match: got %v; want %v", match, tc.match)
+ }
+ if !match {
+ return
+ }
+ if tc.target == nil {
+ t.Fatalf("non-nil result after match")
+ }
+ })
+ }
+}
+
+func TestUnwrap(t *testing.T) {
+ err1 := errors.New("1")
+ erra := fmt.Errorf("wrap 2: %w", err1)
+ erro := errors.Opaque(err1)
+
+ testCases := []struct {
+ err error
+ want error
+ }{
+ {nil, nil},
+ {wrapped{nil}, nil},
+ {err1, nil},
+ {erra, err1},
+ {fmt.Errorf("wrap 3: %w", erra), erra},
+
+ {erro, nil},
+ {fmt.Errorf("opaque: %w", erro), erro},
+ }
+ for _, tc := range testCases {
+ if got := errors.Unwrap(tc.err); got != tc.want {
+ t.Errorf("Unwrap(%v) = %v, want %v", tc.err, got, tc.want)
+ }
+ }
+}
+
+func TestOpaque(t *testing.T) {
+ got := fmt.Errorf("foo: %+v", errors.Opaque(errorT{}))
+ want := "foo: errorT"
+ if got.Error() != want {
+ t.Errorf("error without Format: got %v; want %v", got, want)
+ }
+
+ got = fmt.Errorf("foo: %+v", errors.Opaque(errorD{}))
+ want = "foo: errorD:\n detail"
+ if got.Error() != want {
+ t.Errorf("error with Format: got %v; want %v", got, want)
+ }
+}
+
+type errorT struct{}
+
+func (errorT) Error() string { return "errorT" }
+
+type errorD struct{}
+
+func (errorD) Error() string { return "errorD" }
+
+func (errorD) FormatError(p errors.Printer) error {
+ p.Print("errorD")
+ p.Detail()
+ p.Print("detail")
+ return nil
+}
+
+type wrapped struct{ error }
+
+func (wrapped) Error() string { return "wrapped" }
+
+func (wrapped) Unwrap() error { return nil }
diff --git a/event/adapter/gokit/bench_test.go b/event/adapter/gokit/bench_test.go
new file mode 100644
index 0000000..2313abb
--- /dev/null
+++ b/event/adapter/gokit/bench_test.go
@@ -0,0 +1,71 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gokit_test
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "testing"
+
+ "github.com/go-kit/kit/log"
+ "golang.org/x/exp/event/adapter/logfmt"
+ "golang.org/x/exp/event/eventtest"
+)
+
+var (
+ gokitLog = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ gokitCtx(ctx).Log(eventtest.A.Name, a, "msg", eventtest.A.Msg)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ gokitCtx(ctx).Log(eventtest.B.Name, b, "msg", eventtest.B.Msg)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+ gokitLogf = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ gokitCtx(ctx).Log("msg", fmt.Sprintf(eventtest.A.Msgf, a))
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ gokitCtx(ctx).Log("msg", fmt.Sprintf(eventtest.B.Msgf, b))
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+)
+
+type gokitKey struct{}
+
+func gokitCtx(ctx context.Context) log.Logger {
+ return ctx.Value(gokitKey{}).(log.Logger)
+}
+
+func gokitPrint(w io.Writer) context.Context {
+ logger := log.NewLogfmtLogger(log.NewSyncWriter(w))
+ now := eventtest.ExporterOptions().Now
+ logger = log.With(logger, "time", log.TimestampFormat(now, logfmt.TimeFormat), "level", "info")
+ return context.WithValue(context.Background(), gokitKey{}, logger)
+}
+
+func BenchmarkGokitLogDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, gokitPrint(io.Discard), gokitLog)
+}
+
+func BenchmarkGokitLogfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, gokitPrint(io.Discard), gokitLogf)
+}
+
+func TestGokitLogfDiscard(t *testing.T) {
+ eventtest.TestBenchmark(t, gokitPrint, gokitLogf, eventtest.LogfOutput)
+}
+func TestLogGokit(t *testing.T) {
+ eventtest.TestBenchmark(t, gokitPrint, gokitLog, eventtest.LogfmtOutput)
+}
diff --git a/event/adapter/gokit/gokit.go b/event/adapter/gokit/gokit.go
new file mode 100644
index 0000000..e532266
--- /dev/null
+++ b/event/adapter/gokit/gokit.go
@@ -0,0 +1,53 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package gokit provides a go-kit logger for events.
+package gokit
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-kit/kit/log"
+ "golang.org/x/exp/event"
+)
+
+type logger struct {
+}
+
+// NewLogger returns a logger.
+func NewLogger() log.Logger {
+ return &logger{}
+}
+
+// Log writes a structured log message.
+// If the first argument is a context.Context, it is used
+// to find the exporter to which to deliver the message.
+// Otherwise, the default exporter is used.
+func (l *logger) Log(keyvals ...interface{}) error {
+ ctx := context.Background()
+ if len(keyvals) > 0 {
+ if c, ok := keyvals[0].(context.Context); ok {
+ ctx = c
+ keyvals = keyvals[1:]
+ }
+ }
+ ev := event.New(ctx, event.LogKind)
+ if ev == nil {
+ return nil
+ }
+ var msg string
+ for i := 0; i < len(keyvals); i += 2 {
+ key := keyvals[i].(string)
+ value := keyvals[i+1]
+ if key == "msg" || key == "message" {
+ msg = fmt.Sprint(value)
+ } else {
+ ev.Labels = append(ev.Labels, event.Value(key, value))
+ }
+ }
+ ev.Labels = append(ev.Labels, event.String("msg", msg))
+ ev.Deliver()
+ return nil
+}
diff --git a/event/adapter/gokit/gokit_test.go b/event/adapter/gokit/gokit_test.go
new file mode 100644
index 0000000..29169f8
--- /dev/null
+++ b/event/adapter/gokit/gokit_test.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package gokit_test
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/gokit"
+ "golang.org/x/exp/event/eventtest"
+)
+
+func Test(t *testing.T) {
+ log := gokit.NewLogger()
+ ctx, h := eventtest.NewCapture()
+ log.Log(ctx, "msg", "mess", "level", 1, "name", "n/m", "traceID", 17, "resource", "R")
+ want := []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ event.Value("level", 1),
+ event.Value("name", "n/m"),
+ event.Value("traceID", 17),
+ event.Value("resource", "R"),
+ event.String("msg", "mess"),
+ },
+ }}
+ if diff := cmp.Diff(want, h.Got, eventtest.CmpOptions()...); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+}
diff --git a/event/adapter/logfmt/logfmt.go b/event/adapter/logfmt/logfmt.go
new file mode 100644
index 0000000..5789e7a
--- /dev/null
+++ b/event/adapter/logfmt/logfmt.go
@@ -0,0 +1,258 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package logfmt
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "strconv"
+ "time"
+ "unicode/utf8"
+
+ "golang.org/x/exp/event"
+)
+
+// TODO: some actual research into what this arbritray optimization number should be
+const bufCap = 50
+
+const TimeFormat = "2006/01/02 15:04:05"
+
+type Printer struct {
+ QuoteValues bool
+ SuppressNamespace bool
+ buf [bufCap]byte
+ needSep bool
+ w bytes.Buffer
+}
+
+type Handler struct {
+ to io.Writer
+ Printer
+}
+
+// NewHandler returns a handler that prints the events to the supplied writer.
+// Each event is printed in logfmt format on a single line.
+func NewHandler(to io.Writer) *Handler {
+ return &Handler{to: to}
+}
+
+func (h *Handler) Event(ctx context.Context, ev *event.Event) context.Context {
+ h.Printer.Event(h.to, ev)
+ return ctx
+}
+
+func (p *Printer) Event(w io.Writer, ev *event.Event) {
+ p.needSep = false
+ if !ev.At.IsZero() {
+ p.separator(w)
+ io.WriteString(w, `time="`)
+ p.time(w, ev.At)
+ io.WriteString(w, `"`)
+ }
+
+ if !p.SuppressNamespace && ev.Source.Space != "" {
+ p.Label(w, event.String("in", ev.Source.Space))
+ }
+ if ev.Source.Owner != "" {
+ p.Label(w, event.String("owner", ev.Source.Owner))
+ }
+ if ev.Source.Name != "" {
+ p.Label(w, event.String("name", ev.Source.Name))
+ }
+
+ if ev.Parent != 0 {
+ p.separator(w)
+ io.WriteString(w, `parent=`)
+ w.Write(strconv.AppendUint(p.buf[:0], ev.Parent, 10))
+ }
+ for _, l := range ev.Labels {
+ if l.Name == "" {
+ continue
+ }
+ p.Label(w, l)
+ }
+
+ if ev.ID != 0 && ev.Kind == event.StartKind {
+ p.separator(w)
+ io.WriteString(w, `trace=`)
+ w.Write(strconv.AppendUint(p.buf[:0], ev.ID, 10))
+ }
+
+ if ev.Kind == event.EndKind {
+ p.separator(w)
+ io.WriteString(w, `end`)
+ }
+
+ io.WriteString(w, "\n")
+}
+
+func (p *Printer) separator(w io.Writer) {
+ if p.needSep {
+ io.WriteString(w, " ")
+ }
+ p.needSep = true
+}
+
+func (p *Printer) Label(w io.Writer, l event.Label) {
+ if l.Name == "" {
+ return
+ }
+ p.separator(w)
+ p.Ident(w, l.Name)
+ if l.HasValue() {
+ io.WriteString(w, "=")
+ switch {
+ case l.IsString():
+ p.string(w, l.String())
+ case l.IsBytes():
+ p.bytes(w, l.Bytes())
+ case l.IsInt64():
+ w.Write(strconv.AppendInt(p.buf[:0], l.Int64(), 10))
+ case l.IsUint64():
+ w.Write(strconv.AppendUint(p.buf[:0], l.Uint64(), 10))
+ case l.IsFloat64():
+ w.Write(strconv.AppendFloat(p.buf[:0], l.Float64(), 'g', -1, 64))
+ case l.IsBool():
+ if l.Bool() {
+ io.WriteString(w, "true")
+ } else {
+ io.WriteString(w, "false")
+ }
+ default:
+ v := l.Interface()
+ switch v := v.(type) {
+ case string:
+ p.string(w, v)
+ case fmt.Stringer:
+ p.string(w, v.String())
+ default:
+ if p.w.Cap() == 0 {
+ // we rely on the inliner to cause this to not allocate
+ p.w = *bytes.NewBuffer(p.buf[:0])
+ }
+ fmt.Fprint(&p.w, v)
+ b := p.w.Bytes()
+ p.w.Reset()
+ p.bytes(w, b)
+ }
+ }
+ }
+}
+
+func (p *Printer) Ident(w io.Writer, s string) {
+ if !stringNeedQuote(s) {
+ io.WriteString(w, s)
+ return
+ }
+ p.quoteString(w, s)
+}
+
+func (p *Printer) string(w io.Writer, s string) {
+ if p.QuoteValues || stringNeedQuote(s) {
+ p.quoteString(w, s)
+ } else {
+ io.WriteString(w, s)
+ }
+}
+
+func (p *Printer) quoteString(w io.Writer, s string) {
+ io.WriteString(w, `"`)
+ written := 0
+ for offset, r := range s {
+ q := quoteRune(r)
+ if len(q) == 0 {
+ continue
+ }
+ // write out any prefix
+ io.WriteString(w, s[written:offset])
+ written = offset + utf8.RuneLen(r)
+ // and write out the quoted rune
+ io.WriteString(w, q)
+ }
+ io.WriteString(w, s[written:])
+ io.WriteString(w, `"`)
+}
+
+func (p *Printer) bytes(w io.Writer, buf []byte) {
+ if p.QuoteValues || stringNeedQuote(string(buf)) {
+ p.quoteBytes(w, buf)
+ } else {
+ w.Write(buf)
+ }
+}
+
+// Bytes writes a byte array in string form to the printer.
+func (p *Printer) quoteBytes(w io.Writer, buf []byte) {
+ io.WriteString(w, `"`)
+ written := 0
+ for offset := 0; offset < len(buf); {
+ r, size := utf8.DecodeRune(buf[offset:])
+ offset += size
+ q := quoteRune(r)
+ if len(q) == 0 {
+ continue
+ }
+ // write out any prefix
+ w.Write(buf[written : offset-size])
+ written = offset
+ // and write out the quoted rune
+ io.WriteString(w, q)
+ }
+ w.Write(buf[written:])
+ io.WriteString(w, `"`)
+}
+
+// time writes a timstamp in the same format as
+func (p *Printer) time(w io.Writer, t time.Time) {
+ year, month, day := t.Date()
+ hour, minute, second := t.Clock()
+ p.padInt(w, int64(year), 4)
+ io.WriteString(w, `/`)
+ p.padInt(w, int64(month), 2)
+ io.WriteString(w, `/`)
+ p.padInt(w, int64(day), 2)
+ io.WriteString(w, ` `)
+ p.padInt(w, int64(hour), 2)
+ io.WriteString(w, `:`)
+ p.padInt(w, int64(minute), 2)
+ io.WriteString(w, `:`)
+ p.padInt(w, int64(second), 2)
+}
+
+func (p *Printer) padInt(w io.Writer, v int64, width int) {
+ b := strconv.AppendInt(p.buf[:0], int64(v), 10)
+ if len(b) < width {
+ io.WriteString(w, "0000"[:width-len(b)])
+ }
+ w.Write(b)
+}
+
+func stringNeedQuote(s string) bool {
+ if len(s) == 0 {
+ return true
+ }
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if c >= utf8.RuneSelf || c == ' ' || c == '"' || c == '\n' || c == '\\' {
+ return true
+ }
+ }
+ return false
+}
+
+func quoteRune(r rune) string {
+ switch r {
+ case '"':
+ return `\"`
+ case '\n':
+ return `\n`
+ case '\\':
+ return `\\`
+ default:
+ return ``
+ }
+}
diff --git a/event/adapter/logfmt/logfmt_test.go b/event/adapter/logfmt/logfmt_test.go
new file mode 100644
index 0000000..90b0e6f
--- /dev/null
+++ b/event/adapter/logfmt/logfmt_test.go
@@ -0,0 +1,198 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package logfmt_test
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/logfmt"
+)
+
+func TestPrint(t *testing.T) {
+ var p logfmt.Printer
+ buf := &strings.Builder{}
+ // the exporter is not used, but we need it to build the events
+ at, _ := time.Parse(logfmt.TimeFormat, "2020/03/05 14:27:48")
+ for _, test := range []struct {
+ name string
+ event event.Event
+ expect string
+ }{{
+ name: "empty",
+ event: event.Event{},
+ expect: ``,
+ }, {
+ name: "span",
+ event: event.Event{ID: 34, Kind: event.StartKind},
+ expect: `trace=34`,
+ }, {
+ name: "parent",
+ event: event.Event{Parent: 14},
+ expect: `parent=14`,
+ }, {
+ name: "namespace",
+ event: event.Event{Source: event.Source{Space: "golang.org/x/exp/event"}},
+ expect: `in=golang.org/x/exp/event`,
+ }, {
+ name: "at",
+ event: event.Event{At: at},
+ expect: `time="2020/03/05 14:27:48"`,
+ }, {
+ name: "message",
+ event: event.Event{Labels: []event.Label{event.String("msg", "a message")}},
+ expect: `msg="a message"`,
+ }, {
+ name: "end",
+ event: event.Event{Kind: event.EndKind},
+ expect: `end`,
+ }, {
+ name: "string",
+ event: event.Event{
+ Labels: []event.Label{
+ event.String("v1", "text"),
+ event.String("v2", "text with quotes"),
+ event.String("empty", ""),
+ },
+ },
+ expect: `v1=text v2="text with quotes" empty=""`,
+ }, {
+ name: "int",
+ event: event.Event{
+ Labels: []event.Label{
+ event.Int64("value", 67),
+ },
+ },
+ expect: `value=67`,
+ }, {
+ name: "float",
+ event: event.Event{
+ Labels: []event.Label{
+ event.Float64("value", 263.2),
+ },
+ },
+ expect: `value=263.2`,
+ }, {
+ name: "bool",
+ event: event.Event{
+ Labels: []event.Label{
+ event.Bool("v1", true),
+ event.Bool("v2", false),
+ },
+ },
+ expect: `v1=true v2=false`,
+ }, {
+ name: "value",
+ event: event.Event{
+ Labels: []event.Label{
+ event.Value("v1", notString{"simple"}),
+ event.Value("v2", notString{"needs quoting"}),
+ },
+ },
+ expect: `v1=simple v2="needs quoting"`,
+ }, {
+ name: "empty label",
+ event: event.Event{
+ Labels: []event.Label{
+ event.Value("before", nil),
+ event.String("", "text"),
+ event.Value("after", nil),
+ },
+ },
+ expect: `before after`,
+ }, {
+ name: "quoted ident",
+ event: event.Event{
+ Labels: []event.Label{
+ event.String("name with space", "text"),
+ },
+ },
+ expect: `"name with space"=text`,
+ }, {
+ name: "quoting quote",
+ event: event.Event{Labels: []event.Label{event.String("msg", `with"middle`)}},
+ expect: `msg="with\"middle"`,
+ }, {
+ name: "quoting newline",
+ event: event.Event{Labels: []event.Label{event.String("msg", "with\nmiddle")}},
+ expect: `msg="with\nmiddle"`,
+ }, {
+ name: "quoting slash",
+ event: event.Event{Labels: []event.Label{event.String("msg", `with\middle`)}},
+ expect: `msg="with\\middle"`,
+ }, {
+ name: "quoting bytes",
+ event: event.Event{
+ Labels: []event.Label{
+ event.Bytes("value", ([]byte)(`bytes "need" quote`)),
+ },
+ },
+ expect: `value="bytes \"need\" quote"`,
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ buf.Reset()
+ p.Event(buf, &test.event)
+ got := strings.TrimSpace(buf.String())
+ if got != test.expect {
+ t.Errorf("got: \n%q\nexpect:\n%q\n", got, test.expect)
+ }
+ })
+ }
+}
+
+type notString struct {
+ value string
+}
+
+func (v notString) String() string { return v.value }
+
+func TestPrinterFlags(t *testing.T) {
+ var reference logfmt.Printer
+ buf := &strings.Builder{}
+ // the exporter is not used, but we need it to build the events
+ for _, test := range []struct {
+ name string
+ printer logfmt.Printer
+ event event.Event
+ before string
+ after string
+ }{{
+ name: "quote values",
+ printer: logfmt.Printer{QuoteValues: true},
+ event: event.Event{
+ Labels: []event.Label{
+ event.String("value", "text"),
+ },
+ },
+ before: `value=text`,
+ after: `value="text"`,
+ }, {
+ name: "suppress namespace",
+ printer: logfmt.Printer{SuppressNamespace: true},
+ event: event.Event{
+ Source: event.Source{Space: "golang.org/x/exp/event"},
+ Labels: []event.Label{event.String("msg", "some text")},
+ },
+ before: `in=golang.org/x/exp/event msg="some text"`,
+ after: `msg="some text"`,
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ buf.Reset()
+ reference.Event(buf, &test.event)
+ gotBefore := strings.TrimSpace(buf.String())
+ buf.Reset()
+ test.printer.Event(buf, &test.event)
+ gotAfter := strings.TrimSpace(buf.String())
+ if gotBefore != test.before {
+ t.Errorf("got: \n%q\nexpect:\n%q\n", gotBefore, test.before)
+ }
+ if gotAfter != test.after {
+ t.Errorf("got: \n%q\nexpect:\n%q\n", gotAfter, test.after)
+ }
+ })
+ }
+}
diff --git a/event/adapter/logr/logr.go b/event/adapter/logr/logr.go
new file mode 100644
index 0000000..0dcfa26
--- /dev/null
+++ b/event/adapter/logr/logr.go
@@ -0,0 +1,104 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package logr is a logr implementation that uses events.
+package logr
+
+import (
+ "context"
+
+ "github.com/go-logr/logr"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/severity"
+)
+
+type logSink struct {
+ ev *event.Event // cloned, never delivered
+ labels []event.Label
+ nameSep string
+ name string
+ verbosity int
+}
+
+func NewLogger(ctx context.Context, nameSep string) logr.Logger {
+ return logr.New(&logSink{
+ ev: event.New(ctx, event.LogKind),
+ nameSep: nameSep,
+ })
+}
+
+func (*logSink) Init(logr.RuntimeInfo) {}
+
+// WithName implements logr.LogSink.WithName.
+func (l *logSink) WithName(name string) logr.LogSink {
+ l2 := *l
+ if l.name == "" {
+ l2.name = name
+ } else {
+ l2.name = l.name + l.nameSep + name
+ }
+ return &l2
+}
+
+// Enabled tests whether this LogSink is enabled at the specified V-level.
+// For example, commandline flags might be used to set the logging
+// verbosity and disable some info logs.
+func (l *logSink) Enabled(level int) bool {
+ return true
+}
+
+// Info implements logr.LogSink.Info.
+func (l *logSink) Info(level int, msg string, keysAndValues ...interface{}) {
+ if l.ev == nil {
+ return
+ }
+ ev := l.ev.Clone()
+ ev.Labels = append(ev.Labels, convertVerbosity(level).Label())
+ l.log(ev, msg, keysAndValues)
+}
+
+// Error implements logr.LogSink.Error.
+func (l *logSink) Error(err error, msg string, keysAndValues ...interface{}) {
+ if l.ev == nil {
+ return
+ }
+ ev := l.ev.Clone()
+ ev.Labels = append(ev.Labels, event.Value("error", err))
+ l.log(ev, msg, keysAndValues)
+}
+
+func (l *logSink) log(ev *event.Event, msg string, keysAndValues []interface{}) {
+ ev.Labels = append(ev.Labels)
+ ev.Labels = append(ev.Labels, l.labels...)
+ for i := 0; i < len(keysAndValues); i += 2 {
+ ev.Labels = append(ev.Labels, newLabel(keysAndValues[i], keysAndValues[i+1]))
+ }
+ ev.Labels = append(ev.Labels,
+ event.String("name", l.name),
+ event.String("msg", msg),
+ )
+ ev.Deliver()
+}
+
+// WithValues implements logr.LogSink.WithValues.
+func (l *logSink) WithValues(keysAndValues ...interface{}) logr.LogSink {
+ l2 := *l
+ if len(keysAndValues) > 0 {
+ l2.labels = make([]event.Label, len(l.labels), len(l.labels)+(len(keysAndValues)/2))
+ copy(l2.labels, l.labels)
+ for i := 0; i < len(keysAndValues); i += 2 {
+ l2.labels = append(l2.labels, newLabel(keysAndValues[i], keysAndValues[i+1]))
+ }
+ }
+ return &l2
+}
+
+func newLabel(key, value interface{}) event.Label {
+ return event.Value(key.(string), value)
+}
+
+func convertVerbosity(v int) severity.Level {
+ //TODO: this needs to be more complicated, v decreases with increasing severity
+ return severity.Level(v)
+}
diff --git a/event/adapter/logr/logr_test.go b/event/adapter/logr/logr_test.go
new file mode 100644
index 0000000..aaa877e
--- /dev/null
+++ b/event/adapter/logr/logr_test.go
@@ -0,0 +1,38 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package logr_test
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/exp/event"
+ elogr "golang.org/x/exp/event/adapter/logr"
+ "golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/event/severity"
+)
+
+func TestInfo(t *testing.T) {
+ ctx, th := eventtest.NewCapture()
+ log := elogr.NewLogger(ctx, "/").WithName("n").V(int(severity.Debug))
+ log = log.WithName("m")
+ log.Info("mess", "traceID", 17, "resource", "R")
+ want := []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ severity.Debug.Label(),
+ event.Value("traceID", 17),
+ event.Value("resource", "R"),
+ event.String("name", "n/m"),
+ event.String("msg", "mess"),
+ },
+ }}
+ if diff := cmp.Diff(want, th.Got, eventtest.CmpOptions()...); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+}
diff --git a/event/adapter/logrus/bench_test.go b/event/adapter/logrus/bench_test.go
new file mode 100644
index 0000000..da9f5d6
--- /dev/null
+++ b/event/adapter/logrus/bench_test.go
@@ -0,0 +1,87 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package logrus_test
+
+import (
+ "context"
+ "io"
+ "testing"
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/event/eventtest"
+)
+
+var (
+ logrusLog = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ logrusCtx(ctx).WithField(eventtest.A.Name, a).Info(eventtest.A.Msg)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ logrusCtx(ctx).WithField(eventtest.B.Name, b).Info(eventtest.B.Msg)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+
+ logrusLogf = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ logrusCtx(ctx).Infof(eventtest.A.Msgf, a)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ logrusCtx(ctx).Infof(eventtest.B.Msgf, b)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+)
+
+type logrusKey struct{}
+type logrusTimeFormatter struct {
+ now func() time.Time
+ wrapped logrus.Formatter
+}
+
+func (f *logrusTimeFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ entry.Time = f.now()
+ return f.wrapped.Format(entry)
+}
+
+func logrusCtx(ctx context.Context) *logrus.Logger {
+ return ctx.Value(logrusKey{}).(*logrus.Logger)
+}
+
+func logrusPrint(w io.Writer) context.Context {
+ logger := &logrus.Logger{
+ Out: w,
+ Level: logrus.InfoLevel,
+ Formatter: &logrusTimeFormatter{
+ now: eventtest.ExporterOptions().Now,
+ wrapped: &logrus.TextFormatter{
+ FullTimestamp: true,
+ TimestampFormat: eventtest.TimeFormat,
+ DisableSorting: true,
+ DisableColors: true,
+ },
+ },
+ }
+ return context.WithValue(context.Background(), logrusKey{}, logger)
+}
+
+func BenchmarkLogrusLogDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, logrusPrint(io.Discard), logrusLog)
+}
+
+func BenchmarkLogrusLogfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, logrusPrint(io.Discard), logrusLogf)
+}
+
+func TestLogrusf(t *testing.T) {
+ eventtest.TestBenchmark(t, logrusPrint, logrusLogf, eventtest.LogfOutput)
+}
diff --git a/event/adapter/logrus/logrus.go b/event/adapter/logrus/logrus.go
new file mode 100644
index 0000000..ed2fa3b
--- /dev/null
+++ b/event/adapter/logrus/logrus.go
@@ -0,0 +1,84 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+// Package logrus provides a logrus Formatter for events.
+// To use for the global logger:
+//
+// logrus.SetFormatter(elogrus.NewFormatter(exporter))
+// logrus.SetOutput(io.Discard)
+//
+// and for a Logger instance:
+//
+// logger.SetFormatter(elogrus.NewFormatter(exporter))
+// logger.SetOutput(io.Discard)
+//
+// If you call elogging.SetExporter, then you can pass nil
+// for the exporter above and it will use the global one.
+package logrus
+
+import (
+ "context"
+
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/severity"
+)
+
+type formatter struct{}
+
+func NewFormatter() logrus.Formatter {
+ return &formatter{}
+}
+
+var _ logrus.Formatter = (*formatter)(nil)
+
+// Format writes an entry to an event.Exporter. It always returns nil (see below).
+// If e.Context is non-nil, Format gets the exporter from the context. Otherwise
+// it uses the default exporter.
+//
+// Logrus first calls the Formatter to get a []byte, then writes that to the
+// output. That doesn't work for events, so we subvert it by having the
+// Formatter export the event (and thereby write it). That is why the logrus
+// Output io.Writer should be set to io.Discard.
+func (f *formatter) Format(e *logrus.Entry) ([]byte, error) {
+ ctx := e.Context
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ ev := event.New(ctx, event.LogKind)
+ if ev == nil {
+ return nil, nil
+ }
+ ev.At = e.Time
+ ev.Labels = append(ev.Labels, convertLevel(e.Level).Label())
+ for k, v := range e.Data {
+ ev.Labels = append(ev.Labels, event.Value(k, v))
+ }
+ ev.Labels = append(ev.Labels, event.String("msg", e.Message))
+ ev.Deliver()
+ return nil, nil
+}
+
+func convertLevel(level logrus.Level) severity.Level {
+ switch level {
+ case logrus.PanicLevel:
+ return severity.Fatal + 1
+ case logrus.FatalLevel:
+ return severity.Fatal
+ case logrus.ErrorLevel:
+ return severity.Error
+ case logrus.WarnLevel:
+ return severity.Warning
+ case logrus.InfoLevel:
+ return severity.Info
+ case logrus.DebugLevel:
+ return severity.Debug
+ case logrus.TraceLevel:
+ return severity.Trace
+ default:
+ return severity.Trace
+ }
+}
diff --git a/event/adapter/logrus/logrus_test.go b/event/adapter/logrus/logrus_test.go
new file mode 100644
index 0000000..0404940
--- /dev/null
+++ b/event/adapter/logrus/logrus_test.go
@@ -0,0 +1,44 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package logrus_test
+
+import (
+ "io"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/exp/event"
+ elogrus "golang.org/x/exp/event/adapter/logrus"
+ "golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/event/severity"
+)
+
+func Test(t *testing.T) {
+ ctx, th := eventtest.NewCapture()
+ log := logrus.New()
+ log.SetFormatter(elogrus.NewFormatter())
+ log.SetOutput(io.Discard)
+ // adding WithContext panics, because event.FromContext assumes
+ log.WithContext(ctx).WithField("traceID", 17).WithField("resource", "R").Info("mess")
+
+ want := []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ severity.Info.Label(),
+ event.Value("traceID", 17),
+ event.Value("resource", "R"),
+ event.String("msg", "mess"),
+ },
+ }}
+ // logrus fields are stored in a map, so we have to sort to overcome map
+ // iteration indeterminacy.
+ if diff := cmp.Diff(want, th.Got, eventtest.CmpOptions()...); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+}
diff --git a/event/adapter/stdlib/bench_test.go b/event/adapter/stdlib/bench_test.go
new file mode 100644
index 0000000..da5ddd1
--- /dev/null
+++ b/event/adapter/stdlib/bench_test.go
@@ -0,0 +1,126 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stdlib_test
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "testing"
+
+ "golang.org/x/exp/event/eventtest"
+)
+
+var (
+ baseline = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context { return ctx },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context { return ctx },
+ BEnd: func(ctx context.Context) {},
+ }
+
+ stdlibLog = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ logCtx(ctx).Printf(eventtest.A.Msgf, a)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ logCtx(ctx).Printf(eventtest.B.Msgf, b)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+
+ stdlibPrintf = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ ctxPrintf(ctx, eventtest.A.Msgf, a)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ ctxPrintf(ctx, eventtest.B.Msgf, b)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+)
+
+func BenchmarkBaseline(b *testing.B) {
+ eventtest.RunBenchmark(b, context.Background(), eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context { return ctx },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context { return ctx },
+ BEnd: func(ctx context.Context) {},
+ })
+}
+
+type stdlibLogKey struct{}
+
+func logCtx(ctx context.Context) *log.Logger {
+ return ctx.Value(stdlibLogKey{}).(*log.Logger)
+}
+
+func stdlibLogger(w io.Writer) context.Context {
+ logger := log.New(w, "", log.LstdFlags)
+ return context.WithValue(context.Background(), stdlibLogKey{}, logger)
+}
+
+func stdlibLoggerNoTime(w io.Writer) context.Context {
+ // there is no way to fixup the time, so we have to suppress it
+ logger := log.New(w, "", 0)
+ return context.WithValue(context.Background(), stdlibLogKey{}, logger)
+}
+
+type writerKey struct{}
+
+func ctxPrintf(ctx context.Context, msg string, args ...interface{}) {
+ ctx.Value(writerKey{}).(func(string, ...interface{}))(msg, args...)
+}
+
+func stdlibWriter(w io.Writer) context.Context {
+ now := eventtest.ExporterOptions().Now
+ return context.WithValue(context.Background(), writerKey{},
+ func(msg string, args ...interface{}) {
+ fmt.Fprintf(w, "time=%q level=info msg=%q\n",
+ now().Format(eventtest.TimeFormat),
+ fmt.Sprintf(msg, args...))
+ },
+ )
+}
+
+func BenchmarkStdlibLogfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, stdlibLogger(io.Discard), stdlibLog)
+}
+
+func BenchmarkStdlibPrintfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, stdlibWriter(io.Discard), stdlibPrintf)
+}
+
+func TestLogStdlib(t *testing.T) {
+ eventtest.TestBenchmark(t, stdlibLoggerNoTime, stdlibLog, `
+a where A=0
+b where B="A value"
+a where A=1
+b where B="Some other value"
+a where A=22
+b where B="Some other value"
+a where A=333
+b where B=" "
+a where A=4444
+b where B="prime count of values"
+a where A=55555
+b where B="V"
+a where A=666666
+b where B="A value"
+a where A=7777777
+b where B="A value"
+`)
+}
+
+func TestLogPrintf(t *testing.T) {
+ eventtest.TestBenchmark(t, stdlibWriter, stdlibPrintf, eventtest.LogfOutput)
+}
diff --git a/event/adapter/zap/bench_test.go b/event/adapter/zap/bench_test.go
new file mode 100644
index 0000000..913747f
--- /dev/null
+++ b/event/adapter/zap/bench_test.go
@@ -0,0 +1,115 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zap_test
+
+import (
+ "context"
+ "io"
+ "testing"
+ "time"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "golang.org/x/exp/event/eventtest"
+)
+
+var (
+ zapLog = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ zapCtx(ctx).Info(eventtest.A.Msg, zap.Int(eventtest.A.Name, a))
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ zapCtx(ctx).Info(eventtest.B.Msg, zap.String(eventtest.B.Name, b))
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+ zapLogf = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ zapCtx(ctx).Sugar().Infof(eventtest.A.Msgf, a)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ zapCtx(ctx).Sugar().Infof(eventtest.B.Msgf, b)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+)
+
+type zapKey struct{}
+
+func zapCtx(ctx context.Context) *zap.Logger {
+ return ctx.Value(zapKey{}).(*zap.Logger)
+}
+
+func zapPrint(w io.Writer) context.Context {
+ now := eventtest.ExporterOptions().Now
+ ec := zap.NewProductionEncoderConfig()
+ ec.EncodeDuration = zapcore.NanosDurationEncoder
+ timeEncoder := zapcore.TimeEncoderOfLayout(eventtest.TimeFormat)
+ ec.EncodeTime = func(_ time.Time, a zapcore.PrimitiveArrayEncoder) {
+ timeEncoder(now(), a)
+ }
+ enc := zapcore.NewConsoleEncoder(ec)
+ logger := zap.New(zapcore.NewCore(
+ enc,
+ zapcore.AddSync(w),
+ zap.InfoLevel,
+ ))
+ return context.WithValue(context.Background(), zapKey{}, logger)
+}
+
+func BenchmarkZapLogDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, zapPrint(io.Discard), zapLog)
+}
+
+func BenchmarkZapLogfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, zapPrint(io.Discard), zapLogf)
+}
+
+func TestZapLogfDiscard(t *testing.T) {
+ eventtest.TestBenchmark(t, zapPrint, zapLogf, `
+2020/03/05 14:27:48 info a where A=0
+2020/03/05 14:27:49 info b where B="A value"
+2020/03/05 14:27:50 info a where A=1
+2020/03/05 14:27:51 info b where B="Some other value"
+2020/03/05 14:27:52 info a where A=22
+2020/03/05 14:27:53 info b where B="Some other value"
+2020/03/05 14:27:54 info a where A=333
+2020/03/05 14:27:55 info b where B=" "
+2020/03/05 14:27:56 info a where A=4444
+2020/03/05 14:27:57 info b where B="prime count of values"
+2020/03/05 14:27:58 info a where A=55555
+2020/03/05 14:27:59 info b where B="V"
+2020/03/05 14:28:00 info a where A=666666
+2020/03/05 14:28:01 info b where B="A value"
+2020/03/05 14:28:02 info a where A=7777777
+2020/03/05 14:28:03 info b where B="A value"
+`)
+}
+func TestLogZap(t *testing.T) {
+ eventtest.TestBenchmark(t, zapPrint, zapLog, `
+2020/03/05 14:27:48 info a {"A": 0}
+2020/03/05 14:27:49 info b {"B": "A value"}
+2020/03/05 14:27:50 info a {"A": 1}
+2020/03/05 14:27:51 info b {"B": "Some other value"}
+2020/03/05 14:27:52 info a {"A": 22}
+2020/03/05 14:27:53 info b {"B": "Some other value"}
+2020/03/05 14:27:54 info a {"A": 333}
+2020/03/05 14:27:55 info b {"B": " "}
+2020/03/05 14:27:56 info a {"A": 4444}
+2020/03/05 14:27:57 info b {"B": "prime count of values"}
+2020/03/05 14:27:58 info a {"A": 55555}
+2020/03/05 14:27:59 info b {"B": "V"}
+2020/03/05 14:28:00 info a {"A": 666666}
+2020/03/05 14:28:01 info b {"B": "A value"}
+2020/03/05 14:28:02 info a {"A": 7777777}
+2020/03/05 14:28:03 info b {"B": "A value"}
+`)
+}
diff --git a/event/adapter/zap/zap.go b/event/adapter/zap/zap.go
new file mode 100644
index 0000000..6fe8a27
--- /dev/null
+++ b/event/adapter/zap/zap.go
@@ -0,0 +1,176 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+// zap provides an implementation of zapcore.Core for events.
+// To use globally:
+//
+// zap.ReplaceGlobals(zap.New(NewCore(exporter)))
+//
+// If you call elogging.SetExporter, then you can pass nil
+// for the exporter above and it will use the global one.
+package zap
+
+import (
+ "context"
+ "fmt"
+ "math"
+ "reflect"
+ "time"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/severity"
+)
+
+type core struct {
+ ev *event.Event // cloned but never delivered
+ labels []event.Label
+}
+
+var _ zapcore.Core = (*core)(nil)
+
+func NewCore(ctx context.Context) zapcore.Core {
+ return &core{ev: event.New(ctx, event.LogKind)}
+}
+
+func (c *core) Enabled(level zapcore.Level) bool {
+ return true
+}
+
+func (c *core) With(fields []zapcore.Field) zapcore.Core {
+ c2 := *c
+ if len(fields) > 0 {
+ c2.labels = make([]event.Label, len(c.labels), len(c.labels)+len(fields))
+ copy(c2.labels, c.labels)
+ for _, f := range fields {
+ c2.labels = append(c2.labels, newLabel(f))
+ }
+ }
+ return &c2
+}
+
+func (c *core) Write(e zapcore.Entry, fs []zapcore.Field) error {
+ if c.ev == nil {
+ return nil
+ }
+ ev := c.ev.Clone()
+ if ev == nil {
+ return nil
+ }
+ ev.At = e.Time
+ // TODO: Add labels more efficiently: compare cap(ev.Labels) to the total number to add,
+ // and allocate a []Label once.
+ ev.Labels = append(ev.Labels, c.labels...)
+ ev.Labels = append(ev.Labels, convertLevel(e.Level).Label())
+ ev.Labels = append(ev.Labels, event.String("name", e.LoggerName))
+ if e.Stack != "" {
+ ev.Labels = append(ev.Labels, event.String("stack", e.Stack))
+ }
+ if e.Caller.Defined {
+ ev.Labels = append(ev.Labels, event.String("caller", e.Caller.String()))
+ }
+ for _, f := range fs {
+ ev.Labels = append(ev.Labels, newLabel(f))
+ }
+ ev.Labels = append(ev.Labels, event.String("msg", e.Message))
+ ev.Deliver()
+ return nil
+}
+
+func (c *core) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
+ return ce.AddCore(e, c)
+}
+
+func (c *core) Sync() error { return nil }
+
+func newLabel(f zap.Field) event.Label {
+ switch f.Type {
+ case zapcore.ArrayMarshalerType, zapcore.ObjectMarshalerType, zapcore.BinaryType, zapcore.ByteStringType,
+ zapcore.Complex128Type, zapcore.Complex64Type, zapcore.TimeFullType, zapcore.ReflectType,
+ zapcore.ErrorType:
+ return event.Value(f.Key, f.Interface)
+ case zapcore.DurationType:
+ return event.Duration(f.Key, time.Duration(f.Integer))
+ case zapcore.Float64Type:
+ return event.Float64(f.Key, math.Float64frombits(uint64(f.Integer)))
+ case zapcore.Float32Type:
+ return event.Float64(f.Key, float64(math.Float32frombits(uint32(f.Integer))))
+ case zapcore.BoolType:
+ b := false
+ if f.Integer != 0 {
+ b = true
+ }
+ return event.Bool(f.Key, b)
+ case zapcore.Int64Type:
+ return event.Int64(f.Key, f.Integer)
+ case zapcore.Int32Type:
+ return event.Int64(f.Key, f.Integer)
+
+ //, zapcore.Int16Type, zapcore.Int8Type,
+ // zapcore.Uint64Type, zapcore.Uint32Type, zapcore.Uint16Type, zapcore.Uint8Type, zapcore.UintptrType:
+ // return (f.Key,uint64(f.Integer))
+ case zapcore.StringType:
+ return event.String(f.Key, f.String)
+ case zapcore.TimeType:
+ t := time.Unix(0, f.Integer)
+ if f.Interface != nil {
+ t = t.In(f.Interface.(*time.Location))
+ }
+ return event.Value(f.Key, t)
+ case zapcore.StringerType:
+ return event.String(f.Key, stringerToString(f.Interface))
+ case zapcore.NamespaceType:
+ // TODO: ???
+ return event.Label{}
+ case zapcore.SkipType:
+ // TODO: avoid creating a label at all in this case.
+ return event.Label{}
+ default:
+ panic(fmt.Sprintf("unknown field type: %v", f))
+ }
+}
+
+// Adapter from encodeStringer in go.uber.org/zap/zapcore/field.go.
+func stringerToString(stringer interface{}) (s string) {
+ // Try to capture panics (from nil references or otherwise) when calling
+ // the String() method, similar to https://golang.org/src/fmt/print.go#L540
+ defer func() {
+ if err := recover(); err != nil {
+ // If it's a nil pointer, just say "<nil>". The likeliest causes are a
+ // Stringer that fails to guard against nil or a nil pointer for a
+ // value receiver, and in either case, "<nil>" is a nice result.
+ if v := reflect.ValueOf(stringer); v.Kind() == reflect.Ptr && v.IsNil() {
+ s = "<nil>"
+ return
+ }
+ s = fmt.Sprintf("PANIC=%v", err)
+ }
+ }()
+
+ return stringer.(fmt.Stringer).String()
+}
+
+func convertLevel(level zapcore.Level) severity.Level {
+ switch level {
+ case zapcore.DebugLevel:
+ return severity.Debug
+ case zapcore.InfoLevel:
+ return severity.Info
+ case zapcore.WarnLevel:
+ return severity.Warning
+ case zapcore.ErrorLevel:
+ return severity.Error
+ case zapcore.DPanicLevel:
+ return severity.Fatal - 1
+ case zapcore.PanicLevel:
+ return severity.Fatal + 1
+ case zapcore.FatalLevel:
+ return severity.Fatal
+ default:
+ return severity.Trace
+ }
+}
diff --git a/event/adapter/zap/zap_test.go b/event/adapter/zap/zap_test.go
new file mode 100644
index 0000000..ed2e03c
--- /dev/null
+++ b/event/adapter/zap/zap_test.go
@@ -0,0 +1,40 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package zap_test
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "go.uber.org/zap"
+ "golang.org/x/exp/event"
+ ezap "golang.org/x/exp/event/adapter/zap"
+ "golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/event/severity"
+)
+
+func Test(t *testing.T) {
+ ctx, h := eventtest.NewCapture()
+ log := zap.New(ezap.NewCore(ctx), zap.Fields(zap.Int("traceID", 17), zap.String("resource", "R")))
+ log = log.Named("n/m")
+ log.Info("mess", zap.Float64("pi", 3.14))
+ want := []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ event.Int64("traceID", 17),
+ event.String("resource", "R"),
+ severity.Info.Label(),
+ event.String("name", "n/m"),
+ event.Float64("pi", 3.14),
+ event.String("msg", "mess"),
+ },
+ }}
+ if diff := cmp.Diff(want, h.Got, eventtest.CmpOptions()...); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+}
diff --git a/event/adapter/zerolog/bench_test.go b/event/adapter/zerolog/bench_test.go
new file mode 100644
index 0000000..480754d
--- /dev/null
+++ b/event/adapter/zerolog/bench_test.go
@@ -0,0 +1,78 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zerolog_test
+
+import (
+ "context"
+ "io"
+ "testing"
+
+ "github.com/rs/zerolog"
+ "golang.org/x/exp/event/eventtest"
+)
+
+var (
+ zerologMsg = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ zerolog.Ctx(ctx).Info().Int(eventtest.A.Name, a).Msg(eventtest.A.Msg)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ zerolog.Ctx(ctx).Info().Str(eventtest.B.Name, b).Msg(eventtest.B.Msg)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+
+ zerologMsgf = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ zerolog.Ctx(ctx).Info().Msgf(eventtest.A.Msgf, a)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ zerolog.Ctx(ctx).Info().Msgf(eventtest.B.Msgf, b)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+)
+
+func zerologPrint(w io.Writer) context.Context {
+ zerolog.TimeFieldFormat = eventtest.TimeFormat
+ zerolog.TimestampFunc = eventtest.ExporterOptions().Now
+ logger := zerolog.New(zerolog.SyncWriter(w)).With().Timestamp().Logger()
+ return logger.WithContext(context.Background())
+}
+
+func BenchmarkZerologLogDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, zerologPrint(io.Discard), zerologMsg)
+}
+
+func BenchmarkZerologLogfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, zerologPrint(io.Discard), zerologMsgf)
+}
+
+func TestLogZerologf(t *testing.T) {
+ eventtest.TestBenchmark(t, zerologPrint, zerologMsgf, `
+{"level":"info","time":"2020/03/05 14:27:48","message":"a where A=0"}
+{"level":"info","time":"2020/03/05 14:27:49","message":"b where B=\"A value\""}
+{"level":"info","time":"2020/03/05 14:27:50","message":"a where A=1"}
+{"level":"info","time":"2020/03/05 14:27:51","message":"b where B=\"Some other value\""}
+{"level":"info","time":"2020/03/05 14:27:52","message":"a where A=22"}
+{"level":"info","time":"2020/03/05 14:27:53","message":"b where B=\"Some other value\""}
+{"level":"info","time":"2020/03/05 14:27:54","message":"a where A=333"}
+{"level":"info","time":"2020/03/05 14:27:55","message":"b where B=\" \""}
+{"level":"info","time":"2020/03/05 14:27:56","message":"a where A=4444"}
+{"level":"info","time":"2020/03/05 14:27:57","message":"b where B=\"prime count of values\""}
+{"level":"info","time":"2020/03/05 14:27:58","message":"a where A=55555"}
+{"level":"info","time":"2020/03/05 14:27:59","message":"b where B=\"V\""}
+{"level":"info","time":"2020/03/05 14:28:00","message":"a where A=666666"}
+{"level":"info","time":"2020/03/05 14:28:01","message":"b where B=\"A value\""}
+{"level":"info","time":"2020/03/05 14:28:02","message":"a where A=7777777"}
+{"level":"info","time":"2020/03/05 14:28:03","message":"b where B=\"A value\""}
+`)
+}
diff --git a/event/alloc_test.go b/event/alloc_test.go
new file mode 100644
index 0000000..1cf7e75
--- /dev/null
+++ b/event/alloc_test.go
@@ -0,0 +1,35 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !race
+
+package event_test
+
+import (
+ "context"
+ "io"
+ "testing"
+
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/logfmt"
+ "golang.org/x/exp/event/eventtest"
+)
+
+func TestAllocs(t *testing.T) {
+ anInt := event.Int64("int", 4)
+ aString := event.String("string", "value")
+
+ e := event.NewExporter(logfmt.NewHandler(io.Discard), &event.ExporterOptions{EnableNamespaces: true})
+ ctx := event.WithExporter(context.Background(), e)
+ allocs := int(testing.AllocsPerRun(5, func() {
+ event.Log(ctx, "message", aString, anInt)
+ }))
+ if allocs != 0 {
+ t.Errorf("Got %d allocs, expect 0", allocs)
+ }
+}
+
+func TestBenchAllocs(t *testing.T) {
+ eventtest.TestAllocs(t, eventPrint, eventLog, 0)
+}
diff --git a/event/bench_test.go b/event/bench_test.go
new file mode 100644
index 0000000..b3bd0ea
--- /dev/null
+++ b/event/bench_test.go
@@ -0,0 +1,134 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event_test
+
+import (
+ "context"
+ "io"
+ "testing"
+
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/logfmt"
+ "golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/event/severity"
+)
+
+var (
+ eventLog = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ severity.Info.Log(ctx, eventtest.A.Msg, event.Int64(eventtest.A.Name, int64(a)))
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ severity.Info.Log(ctx, eventtest.B.Msg, event.String(eventtest.B.Name, b))
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+
+ eventLogf = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ severity.Info.Logf(ctx, eventtest.A.Msgf, a)
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ severity.Info.Logf(ctx, eventtest.B.Msgf, b)
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+
+ eventTrace = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ ctx = event.Start(ctx, eventtest.A.Msg, event.Int64(eventtest.A.Name, int64(a)))
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {
+ event.End(ctx)
+ },
+ BStart: func(ctx context.Context, b string) context.Context {
+ ctx = event.Start(ctx, eventtest.B.Msg, event.String(eventtest.B.Name, b))
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {
+ event.End(ctx)
+ },
+ }
+
+ eventMetric = eventtest.Hooks{
+ AStart: func(ctx context.Context, a int) context.Context {
+ gauge.Record(ctx, 1, event.Int64("aValue", int64(a)))
+ gauge.Record(ctx, 1, event.Int64("aCount", 1))
+ return ctx
+ },
+ AEnd: func(ctx context.Context) {},
+ BStart: func(ctx context.Context, b string) context.Context {
+ gauge.Record(ctx, 1, event.Int64("BLen", int64(len(b))))
+ gauge.Record(ctx, 1, event.Int64("B", 1))
+ return ctx
+ },
+ BEnd: func(ctx context.Context) {},
+ }
+)
+
+func eventNoExporter() context.Context {
+ return event.WithExporter(context.Background(), nil)
+}
+
+func eventNoop() context.Context {
+ return event.WithExporter(context.Background(), event.NewExporter(nopHandler{}, eventtest.ExporterOptions()))
+}
+
+func eventPrint(w io.Writer) context.Context {
+ return event.WithExporter(context.Background(), event.NewExporter(logfmt.NewHandler(w), eventtest.ExporterOptions()))
+}
+
+func eventPrintSource(w io.Writer) context.Context {
+ opts := eventtest.ExporterOptions()
+ opts.EnableNamespaces = true
+ return event.WithExporter(context.Background(), event.NewExporter(logfmt.NewHandler(w), opts))
+}
+
+type nopHandler struct{}
+
+func (nopHandler) Event(ctx context.Context, _ *event.Event) context.Context { return ctx }
+
+func BenchmarkEventLogNoExporter(b *testing.B) {
+ eventtest.RunBenchmark(b, eventNoExporter(), eventLog)
+}
+
+func BenchmarkEventLogNoop(b *testing.B) {
+ eventtest.RunBenchmark(b, eventNoop(), eventLog)
+}
+
+func BenchmarkEventLogDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, eventPrint(io.Discard), eventLog)
+}
+
+func BenchmarkEventLogSourceDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, eventPrintSource(io.Discard), eventLog)
+}
+
+func BenchmarkEventLogfDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, eventPrint(io.Discard), eventLogf)
+}
+
+func BenchmarkEventTraceNoop(b *testing.B) {
+ eventtest.RunBenchmark(b, eventNoop(), eventTrace)
+}
+
+func BenchmarkEventTraceDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, eventPrint(io.Discard), eventTrace)
+}
+
+func BenchmarkEventMetricNoop(b *testing.B) {
+ eventtest.RunBenchmark(b, eventNoop(), eventMetric)
+}
+
+func BenchmarkEventMetricDiscard(b *testing.B) {
+ eventtest.RunBenchmark(b, eventPrint(io.Discard), eventMetric)
+}
diff --git a/event/common.go b/event/common.go
new file mode 100644
index 0000000..f12b84a
--- /dev/null
+++ b/event/common.go
@@ -0,0 +1,159 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event
+
+import (
+ "context"
+ "fmt"
+ "sync"
+)
+
+const (
+ MetricKey = interfaceKey("metric")
+ MetricVal = "metricValue"
+ DurationMetric = interfaceKey("durationMetric")
+)
+
+type Kind int
+
+const (
+ unknownKind = Kind(iota)
+
+ LogKind
+ MetricKind
+ StartKind
+ EndKind
+
+ dynamicKindStart
+)
+
+type (
+ valueKey string
+ interfaceKey string
+)
+
+var (
+ dynamicKindMu sync.Mutex
+ nextDynamicKind = dynamicKindStart
+ dynamicKindNames map[Kind]string
+)
+
+func NewKind(name string) Kind {
+ dynamicKindMu.Lock()
+ defer dynamicKindMu.Unlock()
+ for _, n := range dynamicKindNames {
+ if n == name {
+ panic(fmt.Errorf("kind %s is already registered", name))
+ }
+ }
+ k := nextDynamicKind
+ nextDynamicKind++
+ dynamicKindNames[k] = name
+ return k
+}
+
+func (k Kind) String() string {
+ switch k {
+ case unknownKind:
+ return "unknown"
+ case LogKind:
+ return "log"
+ case MetricKind:
+ return "metric"
+ case StartKind:
+ return "start"
+ case EndKind:
+ return "end"
+ default:
+ dynamicKindMu.Lock()
+ defer dynamicKindMu.Unlock()
+ name, ok := dynamicKindNames[k]
+ if !ok {
+ return fmt.Sprintf("?unknownKind:%d?", k)
+ }
+ return name
+ }
+}
+
+func Log(ctx context.Context, msg string, labels ...Label) {
+ ev := New(ctx, LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Labels = append(ev.Labels, String("msg", msg))
+ ev.Deliver()
+ }
+}
+
+func Logf(ctx context.Context, msg string, args ...interface{}) {
+ ev := New(ctx, LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, String("msg", fmt.Sprintf(msg, args...)))
+ ev.Deliver()
+ }
+}
+
+func Error(ctx context.Context, msg string, err error, labels ...Label) {
+ ev := New(ctx, LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Labels = append(ev.Labels, String("msg", msg), Value("error", err))
+ ev.Deliver()
+ }
+}
+
+func Annotate(ctx context.Context, labels ...Label) {
+ ev := New(ctx, 0)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+func Start(ctx context.Context, name string, labels ...Label) context.Context {
+ ev := New(ctx, StartKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, String("name", name))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Trace()
+ ctx = ev.Deliver()
+ }
+ return ctx
+}
+
+func End(ctx context.Context, labels ...Label) {
+ ev := New(ctx, EndKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, labels...)
+ ev.prepare()
+ // this was an end event, do we need to send a duration?
+ if v, ok := DurationMetric.Find(ev); ok {
+ //TODO: do we want the rest of the values from the end event?
+ v.(*DurationDistribution).Record(ctx, ev.At.Sub(ev.target.startTime))
+ }
+ ev.Deliver()
+ }
+}
+
+func (k interfaceKey) Of(v interface{}) Label {
+ return Value(string(k), v)
+}
+
+func (k interfaceKey) Find(ev *Event) (interface{}, bool) {
+ v, ok := lookupValue(string(k), ev.Labels)
+ if !ok {
+ return nil, false
+ }
+ return v.Interface(), true
+
+}
+
+func lookupValue(name string, labels []Label) (Label, bool) {
+ for i := len(labels) - 1; i >= 0; i-- {
+ if labels[i].Name == name {
+ return labels[i], true
+ }
+ }
+ return Label{}, false
+}
diff --git a/event/disabled.go b/event/disabled.go
new file mode 100644
index 0000000..7aee0bd
--- /dev/null
+++ b/event/disabled.go
@@ -0,0 +1,40 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build disable_events
+
+package event
+
+import (
+ "context"
+ "time"
+)
+
+type Builder struct{ ctx context.Context }
+type Exporter struct {
+ Now func() time.Time
+}
+
+func NewExporter(h interface{}) *Exporter { return &Exporter{} }
+
+func To(ctx context.Context) Builder { return Builder{} }
+func (b Builder) Clone() Builder { return b }
+func (b Builder) Label(label Label) Builder { return b }
+func (b Builder) Labels(labels ...Label) Builder { return b }
+func (b Builder) Log(message string) {}
+func (b Builder) Logf(template string, args ...interface{}) {}
+func (b Builder) Metric() {}
+func (b Builder) Annotate() {}
+func (b Builder) End() {}
+func (b Builder) Event() *Event { return &Event{} }
+func (b Builder) Start(name string) (context.Context, func()) {
+ return b.ctx, func() {}
+}
+
+func newContext(ctx context.Context, exporter *Exporter, parent uint64) context.Context {
+ return ctx
+}
+func FromContext(ctx context.Context) *Target { return nil }
+
+func setDefaultExporter(e *Exporter) {}
diff --git a/event/doc.go b/event/doc.go
new file mode 100644
index 0000000..bb11122
--- /dev/null
+++ b/event/doc.go
@@ -0,0 +1,18 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package event provides low-cost tracing, metrics and structured logging.
+// These are often grouped under the term "observability".
+//
+// This package is highly experimental and in a state of flux; it is public only
+// to allow for collaboration on the design and implementation, and may be
+// changed or removed at any time.
+//
+// It uses a common event system to provide a way for libraries to produce
+// observability information in a way that does not tie the libraries to a
+// specific API or applications to a specific export format.
+//
+// It is designed for minimal overhead when no exporter is used so that it is
+// safe to leave calls in libraries.
+package event
diff --git a/event/event.go b/event/event.go
new file mode 100644
index 0000000..e5746b0
--- /dev/null
+++ b/event/event.go
@@ -0,0 +1,138 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event
+
+import (
+ "context"
+ "sync"
+ "time"
+)
+
+// Event holds the information about an event that occurred.
+// It combines the event metadata with the user supplied labels.
+type Event struct {
+ ID uint64
+ Parent uint64 // id of the parent event for this event
+ Source Source // source of event; if empty, set by exporter to import path
+ At time.Time // time at which the event is delivered to the exporter.
+ Kind Kind
+ Labels []Label
+
+ ctx context.Context
+ target *target
+ labels [preallocateLabels]Label
+}
+
+// Handler is a the type for something that handles events as they occur.
+type Handler interface {
+ // Event is called with each event.
+ Event(context.Context, *Event) context.Context
+}
+
+// preallocateLabels controls the space reserved for labels in a builder.
+// Storing the first few labels directly in builders can avoid an allocation at
+// all for the very common cases of simple events. The length needs to be large
+// enough to cope with the majority of events but no so large as to cause undue
+// stack pressure.
+const preallocateLabels = 6
+
+var eventPool = sync.Pool{New: func() interface{} { return &Event{} }}
+
+// WithExporter returns a context with the exporter attached.
+// The exporter is called synchronously from the event call site, so it should
+// return quickly so as not to hold up user code.
+func WithExporter(ctx context.Context, e *Exporter) context.Context {
+ return newContext(ctx, e, 0, time.Time{})
+}
+
+// SetDefaultExporter sets an exporter that is used if no exporter can be
+// found on the context.
+func SetDefaultExporter(e *Exporter) {
+ setDefaultExporter(e)
+}
+
+// New prepares a new event.
+// This is intended to avoid allocations in the steady state case, to do this
+// it uses a pool of events.
+// Events are returned to the pool when Deliver is called. Failure to call
+// Deliver will exhaust the pool and cause allocations.
+// It returns nil if there is no active exporter for this kind of event.
+func New(ctx context.Context, kind Kind) *Event {
+ var t *target
+ if v, ok := ctx.Value(contextKey).(*target); ok {
+ t = v
+ } else {
+ t = getDefaultTarget()
+ }
+ if t == nil {
+ return nil
+ }
+ //TODO: we can change this to a much faster test
+ switch kind {
+ case LogKind:
+ if !t.exporter.loggingEnabled() {
+ return nil
+ }
+ case MetricKind:
+ if !t.exporter.metricsEnabled() {
+ return nil
+ }
+ case StartKind, EndKind:
+ if !t.exporter.tracingEnabled() {
+ return nil
+ }
+ }
+ ev := eventPool.Get().(*Event)
+ *ev = Event{
+ ctx: ctx,
+ target: t,
+ Kind: kind,
+ Parent: t.parent,
+ }
+ ev.Labels = ev.labels[:0]
+ return ev
+}
+
+// Clone makes a deep copy of the Event.
+// Deliver can be called on both Events independently.
+func (ev *Event) Clone() *Event {
+ ev2 := eventPool.Get().(*Event)
+ *ev2 = *ev
+ ev2.Labels = append(ev2.labels[:0], ev.Labels...)
+ return ev2
+}
+
+func (ev *Event) Trace() {
+ ev.prepare()
+ ev.ctx = newContext(ev.ctx, ev.target.exporter, ev.ID, ev.At)
+}
+
+// Deliver the event to the exporter that was found in New.
+// This also returns the event to the pool, it is an error to do anything
+// with the event after it is delivered.
+func (ev *Event) Deliver() context.Context {
+ // get the event ready to send
+ ev.prepare()
+ ctx := ev.deliver()
+ eventPool.Put(ev)
+ return ctx
+}
+
+func (ev *Event) deliver() context.Context {
+ // hold the lock while we deliver the event
+ e := ev.target.exporter
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.handler.Event(ev.ctx, ev)
+}
+
+func (ev *Event) Find(name string) Label {
+ for _, l := range ev.Labels {
+ if l.Name == name {
+ return l
+ }
+ }
+ return Label{}
+}
diff --git a/event/event_test.go b/event/event_test.go
new file mode 100644
index 0000000..5899988
--- /dev/null
+++ b/event/event_test.go
@@ -0,0 +1,347 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package event_test
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/logfmt"
+ "golang.org/x/exp/event/eventtest"
+)
+
+var (
+ l1 = event.Int64("l1", 1)
+ l2 = event.Int64("l2", 2)
+ l3 = event.Int64("l3", 3)
+ counter = event.NewCounter("hits", nil)
+ gauge = event.NewFloatGauge("temperature", nil)
+ latency = event.NewDuration("latency", nil)
+ err = errors.New("an error")
+)
+
+func TestCommon(t *testing.T) {
+ for _, test := range []struct {
+ method string
+ events func(context.Context)
+ expect []event.Event
+ }{{
+ method: "simple",
+ events: func(ctx context.Context) { event.Log(ctx, "a message") },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{event.String("msg", "a message")},
+ }},
+ }, {
+ method: "log 1",
+ events: func(ctx context.Context) { event.Log(ctx, "a message", l1) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{event.String("msg", "a message"), l1},
+ }},
+ }, {
+ method: "log 2",
+ events: func(ctx context.Context) { event.Log(ctx, "a message", l1, l2) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{event.String("msg", "a message"), l1, l2},
+ }},
+ }, {
+ method: "log 3",
+ events: func(ctx context.Context) { event.Log(ctx, "a message", l1, l2, l3) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{event.String("msg", "a message"), l1, l2, l3},
+ }},
+ }, {
+ method: "logf",
+ events: func(ctx context.Context) { event.Logf(ctx, "logf %s message", "to") },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{event.String("msg", "logf to message")},
+ }},
+ }, {
+ method: "error",
+ events: func(ctx context.Context) { event.Error(ctx, "failed", err, l1) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ event.String("msg", "failed"),
+ event.Value("error", err),
+ l1,
+ },
+ }},
+ }, {
+ method: "span",
+ events: func(ctx context.Context) {
+ ctx = event.Start(ctx, `span`)
+ event.End(ctx)
+ },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.StartKind,
+ Labels: []event.Label{event.String("name", "span")},
+ }, {
+ ID: 2,
+ Parent: 1,
+ Kind: event.EndKind,
+ Labels: []event.Label{},
+ }},
+ }, {
+ method: "span nested",
+ events: func(ctx context.Context) {
+ ctx = event.Start(ctx, "parent")
+ defer event.End(ctx)
+ child := event.Start(ctx, "child")
+ defer event.End(child)
+ event.Log(child, "message")
+ },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.StartKind,
+ Labels: []event.Label{event.String("name", "parent")},
+ }, {
+ ID: 2,
+ Parent: 1,
+ Kind: event.StartKind,
+ Labels: []event.Label{event.String("name", "child")},
+ }, {
+ ID: 3,
+ Parent: 2,
+ Kind: event.LogKind,
+ Labels: []event.Label{event.String("msg", "message")},
+ }, {
+ ID: 4,
+ Parent: 2,
+ Kind: event.EndKind,
+ Labels: []event.Label{},
+ }, {
+ ID: 5,
+ Parent: 1,
+ Kind: event.EndKind,
+ Labels: []event.Label{},
+ }},
+ }, {
+ method: "counter",
+ events: func(ctx context.Context) { counter.Record(ctx, 2, l1) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.MetricKind,
+ Labels: []event.Label{
+ event.Int64("metricValue", 2),
+ event.Value("metric", counter),
+ l1,
+ },
+ }},
+ }, {
+ method: "gauge",
+ events: func(ctx context.Context) { gauge.Record(ctx, 98.6, l1) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.MetricKind,
+ Labels: []event.Label{
+ event.Float64("metricValue", 98.6),
+ event.Value("metric", gauge),
+ l1,
+ },
+ }},
+ }, {
+ method: "duration",
+ events: func(ctx context.Context) { latency.Record(ctx, 3*time.Second, l1, l2) },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.MetricKind,
+ Labels: []event.Label{
+ event.Duration("metricValue", 3*time.Second),
+ event.Value("metric", latency),
+ l1, l2,
+ },
+ }},
+ }, {
+ method: "annotate",
+ events: func(ctx context.Context) { event.Annotate(ctx, l1) },
+ expect: []event.Event{{
+ ID: 1,
+ Labels: []event.Label{l1},
+ }},
+ }, {
+ method: "annotate 2",
+ events: func(ctx context.Context) { event.Annotate(ctx, l1, l2) },
+ expect: []event.Event{{
+ ID: 1,
+ Labels: []event.Label{l1, l2},
+ }},
+ }, {
+ method: "multiple events",
+ events: func(ctx context.Context) {
+ /*TODO: this is supposed to be using a cached target
+ t := event.To(ctx)
+ p := event.Prototype{}.As(event.LogKind)
+ t.With(p).Int("myInt", 6).Message("my event").Send()
+ t.With(p).String("myString", "some string value").Message("string event").Send()
+ */
+ event.Log(ctx, "my event", event.Int64("myInt", 6))
+ event.Log(ctx, "string event", event.String("myString", "some string value"))
+ },
+ expect: []event.Event{{
+ ID: 1,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ event.String("msg", "my event"),
+ event.Int64("myInt", 6),
+ },
+ }, {
+ ID: 2,
+ Kind: event.LogKind,
+ Labels: []event.Label{
+ event.String("msg", "string event"),
+ event.String("myString", "some string value"),
+ },
+ }},
+ }} {
+ t.Run(test.method, func(t *testing.T) {
+ ctx, h := eventtest.NewCapture()
+ test.events(ctx)
+ if diff := cmp.Diff(test.expect, h.Got, eventtest.CmpOptions()...); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+ })
+ }
+}
+
+func ExampleLog() {
+ ctx := event.WithExporter(context.Background(), event.NewExporter(logfmt.NewHandler(os.Stdout), eventtest.ExporterOptions()))
+ event.Log(ctx, "my event", event.Int64("myInt", 6))
+ event.Log(ctx, "error event", event.String("myString", "some string value"))
+ // Output:
+ // time="2020/03/05 14:27:48" myInt=6 msg="my event"
+ // time="2020/03/05 14:27:49" myString="some string value" msg="error event"
+}
+
+func TestLogEventf(t *testing.T) {
+ eventtest.TestBenchmark(t, eventPrint, eventLogf, eventtest.LogfOutput)
+}
+
+func TestLogEvent(t *testing.T) {
+ eventtest.TestBenchmark(t, eventPrint, eventLog, eventtest.LogfmtOutput)
+}
+
+func TestTraceBuilder(t *testing.T) {
+ // Verify that the context returned from the handler is also returned from Start,
+ // and is the context passed to End.
+ ctx := event.WithExporter(context.Background(), event.NewExporter(&testTraceHandler{t: t}, eventtest.ExporterOptions()))
+ ctx = event.Start(ctx, "s")
+ val := ctx.Value("x")
+ if val != 1 {
+ t.Fatal("context not returned from Start")
+ }
+ event.End(ctx)
+}
+
+type testTraceHandler struct {
+ t *testing.T
+}
+
+func (t *testTraceHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ switch ev.Kind {
+ case event.StartKind:
+ return context.WithValue(ctx, "x", 1)
+ case event.EndKind:
+ val := ctx.Value("x")
+ if val != 1 {
+ t.t.Fatal("Start context not passed to End")
+ }
+ return ctx
+ default:
+ return ctx
+ }
+}
+
+func TestTraceDuration(t *testing.T) {
+ // Verify that a trace can can emit a latency metric.
+ dur := event.NewDuration("test", nil)
+ want := time.Second
+
+ check := func(t *testing.T, h *testTraceDurationHandler) {
+ if !h.got.HasValue() {
+ t.Fatal("no metric value")
+ }
+ got := h.got.Duration()
+ if got != want {
+ t.Fatalf("got %s, want %s", got, want)
+ }
+ }
+
+ t.Run("returned builder", func(t *testing.T) {
+ h := &testTraceDurationHandler{}
+ ctx := event.WithExporter(context.Background(), event.NewExporter(h, eventtest.ExporterOptions()))
+ ctx = event.Start(ctx, "s")
+ time.Sleep(want)
+ event.End(ctx, event.DurationMetric.Of(dur))
+ check(t, h)
+ })
+ //TODO: come back and fix this
+ t.Run("separate builder", func(t *testing.T) {
+ h := &testTraceDurationHandler{}
+ ctx := event.WithExporter(context.Background(), event.NewExporter(h, eventtest.ExporterOptions()))
+ ctx = event.Start(ctx, "s")
+ time.Sleep(want)
+ event.End(ctx, event.DurationMetric.Of(dur))
+ check(t, h)
+ })
+}
+
+type testTraceDurationHandler struct {
+ got event.Label
+}
+
+func (t *testTraceDurationHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ for _, l := range ev.Labels {
+ if l.Name == string(event.MetricVal) {
+ t.got = l
+ }
+ }
+ return ctx
+}
+
+func BenchmarkBuildContext(b *testing.B) {
+ // How long does it take to deliver an event from a nested context?
+ c := event.NewCounter("c", nil)
+ for _, depth := range []int{1, 5, 7, 10} {
+ b.Run(fmt.Sprintf("depth %d", depth), func(b *testing.B) {
+ ctx := event.WithExporter(context.Background(), event.NewExporter(nopHandler{}, eventtest.ExporterOptions()))
+ for i := 0; i < depth; i++ {
+ ctx = context.WithValue(ctx, i, i)
+ }
+ b.Run("direct", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ c.Record(ctx, 1)
+ }
+ })
+ /*TODO: work out how we do cached labels
+ b.Run("cloned", func(b *testing.B) {
+ bu := event.To(ctx)
+ for i := 0; i < b.N; i++ {
+ c.RecordTB(bu, 1).Name("foo").Send()
+ }
+ })
+ */
+ })
+ }
+}
diff --git a/event/eventtest/bench.go b/event/eventtest/bench.go
new file mode 100644
index 0000000..0fb7189
--- /dev/null
+++ b/event/eventtest/bench.go
@@ -0,0 +1,151 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package eventtest
+
+import (
+ "context"
+ "io"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/exp/event/adapter/logfmt"
+)
+
+type Info struct {
+ Name string
+ Msg string
+ Msgf string
+}
+
+type Hooks struct {
+ AStart func(ctx context.Context, a int) context.Context
+ AEnd func(ctx context.Context)
+ BStart func(ctx context.Context, b string) context.Context
+ BEnd func(ctx context.Context)
+}
+
+var (
+ initialList = []int{0, 1, 22, 333, 4444, 55555, 666666, 7777777}
+ stringList = []string{
+ "A value",
+ "Some other value",
+ "A nice longer value but not too long",
+ "V",
+ " ",
+ "Δ±",
+ "prime count of values",
+ }
+
+ A = Info{
+ Name: "A",
+ Msg: "a",
+ Msgf: "a where A=%d",
+ }
+
+ B = Info{
+ Name: "B",
+ Msg: "b",
+ Msgf: "b where B=%q",
+ }
+)
+
+const (
+ TimeFormat = logfmt.TimeFormat
+
+ LogfmtOutput = `
+time="2020/03/05 14:27:48" level=info A=0 msg=a
+time="2020/03/05 14:27:49" level=info B="A value" msg=b
+time="2020/03/05 14:27:50" level=info A=1 msg=a
+time="2020/03/05 14:27:51" level=info B="Some other value" msg=b
+time="2020/03/05 14:27:52" level=info A=22 msg=a
+time="2020/03/05 14:27:53" level=info B="Some other value" msg=b
+time="2020/03/05 14:27:54" level=info A=333 msg=a
+time="2020/03/05 14:27:55" level=info B=" " msg=b
+time="2020/03/05 14:27:56" level=info A=4444 msg=a
+time="2020/03/05 14:27:57" level=info B="prime count of values" msg=b
+time="2020/03/05 14:27:58" level=info A=55555 msg=a
+time="2020/03/05 14:27:59" level=info B=V msg=b
+time="2020/03/05 14:28:00" level=info A=666666 msg=a
+time="2020/03/05 14:28:01" level=info B="A value" msg=b
+time="2020/03/05 14:28:02" level=info A=7777777 msg=a
+time="2020/03/05 14:28:03" level=info B="A value" msg=b
+`
+
+ LogfOutput = `
+time="2020/03/05 14:27:48" level=info msg="a where A=0"
+time="2020/03/05 14:27:49" level=info msg="b where B=\"A value\""
+time="2020/03/05 14:27:50" level=info msg="a where A=1"
+time="2020/03/05 14:27:51" level=info msg="b where B=\"Some other value\""
+time="2020/03/05 14:27:52" level=info msg="a where A=22"
+time="2020/03/05 14:27:53" level=info msg="b where B=\"Some other value\""
+time="2020/03/05 14:27:54" level=info msg="a where A=333"
+time="2020/03/05 14:27:55" level=info msg="b where B=\" \""
+time="2020/03/05 14:27:56" level=info msg="a where A=4444"
+time="2020/03/05 14:27:57" level=info msg="b where B=\"prime count of values\""
+time="2020/03/05 14:27:58" level=info msg="a where A=55555"
+time="2020/03/05 14:27:59" level=info msg="b where B=\"V\""
+time="2020/03/05 14:28:00" level=info msg="a where A=666666"
+time="2020/03/05 14:28:01" level=info msg="b where B=\"A value\""
+time="2020/03/05 14:28:02" level=info msg="a where A=7777777"
+time="2020/03/05 14:28:03" level=info msg="b where B=\"A value\""
+`
+)
+
+type namedBenchmark struct {
+ name string
+ test func(ctx context.Context) func(*testing.B)
+}
+
+func benchA(ctx context.Context, hooks Hooks, a int) int {
+ ctx = hooks.AStart(ctx, a)
+ defer hooks.AEnd(ctx)
+ return benchB(ctx, hooks, a, stringList[a%len(stringList)])
+}
+
+func benchB(ctx context.Context, hooks Hooks, a int, b string) int {
+ ctx = hooks.BStart(ctx, b)
+ defer hooks.BEnd(ctx)
+ return a + len(b)
+}
+
+func runOnce(ctx context.Context, hooks Hooks) {
+ var acc int
+ for _, value := range initialList {
+ acc += benchA(ctx, hooks, value)
+ }
+}
+
+func RunBenchmark(b *testing.B, ctx context.Context, hooks Hooks) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ runOnce(ctx, hooks)
+ }
+}
+
+func TestBenchmark(t *testing.T, f func(io.Writer) context.Context, hooks Hooks, expect string) {
+ buf := strings.Builder{}
+ ctx := f(&buf)
+ runOnce(ctx, hooks)
+ got := strings.TrimSpace(buf.String())
+ expect = strings.TrimSpace(expect)
+ if diff := cmp.Diff(got, expect); diff != "" {
+ t.Error(diff)
+ }
+}
+
+func TestAllocs(t *testing.T, f func(io.Writer) context.Context, hooks Hooks, expect int) {
+ t.Helper()
+ var acc int
+ ctx := f(io.Discard)
+ got := int(testing.AllocsPerRun(5, func() {
+ for _, value := range initialList {
+ acc += benchA(ctx, hooks, value)
+ }
+ }))
+ if got != expect {
+ t.Errorf("Got %d allocs, expect %d", got, expect)
+ }
+}
diff --git a/event/eventtest/capture.go b/event/eventtest/capture.go
new file mode 100644
index 0000000..9510b3e
--- /dev/null
+++ b/event/eventtest/capture.go
@@ -0,0 +1,31 @@
+package eventtest
+
+import (
+ "context"
+
+ "golang.org/x/exp/event"
+)
+
+type CaptureHandler struct {
+ Got []event.Event
+}
+
+func (h *CaptureHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ h.Got = append(h.Got, *ev)
+ got := &h.Got[len(h.Got)-1]
+ got.Labels = make([]event.Label, len(ev.Labels))
+ copy(got.Labels, ev.Labels)
+ return ctx
+}
+
+func (h *CaptureHandler) Reset() {
+ if len(h.Got) > 0 {
+ h.Got = h.Got[:0]
+ }
+}
+
+func NewCapture() (context.Context, *CaptureHandler) {
+ h := &CaptureHandler{}
+ ctx := event.WithExporter(context.Background(), event.NewExporter(h, ExporterOptions()))
+ return ctx, h
+}
diff --git a/event/eventtest/eventtest.go b/event/eventtest/eventtest.go
new file mode 100644
index 0000000..8081159
--- /dev/null
+++ b/event/eventtest/eventtest.go
@@ -0,0 +1,65 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package eventtest supports logging events to a test.
+// You can use NewContext to create a context that knows how to deliver
+// telemetry events back to the test.
+// You must use this context or a derived one anywhere you want telemetry to be
+// correctly routed back to the test it was constructed with.
+package eventtest
+
+import (
+ "context"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/logfmt"
+)
+
+// NewContext returns a context you should use for the active test.
+func NewContext(ctx context.Context, tb testing.TB) context.Context {
+ h := &testHandler{tb: tb}
+ return event.WithExporter(ctx, event.NewExporter(h, nil))
+}
+
+type testHandler struct {
+ tb testing.TB
+ printer logfmt.Printer
+}
+
+func (h *testHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ //TODO: choose between stdout and stderr based on the event
+ //TODO: decide if we should be calling h.tb.Fail()
+ h.printer.Event(os.Stdout, ev)
+ return ctx
+}
+
+var InitialTime = func() time.Time {
+ t, _ := time.Parse(logfmt.TimeFormat, "2020/03/05 14:27:48")
+ return t
+}()
+
+func ExporterOptions() *event.ExporterOptions {
+ nextTime := InitialTime
+ return &event.ExporterOptions{
+ Now: func() time.Time {
+ thisTime := nextTime
+ nextTime = nextTime.Add(time.Second)
+ return thisTime
+ },
+ }
+}
+
+func CmpOptions() []cmp.Option {
+ return []cmp.Option{
+ cmpopts.SortSlices(func(x, y event.Label) bool {
+ return x.Name < y.Name
+ }),
+ cmpopts.IgnoreFields(event.Event{}, "At", "ctx", "target", "labels"),
+ }
+}
diff --git a/event/export.go b/event/export.go
new file mode 100644
index 0000000..5dc7f02
--- /dev/null
+++ b/event/export.go
@@ -0,0 +1,116 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package event
+
+import (
+ "context"
+ "sync"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+// Exporter synchronizes the delivery of events to handlers.
+type Exporter struct {
+ lastEvent uint64 // accessed using atomic, must be 64 bit aligned
+ opts ExporterOptions
+
+ mu sync.Mutex
+ handler Handler
+ sources sources
+}
+
+// target is a bound exporter.
+// Normally you get a target by looking in the context using To.
+type target struct {
+ exporter *Exporter
+ parent uint64
+ startTime time.Time // for trace latency
+}
+
+type ExporterOptions struct {
+ // If non-nil, sets zero Event.At on delivery.
+ Now func() time.Time
+
+ // Disable some event types, for better performance.
+ DisableLogging bool
+ DisableTracing bool
+ DisableAnnotations bool
+ DisableMetrics bool
+
+ // Enable automatically setting the event Namespace to the calling package's
+ // import path.
+ EnableNamespaces bool
+}
+
+// contextKeyType is used as the key for storing a contextValue on the context.
+type contextKeyType struct{}
+
+var contextKey interface{} = contextKeyType{}
+
+var (
+ defaultTarget unsafe.Pointer
+)
+
+// NewExporter creates an Exporter using the supplied handler and options.
+// Event delivery is serialized to enable safe atomic handling.
+func NewExporter(handler Handler, opts *ExporterOptions) *Exporter {
+ if handler == nil {
+ panic("handler must not be nil")
+ }
+ e := &Exporter{
+ handler: handler,
+ sources: newCallers(),
+ }
+ if opts != nil {
+ e.opts = *opts
+ }
+ if e.opts.Now == nil {
+ e.opts.Now = time.Now
+ }
+ return e
+}
+
+func setDefaultExporter(e *Exporter) {
+ atomic.StorePointer(&defaultTarget, unsafe.Pointer(&target{exporter: e}))
+}
+
+func getDefaultTarget() *target {
+ return (*target)(atomic.LoadPointer(&defaultTarget))
+}
+
+func newContext(ctx context.Context, exporter *Exporter, parent uint64, start time.Time) context.Context {
+ var t *target
+ if exporter != nil {
+ t = &target{exporter: exporter, parent: parent, startTime: start}
+ }
+ return context.WithValue(ctx, contextKey, t)
+}
+
+// prepare events before delivering to the underlying handler.
+// it is safe to call this more than once (trace events have to call it early)
+// If the event does not have a timestamp, and the exporter has a Now function
+// then the timestamp will be updated.
+// If automatic namespaces are enabled and the event doesn't have a namespace,
+// one based on the caller's import path will be provided.
+func (ev *Event) prepare() {
+ e := ev.target.exporter
+ if ev.ID == 0 {
+ ev.ID = atomic.AddUint64(&e.lastEvent, 1)
+ }
+ if e.opts.Now != nil && ev.At.IsZero() {
+ ev.At = e.opts.Now()
+ }
+ if e.opts.EnableNamespaces && ev.Source.Space == "" {
+ ev.Source = e.sources.scanStack()
+ }
+}
+
+func (e *Exporter) loggingEnabled() bool { return !e.opts.DisableLogging }
+func (e *Exporter) annotationsEnabled() bool { return !e.opts.DisableAnnotations }
+func (e *Exporter) tracingEnabled() bool { return !e.opts.DisableTracing }
+func (e *Exporter) metricsEnabled() bool { return !e.opts.DisableMetrics }
diff --git a/event/go.mod b/event/go.mod
new file mode 100644
index 0000000..c96b707
--- /dev/null
+++ b/event/go.mod
@@ -0,0 +1,27 @@
+module golang.org/x/exp/event
+
+go 1.18
+
+require (
+ github.com/go-kit/kit v0.12.0
+ github.com/go-logr/logr v1.2.2
+ github.com/google/go-cmp v0.5.7
+ github.com/rs/zerolog v1.26.1
+ github.com/sirupsen/logrus v1.8.1
+ go.opentelemetry.io/otel v1.4.0
+ go.opentelemetry.io/otel/metric v0.27.0
+ go.opentelemetry.io/otel/sdk v1.4.0
+ go.opentelemetry.io/otel/trace v1.4.0
+ go.uber.org/zap v1.21.0
+)
+
+require (
+ github.com/go-kit/log v0.2.0 // indirect
+ github.com/go-logfmt/logfmt v0.5.1 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.7.0 // indirect
+ golang.org/x/sys v0.11.0 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+)
diff --git a/event/go.sum b/event/go.sum
new file mode 100644
index 0000000..77dda10
--- /dev/null
+++ b/event/go.sum
@@ -0,0 +1,104 @@
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
+github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
+github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
+github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opentelemetry.io/otel v1.4.0 h1:7ESuKPq6zpjRaY5nvVDGiuwK7VAJ8MwkKnmNJ9whNZ4=
+go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
+go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk=
+go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw=
+go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ=
+go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g=
+go.opentelemetry.io/otel/sdk v1.4.0 h1:LJE4SW3jd4lQTESnlpQZcBhQ3oci0U2MLR5uhicfTHQ=
+go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE=
+go.opentelemetry.io/otel/trace v1.4.0 h1:4OOUrPZdVFQkbzl/JSdvGCWIdw5ONXXxzHlaLlWppmo=
+go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
+go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+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-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+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/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/event/keys/keys.go b/event/keys/keys.go
new file mode 100644
index 0000000..b9b7a44
--- /dev/null
+++ b/event/keys/keys.go
@@ -0,0 +1,198 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package keys
+
+import (
+ "golang.org/x/exp/event"
+)
+
+// Value represents a key for untyped values.
+type Value string
+
+// From can be used to get a value from a Label.
+func (k Value) From(l event.Label) interface{} { return l.Interface() }
+
+// Of creates a new Label with this key and the supplied value.
+func (k Value) Of(v interface{}) event.Label {
+ return event.Value(string(k), v)
+}
+
+// Tag represents a key for tagging labels that have no value.
+// These are used when the existence of the label is the entire information it
+// carries, such as marking events to be of a specific kind, or from a specific
+// package.
+type Tag string
+
+// New creates a new Label with this key.
+func (k Tag) New() event.Label {
+ return event.Label{Name: string(k)}
+}
+
+// Int represents a key
+type Int string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Int) Of(v int) event.Label {
+ return event.Int64(string(k), int64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k Int) From(l event.Label) int { return int(l.Int64()) }
+
+// Int8 represents a key
+type Int8 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Int8) Of(v int8) event.Label {
+ return event.Int64(string(k), int64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k Int8) From(l event.Label) int8 { return int8(l.Int64()) }
+
+// Int16 represents a key
+type Int16 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Int16) Of(v int16) event.Label {
+ return event.Int64(string(k), int64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k Int16) From(l event.Label) int16 { return int16(l.Int64()) }
+
+// Int32 represents a key
+type Int32 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Int32) Of(v int32) event.Label {
+ return event.Int64(string(k), int64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k Int32) From(l event.Label) int32 { return int32(l.Int64()) }
+
+// Int64 represents a key
+type Int64 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Int64) Of(v int64) event.Label {
+ return event.Int64(string(k), v)
+}
+
+// From can be used to get a value from a Label.
+func (k Int64) From(l event.Label) int64 { return l.Int64() }
+
+// UInt represents a key
+type UInt string
+
+// Of creates a new Label with this key and the supplied value.
+func (k UInt) Of(v uint) event.Label {
+ return event.Uint64(string(k), uint64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k UInt) From(l event.Label) uint { return uint(l.Uint64()) }
+
+// UInt8 represents a key
+type UInt8 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k UInt8) Of(v uint8) event.Label {
+ return event.Uint64(string(k), uint64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k UInt8) From(l event.Label) uint8 { return uint8(l.Uint64()) }
+
+// UInt16 represents a key
+type UInt16 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k UInt16) Of(v uint16) event.Label {
+ return event.Uint64(string(k), uint64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k UInt16) From(l event.Label) uint16 { return uint16(l.Uint64()) }
+
+// UInt32 represents a key
+type UInt32 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k UInt32) Of(v uint32) event.Label {
+ return event.Uint64(string(k), uint64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k UInt32) From(l event.Label) uint32 { return uint32(l.Uint64()) }
+
+// UInt64 represents a key
+type UInt64 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k UInt64) Of(v uint64) event.Label {
+ return event.Uint64(string(k), v)
+}
+
+// From can be used to get a value from a Label.
+func (k UInt64) From(l event.Label) uint64 { return l.Uint64() }
+
+// Float32 represents a key
+type Float32 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Float32) Of(v float32) event.Label {
+ return event.Float64(string(k), float64(v))
+}
+
+// From can be used to get a value from a Label.
+func (k Float32) From(l event.Label) float32 { return float32(l.Float64()) }
+
+// Float64 represents a key
+type Float64 string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Float64) Of(v float64) event.Label {
+ return event.Float64(string(k), v)
+}
+
+// From can be used to get a value from a Label.
+func (k Float64) From(l event.Label) float64 {
+ return l.Float64()
+}
+
+// String represents a key
+type String string
+
+// Of creates a new Label with this key and the supplied value.
+func (k String) Of(v string) event.Label {
+ return event.String(string(k), v)
+}
+
+// From can be used to get a value from a Label.
+func (k String) From(l event.Label) string { return l.String() }
+
+// Bool represents a key
+type Bool string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Bool) Of(v bool) event.Label {
+ return event.Bool(string(k), v)
+}
+
+// From can be used to get a value from a Label.
+func (k Bool) From(l event.Label) bool { return l.Bool() }
+
+// Error represents a key
+type Error string
+
+// Of creates a new Label with this key and the supplied value.
+func (k Error) Of(v error) event.Label {
+ return event.Value(string(k), v)
+}
+
+// From can be used to get a value from a Label.
+func (k Error) From(l event.Label) error { return l.Interface().(error) }
diff --git a/event/label.go b/event/label.go
new file mode 100644
index 0000000..57307cc
--- /dev/null
+++ b/event/label.go
@@ -0,0 +1,272 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "strconv"
+ "time"
+ "unsafe"
+)
+
+// Label is a named value.
+type Label struct {
+ Name string
+
+ packed uint64
+ untyped interface{}
+}
+
+// stringptr is used in untyped when the Value is a string
+type stringptr unsafe.Pointer
+
+// bytesptr is used in untyped when the Value is a byte slice
+type bytesptr unsafe.Pointer
+
+// int64Kind is used in untyped when the Value is a signed integer
+type int64Kind struct{}
+
+// uint64Kind is used in untyped when the Value is an unsigned integer
+type uint64Kind struct{}
+
+// float64Kind is used in untyped when the Value is a floating point number
+type float64Kind struct{}
+
+// boolKind is used in untyped when the Value is a boolean
+type boolKind struct{}
+
+// durationKind is used in untyped when the Value is a time.Duration
+type durationKind struct{}
+
+// HasValue returns true if the value is set to any type.
+func (l Label) HasValue() bool { return l.untyped != nil }
+
+// Equal reports whether two labels are equal.
+func (l Label) Equal(l2 Label) bool {
+ if l.Name != l2.Name {
+ return false
+ }
+ if !l.HasValue() {
+ return !l2.HasValue()
+ }
+ if !l2.HasValue() {
+ return false
+ }
+ switch {
+ case l.IsString():
+ return l2.IsString() && l.String() == l2.String()
+ case l.IsInt64():
+ return l2.IsInt64() && l.packed == l2.packed
+ case l.IsUint64():
+ return l2.IsUint64() && l.packed == l2.packed
+ case l.IsFloat64():
+ return l2.IsFloat64() && l.Float64() == l2.Float64()
+ case l.IsBool():
+ return l2.IsBool() && l.packed == l2.packed
+ case l.IsDuration():
+ return l2.IsDuration() && l.packed == l2.packed
+ default:
+ return l.untyped == l2.untyped
+ }
+}
+
+// Value returns a Label for the supplied value.
+func Value(name string, value interface{}) Label {
+ return Label{Name: name, untyped: value}
+}
+
+// Interface returns the value.
+// This will never panic, things that were not set using SetInterface will be
+// unpacked and returned anyway.
+func (v Label) Interface() interface{} {
+ switch {
+ case v.IsString():
+ return v.String()
+ case v.IsInt64():
+ return v.Int64()
+ case v.IsUint64():
+ return v.Uint64()
+ case v.IsFloat64():
+ return v.Float64()
+ case v.IsBool():
+ return v.Bool()
+ case v.IsDuration():
+ return v.Duration()
+ default:
+ return v.untyped
+ }
+}
+
+// String returns a new Value for a string.
+func String(name string, s string) Label {
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ return Label{Name: name, packed: uint64(hdr.Len), untyped: stringptr(hdr.Data)}
+}
+
+// String returns the value as a string.
+// This does not panic if v's Kind is not String, instead, it returns a string
+// representation of the value in all cases.
+func (v Label) String() string {
+ if sp, ok := v.untyped.(stringptr); ok {
+ var s string
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ hdr.Data = uintptr(sp)
+ hdr.Len = int(v.packed)
+ return s
+ }
+ // not a string, convert to one
+ switch {
+ case v.IsInt64():
+ return strconv.FormatInt(v.Int64(), 10)
+ case v.IsUint64():
+ return strconv.FormatUint(v.Uint64(), 10)
+ case v.IsFloat64():
+ return strconv.FormatFloat(v.Float64(), 'g', -1, 64)
+ case v.IsBool():
+ if v.Bool() {
+ return "true"
+ } else {
+ return "false"
+ }
+ default:
+ return fmt.Sprint(v.Interface())
+ }
+}
+
+// IsString returns true if the value was built with StringOf.
+func (v Label) IsString() bool {
+ _, ok := v.untyped.(stringptr)
+ return ok
+}
+
+// Bytes returns a new Value for a string.
+func Bytes(name string, data []byte) Label {
+ hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data))
+ return Label{Name: name, packed: uint64(hdr.Len), untyped: bytesptr(hdr.Data)}
+}
+
+// Bytes returns the value as a bytes array.
+func (v Label) Bytes() []byte {
+ bp, ok := v.untyped.(bytesptr)
+ if !ok {
+ panic("Bytes called on non []byte value")
+ }
+ var buf []byte
+ hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
+ hdr.Data = uintptr(bp)
+ hdr.Len = int(v.packed)
+ hdr.Cap = hdr.Len
+ return buf
+}
+
+// IsBytes returns true if the value was built with BytesOf.
+func (v Label) IsBytes() bool {
+ _, ok := v.untyped.(bytesptr)
+ return ok
+}
+
+// Int64 returns a new Value for a signed integer.
+func Int64(name string, u int64) Label {
+ return Label{Name: name, packed: uint64(u), untyped: int64Kind{}}
+}
+
+// Int64 returns the int64 from a value that was set with SetInt64.
+// It will panic for any value for which IsInt64 is not true.
+func (v Label) Int64() int64 {
+ if !v.IsInt64() {
+ panic("Int64 called on non int64 value")
+ }
+ return int64(v.packed)
+}
+
+// IsInt64 returns true if the value was built with SetInt64.
+func (v Label) IsInt64() bool {
+ _, ok := v.untyped.(int64Kind)
+ return ok
+}
+
+// Uint64 returns a new Value for an unsigned integer.
+func Uint64(name string, u uint64) Label {
+ return Label{Name: name, packed: u, untyped: uint64Kind{}}
+}
+
+// Uint64 returns the uint64 from a value that was set with SetUint64.
+// It will panic for any value for which IsUint64 is not true.
+func (v Label) Uint64() uint64 {
+ if !v.IsUint64() {
+ panic("Uint64 called on non uint64 value")
+ }
+ return v.packed
+}
+
+// IsUint64 returns true if the value was built with SetUint64.
+func (v Label) IsUint64() bool {
+ _, ok := v.untyped.(uint64Kind)
+ return ok
+}
+
+// Float64 returns a new Value for a floating point number.
+func Float64(name string, f float64) Label {
+ return Label{Name: name, packed: math.Float64bits(f), untyped: float64Kind{}}
+}
+
+// Float64 returns the float64 from a value that was set with SetFloat64.
+// It will panic for any value for which IsFloat64 is not true.
+func (v Label) Float64() float64 {
+ if !v.IsFloat64() {
+ panic("Float64 called on non float64 value")
+ }
+ return math.Float64frombits(v.packed)
+}
+
+// IsFloat64 returns true if the value was built with SetFloat64.
+func (v Label) IsFloat64() bool {
+ _, ok := v.untyped.(float64Kind)
+ return ok
+}
+
+// Bool returns a new Value for a bool.
+func Bool(name string, b bool) Label {
+ if b {
+ return Label{Name: name, packed: 1, untyped: boolKind{}}
+ }
+ return Label{Name: name, packed: 0, untyped: boolKind{}}
+}
+
+// Bool returns the bool from a value that was set with SetBool.
+// It will panic for any value for which IsBool is not true.
+func (v Label) Bool() bool {
+ if !v.IsBool() {
+ panic("Bool called on non bool value")
+ }
+ if v.packed != 0 {
+ return true
+ }
+ return false
+}
+
+// IsBool returns true if the value was built with SetBool.
+func (v Label) IsBool() bool {
+ _, ok := v.untyped.(boolKind)
+ return ok
+}
+
+func Duration(name string, d time.Duration) Label {
+ return Label{Name: name, packed: uint64(d), untyped: durationKind{}}
+}
+
+func (v Label) Duration() time.Duration {
+ if !v.IsDuration() {
+ panic("Duration called on non-Duration value")
+ }
+ return time.Duration(v.packed)
+}
+
+func (v Label) IsDuration() bool {
+ _, ok := v.untyped.(durationKind)
+ return ok
+}
diff --git a/event/label_test.go b/event/label_test.go
new file mode 100644
index 0000000..0bf3ae1
--- /dev/null
+++ b/event/label_test.go
@@ -0,0 +1,152 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event_test
+
+import (
+ "testing"
+ "time"
+
+ "golang.org/x/exp/event"
+)
+
+func TestOfAs(t *testing.T) {
+ const i = 3
+ var v event.Label
+ v = event.Int64("key", i)
+ if got := v.Int64(); got != i {
+ t.Errorf("got %v, want %v", got, i)
+ }
+ v = event.Uint64("key", i)
+ if got := v.Uint64(); got != i {
+ t.Errorf("got %v, want %v", got, i)
+ }
+ v = event.Float64("key", i)
+ if got := v.Float64(); got != i {
+ t.Errorf("got %v, want %v", got, i)
+ }
+ v = event.Bool("key", true)
+ if got := v.Bool(); got != true {
+ t.Errorf("got %v, want %v", got, true)
+ }
+ const s = "foo"
+ v = event.String("key", s)
+ if got := v.String(); got != s {
+ t.Errorf("got %v, want %v", got, s)
+ }
+ v = event.Bytes("key", []byte(s))
+ if got := v.Bytes(); string(got) != s {
+ t.Errorf("got %v, want %v", got, s)
+ }
+ tm := time.Now()
+ v = event.Value("key", tm)
+ if got := v.Interface(); got != tm {
+ t.Errorf("got %v, want %v", got, tm)
+ }
+ var vnil event.Label
+ if got := vnil.Interface(); got != nil {
+ t.Errorf("got %v, want nil", got)
+ }
+}
+
+func TestEqual(t *testing.T) {
+ var x, y int
+ vals := []event.Label{
+ {},
+ event.Int64("key", 1),
+ event.Int64("key", 2),
+ event.Uint64("key", 3),
+ event.Uint64("key", 4),
+ event.Float64("key", 3.5),
+ event.Float64("key", 3.7),
+ event.Bool("key", true),
+ event.Bool("key", false),
+ event.Value("key", &x),
+ event.Value("key", &y),
+ }
+ for i, v1 := range vals {
+ for j, v2 := range vals {
+ got := v1.Equal(v2)
+ want := i == j
+ if got != want {
+ t.Errorf("%v.Equal(%v): got %t, want %t", v1, v2, got, want)
+ }
+ }
+ }
+}
+
+func panics(f func()) (b bool) {
+ defer func() {
+ if x := recover(); x != nil {
+ b = true
+ }
+ }()
+ f()
+ return false
+}
+
+func TestPanics(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ f func()
+ }{
+ {"int64", func() { event.Float64("key", 3).Int64() }},
+ {"uint64", func() { event.Int64("key", 3).Uint64() }},
+ {"float64", func() { event.Uint64("key", 3).Float64() }},
+ {"bool", func() { event.Int64("key", 3).Bool() }},
+ {"duration", func() { event.Value("key", "value").Duration() }},
+ {"bytes", func() { event.String("key", "value").Bytes() }},
+ } {
+ if !panics(test.f) {
+ t.Errorf("%s: got no panic, want panic", test.name)
+ }
+ }
+}
+
+func TestString(t *testing.T) {
+ for _, test := range []struct {
+ v event.Label
+ want string
+ }{
+ {event.Int64("key", -3), "-3"},
+ {event.Uint64("key", 3), "3"},
+ {event.Float64("key", .15), "0.15"},
+ {event.Bool("key", true), "true"},
+ {event.String("key", "foo"), "foo"},
+ {event.Value("key", time.Duration(3*time.Second)), "3s"},
+ } {
+ if got := test.v.String(); got != test.want {
+ t.Errorf("%#v: got %q, want %q", test.v, got, test.want)
+ }
+ }
+}
+
+func TestNoAlloc(t *testing.T) {
+ // Assign values just to make sure the compiler doesn't optimize away the statements.
+ var (
+ i int64
+ u uint64
+ f float64
+ b bool
+ s string
+ x interface{}
+ p = &i
+ )
+ a := int(testing.AllocsPerRun(5, func() {
+ i = event.Int64("key", 1).Int64()
+ u = event.Uint64("key", 1).Uint64()
+ f = event.Float64("key", 1).Float64()
+ b = event.Bool("key", true).Bool()
+ s = event.String("key", "foo").String()
+ x = event.Value("key", p).Interface()
+ }))
+ if a != 0 {
+ t.Errorf("got %d allocs, want zero", a)
+ }
+ _ = u
+ _ = f
+ _ = b
+ _ = s
+ _ = x
+}
diff --git a/event/metric.go b/event/metric.go
new file mode 100644
index 0000000..e74689e
--- /dev/null
+++ b/event/metric.go
@@ -0,0 +1,158 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event
+
+import (
+ "context"
+ "time"
+)
+
+// A Unit is a unit of measurement for a metric.
+type Unit string
+
+const (
+ UnitDimensionless Unit = "1"
+ UnitBytes Unit = "By"
+ UnitMilliseconds Unit = "ms"
+)
+
+// A Metric represents a kind of recorded measurement.
+type Metric interface {
+ Name() string
+ Options() MetricOptions
+}
+
+type MetricOptions struct {
+ // A string that should be common for all metrics of an application or
+ // service. Defaults to the import path of the package calling
+ // the metric construction function (NewCounter, etc.).
+ Namespace string
+
+ // Optional description of the metric.
+ Description string
+
+ // Optional unit for the metric. Defaults to UnitDimensionless.
+ Unit Unit
+}
+
+// A Counter is a metric that counts something cumulatively.
+type Counter struct {
+ name string
+ opts MetricOptions
+}
+
+func initOpts(popts *MetricOptions) MetricOptions {
+ var opts MetricOptions
+ if popts != nil {
+ opts = *popts
+ }
+ if opts.Namespace == "" {
+ opts.Namespace = scanStack().Space
+ }
+ if opts.Unit == "" {
+ opts.Unit = UnitDimensionless
+ }
+ return opts
+}
+
+// NewCounter creates a counter with the given name.
+func NewCounter(name string, opts *MetricOptions) *Counter {
+ return &Counter{name, initOpts(opts)}
+}
+
+func (c *Counter) Name() string { return c.name }
+func (c *Counter) Options() MetricOptions { return c.opts }
+
+// Record delivers a metric event with the given metric, value and labels to the
+// exporter in the context.
+func (c *Counter) Record(ctx context.Context, v int64, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, c, Int64(string(MetricVal), v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+// A FloatGauge records a single floating-point value that may go up or down.
+// TODO(generics): Gauge[T]
+type FloatGauge struct {
+ name string
+ opts MetricOptions
+}
+
+// NewFloatGauge creates a new FloatGauge with the given name.
+func NewFloatGauge(name string, opts *MetricOptions) *FloatGauge {
+ return &FloatGauge{name, initOpts(opts)}
+}
+
+func (g *FloatGauge) Name() string { return g.name }
+func (g *FloatGauge) Options() MetricOptions { return g.opts }
+
+// Record converts its argument into a Value and returns a MetricValue with the
+// receiver and the value.
+func (g *FloatGauge) Record(ctx context.Context, v float64, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, g, Float64(string(MetricVal), v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+// A DurationDistribution records a distribution of durations.
+// TODO(generics): Distribution[T]
+type DurationDistribution struct {
+ name string
+ opts MetricOptions
+}
+
+// NewDuration creates a new Duration with the given name.
+func NewDuration(name string, opts *MetricOptions) *DurationDistribution {
+ return &DurationDistribution{name, initOpts(opts)}
+}
+
+func (d *DurationDistribution) Name() string { return d.name }
+func (d *DurationDistribution) Options() MetricOptions { return d.opts }
+
+// Record converts its argument into a Value and returns a MetricValue with the
+// receiver and the value.
+func (d *DurationDistribution) Record(ctx context.Context, v time.Duration, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, d, Duration(string(MetricVal), v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+// An IntDistribution records a distribution of int64s.
+type IntDistribution struct {
+ name string
+ opts MetricOptions
+}
+
+func (d *IntDistribution) Name() string { return d.name }
+func (d *IntDistribution) Options() MetricOptions { return d.opts }
+
+// NewIntDistribution creates a new IntDistribution with the given name.
+func NewIntDistribution(name string, opts *MetricOptions) *IntDistribution {
+ return &IntDistribution{name, initOpts(opts)}
+}
+
+// Record converts its argument into a Value and returns a MetricValue with the
+// receiver and the value.
+func (d *IntDistribution) Record(ctx context.Context, v int64, labels ...Label) {
+ ev := New(ctx, MetricKind)
+ if ev != nil {
+ record(ev, d, Int64(string(MetricVal), v))
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Deliver()
+ }
+}
+
+func record(ev *Event, m Metric, l Label) {
+ ev.Labels = append(ev.Labels, l, MetricKey.Of(m))
+}
diff --git a/event/otel/metric.go b/event/otel/metric.go
new file mode 100644
index 0000000..910cc94
--- /dev/null
+++ b/event/otel/metric.go
@@ -0,0 +1,131 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package otel
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "sync"
+
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
+ otelunit "go.opentelemetry.io/otel/metric/unit"
+ "golang.org/x/exp/event"
+)
+
+// MetricHandler is an event.Handler for OpenTelemetry metrics.
+// Its Event method handles Metric events and ignores all others.
+type MetricHandler struct {
+ meter metric.MeterMust
+ mu sync.Mutex
+ // A map from event.Metrics to, effectively, otel Meters.
+ // But since the only thing we need from the Meter is recording a value, we
+ // use a function for that that closes over the Meter itself.
+ recordFuncs map[event.Metric]recordFunc
+}
+
+type recordFunc func(context.Context, event.Label, []attribute.KeyValue)
+
+var _ event.Handler = (*MetricHandler)(nil)
+
+// NewMetricHandler creates a new MetricHandler.
+func NewMetricHandler(m metric.Meter) *MetricHandler {
+ return &MetricHandler{
+ meter: metric.Must(m),
+ recordFuncs: map[event.Metric]recordFunc{},
+ }
+}
+
+func (m *MetricHandler) Event(ctx context.Context, e *event.Event) context.Context {
+ if e.Kind != event.MetricKind {
+ return ctx
+ }
+ // Get the otel instrument corresponding to the event's MetricDescriptor,
+ // or create a new one.
+ mi, ok := event.MetricKey.Find(e)
+ if !ok {
+ panic(errors.New("no metric key for metric event"))
+ }
+ em := mi.(event.Metric)
+ lval := e.Find(event.MetricVal)
+ if !lval.HasValue() {
+ panic(errors.New("no metric value for metric event"))
+ }
+ rf := m.getRecordFunc(em)
+ if rf == nil {
+ panic(fmt.Errorf("unable to record for metric %v", em))
+ }
+ rf(ctx, lval, labelsToAttributes(e.Labels))
+ return ctx
+}
+
+func (m *MetricHandler) getRecordFunc(em event.Metric) recordFunc {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if f, ok := m.recordFuncs[em]; ok {
+ return f
+ }
+ f := m.newRecordFunc(em)
+ m.recordFuncs[em] = f
+ return f
+}
+
+func (m *MetricHandler) newRecordFunc(em event.Metric) recordFunc {
+ opts := em.Options()
+ name := opts.Namespace + "/" + em.Name()
+ otelOpts := []metric.InstrumentOption{
+ metric.WithDescription(opts.Description),
+ metric.WithUnit(otelunit.Unit(opts.Unit)), // cast OK: same strings
+ }
+ switch em.(type) {
+ case *event.Counter:
+ c := m.meter.NewInt64Counter(name, otelOpts...)
+ return func(ctx context.Context, l event.Label, attrs []attribute.KeyValue) {
+ c.Add(ctx, l.Int64(), attrs...)
+ }
+
+ case *event.FloatGauge:
+ g := m.meter.NewFloat64UpDownCounter(name, otelOpts...)
+ return func(ctx context.Context, l event.Label, attrs []attribute.KeyValue) {
+ g.Add(ctx, l.Float64(), attrs...)
+ }
+
+ case *event.DurationDistribution:
+ r := m.meter.NewInt64Histogram(name, otelOpts...)
+ return func(ctx context.Context, l event.Label, attrs []attribute.KeyValue) {
+ r.Record(ctx, l.Duration().Nanoseconds(), attrs...)
+ }
+
+ default:
+ return nil
+ }
+}
+
+func labelsToAttributes(ls []event.Label) []attribute.KeyValue {
+ var attrs []attribute.KeyValue
+ for _, l := range ls {
+ if l.Name == string(event.MetricKey) || l.Name == string(event.MetricVal) {
+ continue
+ }
+ attrs = append(attrs, labelToAttribute(l))
+ }
+ return attrs
+}
+
+func labelToAttribute(l event.Label) attribute.KeyValue {
+ switch {
+ case l.IsString():
+ return attribute.String(l.Name, l.String())
+ case l.IsInt64():
+ return attribute.Int64(l.Name, l.Int64())
+ case l.IsFloat64():
+ return attribute.Float64(l.Name, l.Float64())
+ case l.IsBool():
+ return attribute.Bool(l.Name, l.Bool())
+ default: // including uint64
+ panic(fmt.Errorf("cannot convert label value of type %T to attribute.KeyValue", l.Interface()))
+ }
+}
diff --git a/event/otel/metric_test.go b/event/otel/metric_test.go
new file mode 100644
index 0000000..300f4a7
--- /dev/null
+++ b/event/otel/metric_test.go
@@ -0,0 +1,75 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package otel_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric/metrictest"
+ "go.opentelemetry.io/otel/metric/number"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/otel"
+)
+
+func TestMeter(t *testing.T) {
+ ctx := context.Background()
+ mp := metrictest.NewMeterProvider()
+ mh := otel.NewMetricHandler(mp.Meter("test"))
+ ctx = event.WithExporter(ctx, event.NewExporter(mh, nil))
+ recordMetrics(ctx)
+
+ lib := metrictest.Library{InstrumentationName: "test"}
+ emptyLabels := map[attribute.Key]attribute.Value{}
+ got := metrictest.AsStructs(mp.MeasurementBatches)
+ want := []metrictest.Measured{
+ {
+ Name: "golang.org/x/exp/event/otel_test/hits",
+ Number: number.NewInt64Number(8),
+ Labels: emptyLabels,
+ Library: lib,
+ },
+ {
+ Name: "golang.org/x/exp/event/otel_test/temp",
+ Number: number.NewFloat64Number(-100),
+ Labels: map[attribute.Key]attribute.Value{"location": attribute.StringValue("Mare Imbrium")},
+ Library: lib,
+ },
+ {
+ Name: "golang.org/x/exp/event/otel_test/latency",
+ Number: number.NewInt64Number(int64(1248 * time.Millisecond)),
+ Labels: emptyLabels,
+ Library: lib,
+ },
+ {
+ Name: "golang.org/x/exp/event/otel_test/latency",
+ Number: number.NewInt64Number(int64(1255 * time.Millisecond)),
+ Labels: emptyLabels,
+ Library: lib,
+ },
+ }
+
+ if diff := cmp.Diff(want, got, cmp.Comparer(valuesEqual)); diff != "" {
+ t.Errorf("mismatch (-want, got):\n%s", diff)
+ }
+}
+
+func valuesEqual(v1, v2 attribute.Value) bool {
+ return v1.AsInterface() == v2.AsInterface()
+}
+
+func recordMetrics(ctx context.Context) {
+ c := event.NewCounter("hits", &event.MetricOptions{Description: "Earth meteorite hits"})
+ g := event.NewFloatGauge("temp", &event.MetricOptions{Description: "moon surface temperature in Kelvin"})
+ d := event.NewDuration("latency", &event.MetricOptions{Description: "Earth-moon comms lag, milliseconds"})
+
+ c.Record(ctx, 8)
+ g.Record(ctx, -100, event.String("location", "Mare Imbrium"))
+ d.Record(ctx, 1248*time.Millisecond)
+ d.Record(ctx, 1255*time.Millisecond)
+}
diff --git a/event/otel/trace.go b/event/otel/trace.go
new file mode 100644
index 0000000..67fbe29
--- /dev/null
+++ b/event/otel/trace.go
@@ -0,0 +1,58 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package otel
+
+import (
+ "context"
+
+ "go.opentelemetry.io/otel/trace"
+ "golang.org/x/exp/event"
+)
+
+type TraceHandler struct {
+ tracer trace.Tracer
+}
+
+func NewTraceHandler(t trace.Tracer) *TraceHandler {
+ return &TraceHandler{tracer: t}
+}
+
+type spanKey struct{}
+
+func (t *TraceHandler) Event(ctx context.Context, ev *event.Event) context.Context {
+ switch ev.Kind {
+ case event.StartKind:
+ name, opts := labelsToSpanStartOptions(ev.Labels)
+ octx, span := t.tracer.Start(ctx, name, opts...)
+ return context.WithValue(octx, spanKey{}, span)
+ case event.EndKind:
+ span, ok := ctx.Value(spanKey{}).(trace.Span)
+ if !ok {
+ panic("End called on context with no span")
+ }
+ span.End()
+ return ctx
+ default:
+ return ctx
+ }
+}
+
+func labelsToSpanStartOptions(ls []event.Label) (string, []trace.SpanStartOption) {
+ var opts []trace.SpanStartOption
+ var name string
+ for _, l := range ls {
+ switch l.Name {
+ case "link":
+ opts = append(opts, trace.WithLinks(l.Interface().(trace.Link)))
+ case "newRoot":
+ opts = append(opts, trace.WithNewRoot())
+ case "spanKind":
+ opts = append(opts, trace.WithSpanKind(l.Interface().(trace.SpanKind)))
+ case "name":
+ name = l.String()
+ }
+ }
+ return name, opts
+}
diff --git a/event/otel/trace_test.go b/event/otel/trace_test.go
new file mode 100644
index 0000000..81d369f
--- /dev/null
+++ b/event/otel/trace_test.go
@@ -0,0 +1,159 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package otel_test
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "testing"
+
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ "go.opentelemetry.io/otel/trace"
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/otel"
+)
+
+func TestTrace(t *testing.T) {
+ // Verify that otel and event traces work well together.
+ // This test uses a single, fixed span tree (see makeTraceSpec).
+ // Each test case varies which of the individual spans are
+ // created directly from an otel tracer, and which are created
+ // using the event package.
+
+ want := "root (f (g h) p (q r))"
+
+ for i, tfunc := range []func(int) bool{
+ func(int) bool { return true },
+ func(int) bool { return false },
+ func(i int) bool { return i%2 == 0 },
+ func(i int) bool { return i%2 == 1 },
+ func(i int) bool { return i%3 == 0 },
+ func(i int) bool { return i%3 == 1 },
+ } {
+ ctx, tr, shutdown := setupOtel()
+ // There are 7 spans, so we create a 7-element slice.
+ // tfunc determines, for each index, whether it holds
+ // an otel tracer or nil.
+ tracers := make([]trace.Tracer, 7)
+ for i := 0; i < len(tracers); i++ {
+ if tfunc(i) {
+ tracers[i] = tr
+ }
+ }
+ s := makeTraceSpec(tracers)
+ s.apply(ctx)
+ got := shutdown()
+ if got != want {
+ t.Errorf("#%d: got %v, want %v", i, got, want)
+ }
+ }
+}
+
+func makeTraceSpec(tracers []trace.Tracer) *traceSpec {
+ return &traceSpec{
+ name: "root",
+ tracer: tracers[0],
+ children: []*traceSpec{
+ {
+ name: "f",
+ tracer: tracers[1],
+ children: []*traceSpec{
+ {name: "g", tracer: tracers[2]},
+ {name: "h", tracer: tracers[3]},
+ },
+ },
+ {
+ name: "p",
+ tracer: tracers[4],
+ children: []*traceSpec{
+ {name: "q", tracer: tracers[5]},
+ {name: "r", tracer: tracers[6]},
+ },
+ },
+ },
+ }
+}
+
+type traceSpec struct {
+ name string
+ tracer trace.Tracer // nil for event
+ children []*traceSpec
+}
+
+// apply builds spans for the traceSpec and all its children,
+// If the traceSpec has a non-nil tracer, it is used to create the span.
+// Otherwise, event.Trace.Start is used.
+func (s *traceSpec) apply(ctx context.Context) {
+ if s.tracer != nil {
+ var span trace.Span
+ ctx, span = s.tracer.Start(ctx, s.name)
+ defer span.End()
+ } else {
+ ctx = event.Start(ctx, s.name)
+ defer event.End(ctx)
+ }
+ for _, c := range s.children {
+ c.apply(ctx)
+ }
+}
+
+func setupOtel() (context.Context, trace.Tracer, func() string) {
+ ctx := context.Background()
+ e := newTestExporter()
+ bsp := sdktrace.NewSimpleSpanProcessor(e)
+ stp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(bsp))
+ tracer := stp.Tracer("")
+
+ ee := event.NewExporter(otel.NewTraceHandler(tracer), nil)
+ ctx = event.WithExporter(ctx, ee)
+ return ctx, tracer, func() string { stp.Shutdown(ctx); return e.got }
+}
+
+// testExporter is an otel exporter for traces
+type testExporter struct {
+ m map[trace.SpanID][]sdktrace.ReadOnlySpan // key is parent SpanID
+ got string
+}
+
+var _ sdktrace.SpanExporter = (*testExporter)(nil)
+
+func newTestExporter() *testExporter {
+ return &testExporter{m: map[trace.SpanID][]sdktrace.ReadOnlySpan{}}
+}
+
+func (e *testExporter) ExportSpans(ctx context.Context, ss []sdktrace.ReadOnlySpan) error {
+ for _, s := range ss {
+ sid := s.Parent().SpanID()
+ e.m[sid] = append(e.m[sid], s)
+ }
+ return nil
+}
+
+func (e *testExporter) Shutdown(ctx context.Context) error {
+ root := e.m[trace.SpanID{}][0]
+ var buf bytes.Buffer
+ e.print(&buf, root)
+ e.got = buf.String()
+ return nil
+}
+
+func (e *testExporter) print(w io.Writer, ss sdktrace.ReadOnlySpan) {
+ fmt.Fprintf(w, "%s", ss.Name())
+ children := e.m[ss.SpanContext().SpanID()]
+ if len(children) > 0 {
+ fmt.Fprint(w, " (")
+ for i, ss := range children {
+ if i != 0 {
+ fmt.Fprint(w, " ")
+ }
+ e.print(w, ss)
+ }
+ fmt.Fprint(w, ")")
+ }
+}
diff --git a/event/severity/severity.go b/event/severity/severity.go
new file mode 100644
index 0000000..aeae88d
--- /dev/null
+++ b/event/severity/severity.go
@@ -0,0 +1,154 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package severity
+
+import (
+ "context"
+ "fmt"
+
+ "golang.org/x/exp/event"
+)
+
+// Level represents a severity level of an event.
+// The basic severity levels are designed to match the levels used in open telemetry.
+// Smaller numerical values correspond to less severe events (such as debug events),
+// larger numerical values correspond to more severe events (such as errors and critical events).
+//
+// The following table defines the meaning severity levels:
+// 1-4 TRACE A fine-grained debugging event. Typically disabled in default configurations.
+// 5-8 DEBUG A debugging event.
+// 9-12 INFO An informational event. Indicates that an event happened.
+// 13-16 WARN A warning event. Not an error but is likely more important than an informational event.
+// 17-20 ERROR An error event. Something went wrong.
+// 21-24 FATAL A fatal error such as application or system crash.
+//
+// See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#severity-fields
+// for more details
+type Level uint64
+
+const (
+ Trace = Level(1)
+ Debug = Level(5)
+ Info = Level(9)
+ Warning = Level(13)
+ Error = Level(17)
+ Fatal = Level(21)
+ Max = Level(24)
+)
+
+const Key = "level"
+
+// Of creates a label for the level.
+func (l Level) Label() event.Label {
+ return event.Value(Key, l)
+}
+
+// From can be used to get a value from a Label.
+func From(t event.Label) Level {
+ return t.Interface().(Level)
+}
+
+func (l Level) Log(ctx context.Context, msg string, labels ...event.Label) {
+ ev := event.New(ctx, event.LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, l.Label())
+ ev.Labels = append(ev.Labels, labels...)
+ ev.Labels = append(ev.Labels, event.String("msg", msg))
+ ev.Deliver()
+ }
+}
+
+func (l Level) Logf(ctx context.Context, msg string, args ...interface{}) {
+ ev := event.New(ctx, event.LogKind)
+ if ev != nil {
+ ev.Labels = append(ev.Labels, l.Label())
+ ev.Labels = append(ev.Labels, event.String("msg", fmt.Sprintf(msg, args...)))
+ ev.Deliver()
+ }
+}
+
+func (l Level) Class() Level {
+ switch {
+ case l > Max:
+ return Max
+ case l > Fatal:
+ return Fatal
+ case l > Error:
+ return Error
+ case l > Warning:
+ return Warning
+ case l > Debug:
+ return Debug
+ case l > Info:
+ return Info
+ case l > Trace:
+ return Trace
+ default:
+ return 0
+ }
+}
+
+func (l Level) String() string {
+ switch l {
+ case 0:
+ return "invalid"
+
+ case Trace:
+ return "trace"
+ case Trace + 1:
+ return "trace2"
+ case Trace + 2:
+ return "trace3"
+ case Trace + 3:
+ return "trace4"
+
+ case Debug:
+ return "debug"
+ case Debug + 1:
+ return "debug2"
+ case Debug + 2:
+ return "debug3"
+ case Debug + 3:
+ return "debug4"
+
+ case Info:
+ return "info"
+ case Info + 1:
+ return "info2"
+ case Info + 2:
+ return "info3"
+ case Info + 3:
+ return "info4"
+
+ case Warning:
+ return "warning"
+ case Warning + 1:
+ return "warning2"
+ case Warning + 2:
+ return "warning3"
+ case Warning + 3:
+ return "warning4"
+
+ case Error:
+ return "error"
+ case Error + 1:
+ return "error2"
+ case Error + 2:
+ return "error3"
+ case Error + 3:
+ return "error4"
+
+ case Fatal:
+ return "fatal"
+ case Fatal + 1:
+ return "fatal2"
+ case Fatal + 2:
+ return "fatal3"
+ case Fatal + 3:
+ return "fatal4"
+ default:
+ return "invalid"
+ }
+}
diff --git a/event/severity/severity_test.go b/event/severity/severity_test.go
new file mode 100644
index 0000000..b5ca973
--- /dev/null
+++ b/event/severity/severity_test.go
@@ -0,0 +1,44 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package severity_test
+
+import (
+ "context"
+ "strings"
+ "testing"
+
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/adapter/logfmt"
+ "golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/event/severity"
+)
+
+func TestPrint(t *testing.T) {
+ ctx := context.Background()
+ for _, test := range []struct {
+ name string
+ events func(context.Context)
+ expect string
+ }{{
+ name: "debug",
+ events: func(ctx context.Context) { severity.Debug.Log(ctx, "a message") },
+ expect: `time="2020/03/05 14:27:48" level=debug msg="a message"`,
+ }, {
+ name: "info",
+ events: func(ctx context.Context) { severity.Info.Log(ctx, "a message") },
+ expect: `time="2020/03/05 14:27:48" level=info msg="a message"`},
+ } {
+ buf := &strings.Builder{}
+ ctx := event.WithExporter(ctx, event.NewExporter(logfmt.NewHandler(buf), eventtest.ExporterOptions()))
+ test.events(ctx)
+ got := strings.TrimSpace(buf.String())
+ expect := strings.TrimSpace(test.expect)
+ if got != expect {
+ t.Errorf("%s failed\ngot : %q\nexpect: %q", test.name, got, expect)
+ }
+ }
+}
diff --git a/event/source.go b/event/source.go
new file mode 100644
index 0000000..3af0bd4
--- /dev/null
+++ b/event/source.go
@@ -0,0 +1,210 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !disable_events
+
+package event
+
+import (
+ "reflect"
+ "runtime"
+ "sort"
+ "strings"
+)
+
+const (
+ // this is the maximum amount of helpers we scan past to find a non helper
+ helperDepthLimit = 5
+)
+
+type sources struct {
+ entries []caller
+}
+
+type Source struct {
+ Space string
+ Owner string
+ Name string
+}
+
+type caller struct {
+ helper bool
+ pc uintptr
+ source Source
+}
+
+var globalCallers chan *sources
+
+// RegisterHelper records a function as being an event helper that should not
+// be used when capturing the source information on events.
+// v should be either a string or a function pointer.
+// If v is a string it is of the form
+//
+// Space.Owner.Name
+//
+// where Owner and Name cannot contain '/' and Name also cannot contain '.'
+func RegisterHelper(v interface{}) {
+ g := <-globalCallers
+ defer func() { globalCallers <- g }()
+ switch v := v.(type) {
+ case string:
+ g.entries = append(g.entries, caller{source: splitName(v), helper: true})
+ default:
+ g.helperFunction(v)
+ }
+}
+
+func init() {
+ g := &sources{}
+ // make all entries in the event package helpers
+ globalCallers = make(chan *sources, 1)
+ globalCallers <- g
+ RegisterHelper("golang.org/x/exp/event")
+}
+
+func newCallers() sources {
+ g := <-globalCallers
+ defer func() { globalCallers <- g }()
+ c := sources{}
+ c.entries = make([]caller, len(g.entries))
+ copy(c.entries, g.entries)
+ return c
+}
+
+func (c *sources) addCaller(entry caller) {
+ i := sort.Search(len(c.entries), func(i int) bool {
+ return c.entries[i].pc >= entry.pc
+ })
+ if i >= len(c.entries) {
+ // add to end
+ c.entries = append(c.entries, entry)
+ return
+ }
+ if c.entries[i].pc == entry.pc {
+ // already present
+ return
+ }
+ //expand the array
+ c.entries = append(c.entries, caller{})
+ //make a space
+ copy(c.entries[i+1:], c.entries[i:])
+ // insert the entry
+ c.entries[i] = entry
+}
+
+func (c *sources) getCaller(pc uintptr) (caller, bool) {
+ i := sort.Search(len(c.entries), func(i int) bool {
+ return c.entries[i].pc >= pc
+ })
+ if i == len(c.entries) || c.entries[i].pc != pc {
+ return caller{}, false
+ }
+ return c.entries[i], true
+}
+
+func scanStack() Source {
+ g := <-globalCallers
+ defer func() { globalCallers <- g }()
+ return g.scanStack()
+}
+
+func (c *sources) scanStack() Source {
+ // first capture the caller stack
+ var stack [helperDepthLimit]uintptr
+ // we can skip the first three entries
+ // runtime.Callers
+ // event.(*sources).scanStack (this function)
+ // another function in this package (because scanStack is private)
+ depth := runtime.Callers(3, stack[:]) // start at 2 to skip Callers and this function
+ // do a cheap first pass to see if we have an entry for this stack
+ for i := 0; i < depth; i++ {
+ pc := stack[i]
+ e, found := c.getCaller(pc)
+ if found {
+ if !e.helper {
+ // exact non helper match match found, return it
+ return e.source
+ }
+ // helper found, keep scanning
+ continue
+ }
+ // stack entry not found, we need to fill one in
+ f := runtime.FuncForPC(stack[i])
+ if f == nil {
+ // symtab lookup failed, pretend it does not exist
+ continue
+ }
+ e = caller{
+ source: splitName(f.Name()),
+ pc: pc,
+ }
+ e.helper = c.isHelper(e)
+ c.addCaller(e)
+ if !e.helper {
+ // found a non helper entry, add it and return it
+ return e.source
+ }
+ }
+ // ran out of stack, was all helpers
+ return Source{}
+}
+
+// we do helper matching by name, if the pc matched we would have already found
+// that, but helper registration does not know the call stack pcs
+func (c *sources) isHelper(entry caller) bool {
+ // scan to see if it matches any of the helpers
+ // we match by name in case of inlining
+ for _, e := range c.entries {
+ if !e.helper {
+ // ignore all the non helper entries
+ continue
+ }
+ if isMatch(entry.source.Space, e.source.Space) &&
+ isMatch(entry.source.Owner, e.source.Owner) &&
+ isMatch(entry.source.Name, e.source.Name) {
+ return true
+ }
+ }
+ return false
+}
+
+func isMatch(value, against string) bool {
+ return len(against) == 0 || value == against
+}
+
+func (c *sources) helperFunction(v interface{}) {
+ r := reflect.ValueOf(v)
+ pc := r.Pointer()
+ f := runtime.FuncForPC(pc)
+ entry := caller{
+ source: splitName(f.Name()),
+ pc: f.Entry(),
+ helper: true,
+ }
+ c.addCaller(entry)
+ if entry.pc != pc {
+ entry.pc = pc
+ c.addCaller(entry)
+ }
+}
+
+func splitName(full string) Source {
+ // Function is the fully-qualified function name. The name itself may
+ // have dots (for a closure, for instance), but it can't have slashes.
+ // So the package path ends at the first dot after the last slash.
+ entry := Source{Space: full}
+ slash := strings.LastIndexByte(full, '/')
+ if slash < 0 {
+ slash = 0
+ }
+ if dot := strings.IndexByte(full[slash:], '.'); dot >= 0 {
+ entry.Space = full[:slash+dot]
+ entry.Name = full[slash+dot+1:]
+ if dot = strings.LastIndexByte(entry.Name, '.'); dot >= 0 {
+ entry.Owner = entry.Name[:dot]
+ entry.Name = entry.Name[dot+1:]
+ }
+ }
+ return entry
+}
diff --git a/event/source_test.go b/event/source_test.go
new file mode 100644
index 0000000..603f23c
--- /dev/null
+++ b/event/source_test.go
@@ -0,0 +1,86 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package event_test
+
+import (
+ "context"
+ "testing"
+
+ "golang.org/x/exp/event"
+ "golang.org/x/exp/event/eventtest"
+)
+
+const thisImportPath = "golang.org/x/exp/event_test"
+
+func TestNamespace(t *testing.T) {
+ event.RegisterHelper(testHelperB)
+ event.RegisterHelper(thisImportPath + ".testHelperC")
+ h := &eventtest.CaptureHandler{}
+ opt := eventtest.ExporterOptions()
+ opt.EnableNamespaces = true
+ ctx := event.WithExporter(context.Background(), event.NewExporter(h, opt))
+ for _, test := range []struct {
+ name string
+ do func(context.Context)
+ expect event.Source
+ }{{
+ name: "simple",
+ do: testA,
+ expect: event.Source{Space: thisImportPath, Name: "testA"},
+ }, {
+ name: "pointer helper",
+ do: testB,
+ expect: event.Source{Space: thisImportPath, Name: "testB"},
+ }, {
+ name: "named helper",
+ do: testC,
+ expect: event.Source{Space: thisImportPath, Name: "testC"},
+ }, {
+ name: "method",
+ do: testD,
+ expect: event.Source{Space: thisImportPath, Owner: "tester", Name: "D"},
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ h.Got = h.Got[:0]
+ test.do(ctx)
+ if len(h.Got) != 1 {
+ t.Fatalf("Expected 1 event, got %v", len(h.Got))
+ }
+ got := h.Got[0].Source
+ if got.Space != test.expect.Space {
+ t.Errorf("got namespace %q, want, %q", got.Space, test.expect.Space)
+ }
+ if got.Owner != test.expect.Owner {
+ t.Errorf("got owner %q, want, %q", got.Owner, test.expect.Owner)
+ }
+ if got.Name != test.expect.Name {
+ t.Errorf("got name %q, want, %q", got.Name, test.expect.Name)
+ }
+ })
+ }
+}
+
+type tester struct{}
+
+//go:noinline
+func testA(ctx context.Context) { event.Log(ctx, "test A") }
+
+//go:noinline
+func testB(ctx context.Context) { testHelperB(ctx) }
+
+//go:noinline
+func testHelperB(ctx context.Context) { event.Log(ctx, "test B") }
+
+//go:noinline
+func testC(ctx context.Context) { testHelperC(ctx) }
+
+//go:noinline
+func testHelperC(ctx context.Context) { event.Log(ctx, "test C") }
+
+//go:noinline
+func testD(ctx context.Context) { tester{}.D(ctx) }
+
+//go:noinline
+func (tester) D(ctx context.Context) { event.Log(ctx, "test D") }
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..bd33fb7
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+module golang.org/x/exp
+
+go 1.20
+
+require (
+ github.com/google/go-cmp v0.5.8
+ golang.org/x/mod v0.13.0
+ golang.org/x/tools v0.14.0
+)
+
+require golang.org/x/sys v0.13.0 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5701c53
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,9 @@
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
diff --git a/inotify/README.txt b/inotify/README.txt
new file mode 100644
index 0000000..2ba49f6
--- /dev/null
+++ b/inotify/README.txt
@@ -0,0 +1,3 @@
+Please use gopkg.in/fsnotify.v0 instead.
+
+For updates, see: https://fsnotify.org/
diff --git a/io/i2c/devfs.go b/io/i2c/devfs.go
new file mode 100644
index 0000000..06a45f1
--- /dev/null
+++ b/io/i2c/devfs.go
@@ -0,0 +1,79 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+
+package i2c
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "syscall"
+
+ "golang.org/x/exp/io/i2c/driver"
+)
+
+// Devfs is an I2C driver that works against the devfs.
+// You need to load the "i2c-dev" kernel module to use this driver.
+type Devfs struct {
+ // Dev is the I2C bus device, e.g. /dev/i2c-1. Required.
+ Dev string
+}
+
+const (
+ i2c_SLAVE = 0x0703 // TODO(jbd): Allow users to use I2C_SLAVE_FORCE?
+ i2c_TENBIT = 0x0704
+)
+
+// TODO(jbd): Support I2C_RETRIES and I2C_TIMEOUT at the driver and implementation level.
+
+func (d *Devfs) Open(addr int, tenbit bool) (driver.Conn, error) {
+ f, err := os.OpenFile(d.Dev, os.O_RDWR, os.ModeDevice)
+ if err != nil {
+ return nil, err
+ }
+ conn := &devfsConn{f: f}
+ if tenbit {
+ if err := conn.ioctl(i2c_TENBIT, uintptr(1)); err != nil {
+ conn.Close()
+ return nil, fmt.Errorf("cannot enable the 10-bit address mode on bus %v: %v", d.Dev, err)
+ }
+ }
+ if err := conn.ioctl(i2c_SLAVE, uintptr(addr)); err != nil {
+ conn.Close()
+ return nil, fmt.Errorf("error opening the address (%v) on the bus (%v): %v", addr, d.Dev, err)
+ }
+ return conn, nil
+}
+
+type devfsConn struct {
+ f *os.File
+}
+
+func (c *devfsConn) Tx(w, r []byte) error {
+ if w != nil {
+ if _, err := c.f.Write(w); err != nil {
+ return err
+ }
+ c.f.Sync()
+ }
+ if r != nil {
+ if _, err := io.ReadFull(c.f, r); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *devfsConn) Close() error {
+ return c.f.Close()
+}
+
+func (c *devfsConn) ioctl(arg1, arg2 uintptr) error {
+ if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, c.f.Fd(), arg1, arg2); errno != 0 {
+ return syscall.Errno(errno)
+ }
+ return nil
+}
diff --git a/io/i2c/devfs_nonlinux.go b/io/i2c/devfs_nonlinux.go
new file mode 100644
index 0000000..3cc48cd
--- /dev/null
+++ b/io/i2c/devfs_nonlinux.go
@@ -0,0 +1,24 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !linux
+
+package i2c
+
+import (
+ "errors"
+
+ "golang.org/x/exp/io/i2c/driver"
+)
+
+// Devfs is no-implementation so developers using cross compilation
+// can rely on local tools even though the real implementation isn't
+// available on their platform.
+type Devfs struct {
+ Dev string
+}
+
+func (d *Devfs) Open(addr int, tenbit bool) (driver.Conn, error) {
+ return nil, errors.New("not implemented on this platform")
+}
diff --git a/io/i2c/driver/driver.go b/io/i2c/driver/driver.go
new file mode 100644
index 0000000..3fe51a4
--- /dev/null
+++ b/io/i2c/driver/driver.go
@@ -0,0 +1,24 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package driver contains interfaces to be implemented by various I2C implementations.
+package driver // import "golang.org/x/exp/io/i2c/driver"
+
+// Opener opens a connection to an I2C device to communicate with
+// the I2C address given. If the address is an 10-bit I2C address,
+// tenbit is true.
+type Opener interface {
+ Open(addr int, tenbit bool) (Conn, error)
+}
+
+// Conn represents an active connection to an I2C device.
+type Conn interface {
+ // Tx first writes w (if not nil), then reads len(r)
+ // bytes from device into r (if not nil) in a single
+ // I2C transaction.
+ Tx(w, r []byte) error
+
+ // Close closes the connection.
+ Close() error
+}
diff --git a/io/i2c/example/displayip/main.go b/io/i2c/example/displayip/main.go
new file mode 100644
index 0000000..58fe694
--- /dev/null
+++ b/io/i2c/example/displayip/main.go
@@ -0,0 +1,89 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package main contains a program that displays the IPv4 address
+// of the machine on an a Grove-LCD RGB backlight.
+package main
+
+import (
+ "fmt"
+ "net"
+
+ "golang.org/x/exp/io/i2c"
+)
+
+const (
+ DISPLAY_RGB_ADDR = 0x62
+ DISPLAY_TEXT_ADDR = 0x3e
+)
+
+func main() {
+ d, err := i2c.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}, DISPLAY_RGB_ADDR)
+ if err != nil {
+ panic(err)
+ }
+
+ td, err := i2c.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}, DISPLAY_TEXT_ADDR)
+ if err != nil {
+ panic(err)
+ }
+
+ // Set the backlight color to 100,100,100.
+ write(d, []byte{0, 0})
+ write(d, []byte{1, 0})
+ write(d, []byte{0x08, 0xaa})
+ write(d, []byte{4, 100}) // R value
+ write(d, []byte{3, 100}) // G value
+ write(d, []byte{2, 100}) // B value
+
+ ip, err := resolveIP()
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("host machine IP is %v\n", ip)
+
+ write(td, []byte{0x80, 0x02}) // return home
+ write(td, []byte{0x80, 0x01}) // clean the display
+ write(td, []byte{0x80, 0x08 | 0x04}) // no cursor
+ write(td, []byte{0x80, 0x28}) // two lines
+
+ for _, s := range ip {
+ write(td, []byte{0x40, byte(s)})
+ }
+}
+
+func write(d *i2c.Device, buf []byte) {
+ err := d.Write(buf)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func resolveIP() (string, error) {
+ var ip net.IP
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ panic(err)
+ }
+ for _, i := range ifaces {
+ addrs, err := i.Addrs()
+ if err != nil {
+ panic(err)
+ }
+ for _, addr := range addrs {
+ switch v := addr.(type) {
+ case *net.IPNet:
+ ip = v.IP
+ case *net.IPAddr:
+ ip = v.IP
+ }
+ ip = ip.To4()
+ if ip != nil && ip.String() != "127.0.0.1" {
+ return ip.String(), nil
+ }
+ }
+ }
+ return "", fmt.Errorf("cannot resolve the IP")
+}
diff --git a/io/i2c/example_test.go b/io/i2c/example_test.go
new file mode 100644
index 0000000..aeedaeb
--- /dev/null
+++ b/io/i2c/example_test.go
@@ -0,0 +1,24 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package i2c_test
+
+import (
+ "golang.org/x/exp/io/i2c"
+)
+
+func ExampleOpen() {
+ d, err := i2c.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}, 0x39)
+ if err != nil {
+ panic(err)
+ }
+
+ // opens a 10-bit address
+ d, err = i2c.Open(&i2c.Devfs{Dev: "/dev/i2c-1"}, i2c.TenBit(0x78))
+ if err != nil {
+ panic(err)
+ }
+
+ _ = d
+}
diff --git a/io/i2c/i2c.go b/io/i2c/i2c.go
new file mode 100644
index 0000000..192ad3d
--- /dev/null
+++ b/io/i2c/i2c.go
@@ -0,0 +1,75 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package i2c allows users to read from and write to a slave I2C device.
+//
+// # Deprecated
+//
+// This package is not maintained anymore. An actively supported cross-platform
+// alternative is https://periph.io/.
+package i2c // import "golang.org/x/exp/io/i2c"
+
+import (
+ "golang.org/x/exp/io/i2c/driver"
+)
+
+const tenbitMask = 1 << 12
+
+// Device represents an I2C device. Devices must be closed once
+// they are no longer in use.
+type Device struct {
+ conn driver.Conn
+}
+
+// TenBit marks an I2C address as a 10-bit address.
+func TenBit(addr int) int {
+ return addr | tenbitMask
+}
+
+// Read reads len(buf) bytes from the device.
+func (d *Device) Read(buf []byte) error {
+ return d.conn.Tx(nil, buf)
+}
+
+// ReadReg is similar to Read but it reads from a register.
+func (d *Device) ReadReg(reg byte, buf []byte) error {
+ return d.conn.Tx([]byte{reg}, buf)
+}
+
+// Write writes the buffer to the device. If it is required to write to a
+// specific register, the register should be passed as the first byte in the
+// given buffer.
+func (d *Device) Write(buf []byte) (err error) {
+ return d.conn.Tx(buf, nil)
+}
+
+// WriteReg is similar to Write but writes to a register.
+func (d *Device) WriteReg(reg byte, buf []byte) (err error) {
+ // TODO(jbd): Do not allocate, not optimal.
+ return d.conn.Tx(append([]byte{reg}, buf...), nil)
+}
+
+// Close closes the device and releases the underlying sources.
+func (d *Device) Close() error {
+ return d.conn.Close()
+}
+
+// Open opens a connection to an I2C device.
+// All devices must be closed once they are no longer in use.
+// For devices that use 10-bit I2C addresses, addr can be marked
+// as a 10-bit address with TenBit.
+func Open(o driver.Opener, addr int) (*Device, error) {
+ unmasked, tenbit := resolveAddr(addr)
+ conn, err := o.Open(unmasked, tenbit)
+ if err != nil {
+ return nil, err
+ }
+ return &Device{conn: conn}, nil
+}
+
+// resolveAddr returns whether the addr is 10-bit masked or not.
+// It also returns the unmasked address.
+func resolveAddr(addr int) (unmasked int, tenbit bool) {
+ return addr & (tenbitMask - 1), addr&tenbitMask == tenbitMask
+}
diff --git a/io/i2c/i2c_test.go b/io/i2c/i2c_test.go
new file mode 100644
index 0000000..8765e3e
--- /dev/null
+++ b/io/i2c/i2c_test.go
@@ -0,0 +1,31 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package i2c
+
+import (
+ "testing"
+)
+
+func TestTenBit(t *testing.T) {
+ tc := []struct {
+ masked int
+ addrWant int
+ tenbitWant bool
+ }{
+ {TenBit(0x5), 0x5, true},
+ {0x5, 0x5, false},
+ {TenBit(0x200), 0x200, true},
+ }
+
+ for _, tt := range tc {
+ unmasked, tenbit := resolveAddr(tt.masked)
+ if want, got := tt.tenbitWant, tenbit; got != want {
+ t.Errorf("want address %x as 10-bit; got non 10-bit", tt.masked)
+ }
+ if want, got := tt.addrWant, unmasked; got != want {
+ t.Errorf("want address %v; got %v", want, got)
+ }
+ }
+}
diff --git a/io/spi/devfs.go b/io/spi/devfs.go
new file mode 100644
index 0000000..f67df50
--- /dev/null
+++ b/io/spi/devfs.go
@@ -0,0 +1,176 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+
+package spi
+
+import (
+ "fmt"
+ "os"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/exp/io/spi/driver"
+)
+
+const (
+ devfs_MAGIC = 107
+
+ devfs_NRBITS = 8
+ devfs_TYPEBITS = 8
+ devfs_SIZEBITS = 13
+ devfs_DIRBITS = 3
+
+ devfs_NRSHIFT = 0
+ devfs_TYPESHIFT = devfs_NRSHIFT + devfs_NRBITS
+ devfs_SIZESHIFT = devfs_TYPESHIFT + devfs_TYPEBITS
+ devfs_DIRSHIFT = devfs_SIZESHIFT + devfs_SIZEBITS
+
+ devfs_READ = 2
+ devfs_WRITE = 4
+)
+
+type payload struct {
+ tx uint64
+ rx uint64
+ length uint32
+ speed uint32
+ delay uint16
+ bits uint8
+ csChange uint8
+ txNBits uint8
+ rxNBits uint8
+ pad uint16
+}
+
+// Devfs is an SPI driver that works against the devfs.
+// You need to have loaded the "spidev" Linux module to use this driver.
+type Devfs struct {
+ // Dev is the device to be opened.
+ // Device name is usually in the /dev/spidev<bus>.<chip> format.
+ // Required.
+ Dev string
+
+ // Mode is the SPI mode. SPI mode is a combination of polarity and phases.
+ // CPOL is the high order bit, CPHA is the low order. Pre-computed mode
+ // values are Mode0, Mode1, Mode2 and Mode3. The value of the mode argument
+ // can be overridden by the device's driver.
+ // Required.
+ Mode Mode
+
+ // MaxSpeed is the max clock speed (Hz) and can be overridden by the device's driver.
+ // Required.
+ MaxSpeed int64
+}
+
+// Open opens the provided device with the specified options
+// and returns a connection.
+func (d *Devfs) Open() (driver.Conn, error) {
+ f, err := os.OpenFile(d.Dev, os.O_RDWR, os.ModeDevice)
+ if err != nil {
+ return nil, err
+ }
+ conn := &devfsConn{f: f}
+ if err := conn.Configure(driver.Mode, int(d.Mode)); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ if err := conn.Configure(driver.MaxSpeed, int(d.MaxSpeed)); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ return conn, nil
+}
+
+type devfsConn struct {
+ f *os.File
+ mode uint8
+ speed uint32
+ bits uint8
+ delay uint16
+ csChange uint8
+}
+
+func (c *devfsConn) Configure(k, v int) error {
+ switch k {
+ case driver.Mode:
+ m := uint8(v)
+ if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 1, 1), uintptr(unsafe.Pointer(&m))); err != nil {
+ return fmt.Errorf("error setting mode to %v: %v", m, err)
+ }
+ c.mode = m
+ case driver.Bits:
+ b := uint8(v)
+ if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 3, 1), uintptr(unsafe.Pointer(&b))); err != nil {
+ return fmt.Errorf("error setting bits per word to %v: %v", b, err)
+ }
+ c.bits = b
+ case driver.MaxSpeed:
+ s := uint32(v)
+ if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 4, 4), uintptr(unsafe.Pointer(&s))); err != nil {
+ return fmt.Errorf("error setting speed to %v: %v", s, err)
+ }
+ c.speed = s
+ case driver.Order:
+ o := uint8(v)
+ if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 2, 1), uintptr(unsafe.Pointer(&o))); err != nil {
+ return fmt.Errorf("error setting bit order to %v: %v", o, err)
+ }
+ case driver.Delay:
+ c.delay = uint16(v)
+ case driver.CSChange:
+ c.csChange = uint8(v)
+ default:
+ return fmt.Errorf("unknown key: %v", k)
+ }
+ return nil
+}
+
+func (c *devfsConn) Tx(w, r []byte) error {
+ if r == nil {
+ r = make([]byte, len(w))
+ }
+ // TODO(jbd): len(w) == len(r)?
+ // TODO(jbd): Allow nil w.
+ p := payload{
+ tx: uint64(uintptr(unsafe.Pointer(&w[0]))),
+ rx: uint64(uintptr(unsafe.Pointer(&r[0]))),
+ length: uint32(len(w)),
+ speed: c.speed,
+ delay: c.delay,
+ bits: c.bits,
+ csChange: c.csChange,
+ }
+ // TODO(jbd): Read from the device and fill rx.
+ return c.ioctl(msgRequestCode(1), uintptr(unsafe.Pointer(&p)))
+}
+
+func (c *devfsConn) Close() error {
+ return c.f.Close()
+}
+
+// requestCode returns the device specific request code for the specified direction,
+// type, number and size to be used in the ioctl call.
+func requestCode(dir, typ, nr, size uintptr) uintptr {
+ return (dir << devfs_DIRSHIFT) | (typ << devfs_TYPESHIFT) | (nr << devfs_NRSHIFT) | (size << devfs_SIZESHIFT)
+}
+
+// msgRequestCode returns the device specific value for the SPI
+// message payload to be used in the ioctl call.
+// n represents the number of messages.
+func msgRequestCode(n uint32) uintptr {
+ return uintptr(0x40006B00 + (n * 0x200000))
+}
+
+// ioctl makes an IOCTL on the open device file descriptor.
+func (c *devfsConn) ioctl(a1, a2 uintptr) error {
+ _, _, errno := syscall.Syscall(
+ syscall.SYS_IOCTL, c.f.Fd(), a1, a2,
+ )
+ if errno != 0 {
+ return syscall.Errno(errno)
+ }
+ return nil
+}
diff --git a/io/spi/devfs_nonlinux.go b/io/spi/devfs_nonlinux.go
new file mode 100644
index 0000000..e04d7ea
--- /dev/null
+++ b/io/spi/devfs_nonlinux.go
@@ -0,0 +1,39 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !linux
+
+package spi
+
+import (
+ "errors"
+
+ "golang.org/x/exp/io/spi/driver"
+)
+
+// Devfs is a no-implementation of an SPI driver that works against the devfs.
+// You need to have loaded the Linux "spidev" module to use this driver.
+type Devfs struct {
+ // Dev is the device to be opened.
+ // Device name is usually in the /dev/spidev<bus>.<chip> format.
+ // Required.
+ Dev string
+
+ // Mode is the SPI mode. SPI mode is a combination of polarity and phases.
+ // CPOL is the high order bit, CPHA is the low order. Pre-computed mode
+ // values are Mode0, Mode1, Mode2 and Mode3. The value of the mode argument
+ // can be overridden by the device's driver.
+ // Required.
+ Mode Mode
+
+ // MaxSpeed is the max clock speed (Hz) and can be overridden by the device's driver.
+ // Required.
+ MaxSpeed int64
+}
+
+// Open opens the provided device with the speicifed options
+// and returns a connection.
+func (d *Devfs) Open() (driver.Conn, error) {
+ return nil, errors.New("not implemented on this platform")
+}
diff --git a/io/spi/driver/driver.go b/io/spi/driver/driver.go
new file mode 100644
index 0000000..7634440
--- /dev/null
+++ b/io/spi/driver/driver.go
@@ -0,0 +1,50 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package driver contains interfaces to be implemented by various SPI implementations.
+package driver // import "golang.org/x/exp/io/spi/driver"
+
+const (
+ Mode = iota
+ Bits
+ MaxSpeed
+ Order
+ Delay
+ CSChange
+)
+
+// Opener is an interface to be implemented by the SPI driver to open
+// a connection to an SPI device.
+type Opener interface {
+ Open() (Conn, error)
+}
+
+// Conn is a connection to an SPI device.
+// TODO(jbd): Extend the interface to query configuration values.
+type Conn interface {
+ // Configure configures the SPI device.
+ //
+ // Available configuration keys are:
+ // - Mode, the SPI mode (valid values are 0, 1, 2 and 3).
+ // - Bits, bits per word (default is 8-bit per word).
+ // - Speed, the max clock speed (in Hz).
+ // - Order, bit order to be used in transfers. Zero value represents
+ // the MSB-first, non-zero values represent LSB-first encoding.
+ // - Delay, the pause time between frames (in usecs).
+ // Some SPI devices require a minimum amount of wait time after
+ // each frame write. If set, Delay amount of usecs are inserted after
+ // each write.
+ // - CSChange, whether to leave the device's chipselect active after a Tx.
+ //
+ // SPI devices can override these values.
+ Configure(k, v int) error
+
+ // Tx performs a SPI transaction: w is written if not nil, the result is
+ // put into r if not nil. len(w) must be equal to len(r), otherwise the
+ // driver should return an error.
+ Tx(w, r []byte) error
+
+ // Close frees the underlying resources and closes the connection.
+ Close() error
+}
diff --git a/io/spi/example_test.go b/io/spi/example_test.go
new file mode 100644
index 0000000..e75ec37
--- /dev/null
+++ b/io/spi/example_test.go
@@ -0,0 +1,38 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package spi_test
+
+import "golang.org/x/exp/io/spi"
+
+// Example illustrates a program that drives an APA-102 LED strip.
+func Example() {
+ dev, err := spi.Open(&spi.Devfs{
+ Dev: "/dev/spidev0.1",
+ Mode: spi.Mode3,
+ MaxSpeed: 500000,
+ })
+ if err != nil {
+ panic(err)
+ }
+ defer dev.Close()
+
+ if err := dev.Tx([]byte{
+ 0, 0, 0, 0,
+ 0xff, 200, 0, 200,
+ 0xff, 200, 0, 200,
+ 0xe0, 200, 0, 200,
+ 0xff, 200, 0, 200,
+ 0xff, 8, 50, 0,
+ 0xff, 200, 0, 0,
+ 0xff, 0, 0, 0,
+ 0xff, 200, 0, 200,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ }, nil); err != nil {
+ panic(err)
+ }
+}
diff --git a/io/spi/spi.go b/io/spi/spi.go
new file mode 100644
index 0000000..a675bd1
--- /dev/null
+++ b/io/spi/spi.go
@@ -0,0 +1,107 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package spi allows users to read from and write to an SPI device.
+//
+// # Deprecated
+//
+// This package is not maintained anymore. An actively supported cross-platform
+// alternative is https://periph.io/.
+package spi // import "golang.org/x/exp/io/spi"
+
+import (
+ "time"
+
+ "golang.org/x/exp/io/spi/driver"
+)
+
+// Mode represents the SPI mode number where clock parity (CPOL)
+// is the high order and clock edge (CPHA) is the low order bit.
+type Mode int
+
+const (
+ Mode0 = Mode(0)
+ Mode1 = Mode(1)
+ Mode2 = Mode(2)
+ Mode3 = Mode(3)
+)
+
+// Order is the bit justification to be used while transferring
+// words to the SPI device. MSB-first encoding is more popular
+// than LSB-first.
+type Order int
+
+const (
+ MSBFirst = Order(0)
+ LSBFirst = Order(1)
+)
+
+type Device struct {
+ conn driver.Conn
+}
+
+// SetMode sets the SPI mode. SPI mode is a combination of polarity and phases.
+// CPOL is the high order bit, CPHA is the low order. Pre-computed mode
+// values are Mode0, Mode1, Mode2 and Mode3.
+// The value can be changed by SPI device's driver.
+func (d *Device) SetMode(mode Mode) error {
+ return d.conn.Configure(driver.Mode, int(mode))
+}
+
+// SetMaxSpeed sets the maximum clock speed in Hz.
+// The value can be overridden by SPI device's driver.
+func (d *Device) SetMaxSpeed(speed int) error {
+ return d.conn.Configure(driver.MaxSpeed, speed)
+}
+
+// SetBitsPerWord sets how many bits it takes to represent a word, e.g. 8 represents 8-bit words.
+// The default is 8 bits per word.
+func (d *Device) SetBitsPerWord(bits int) error {
+ return d.conn.Configure(driver.Bits, bits)
+}
+
+// SetBitOrder sets the bit justification used to transfer SPI words.
+// Valid values are MSBFirst and LSBFirst.
+func (d *Device) SetBitOrder(o Order) error {
+ return d.conn.Configure(driver.Order, int(o))
+}
+
+// SetDelay sets the amount of pause will be added after each frame write.
+func (d *Device) SetDelay(t time.Duration) error {
+ return d.conn.Configure(driver.Delay, int(t.Nanoseconds()/1000))
+}
+
+// SetCSChange sets whether to leave the chipselect enabled after a Tx.
+func (d *Device) SetCSChange(leaveEnabled bool) error {
+ v := 0
+ if leaveEnabled {
+ v = 1
+ }
+ return d.conn.Configure(driver.CSChange, v)
+}
+
+// Tx performs a duplex transmission to write w to the SPI device
+// and read len(r) bytes to r.
+// User should not mutate the w and r until this call returns.
+func (d *Device) Tx(w, r []byte) error {
+ // TODO(jbd): Allow nil w.
+ return d.conn.Tx(w, r)
+}
+
+// Open opens a device with the specified bus and chip select
+// by using the given driver. If a nil driver is provided,
+// the default driver (devfs) is used.
+
+func Open(o driver.Opener) (*Device, error) {
+ conn, err := o.Open()
+ if err != nil {
+ return nil, err
+ }
+ return &Device{conn: conn}, nil
+}
+
+// Close closes the SPI device and releases the related resources.
+func (d *Device) Close() error {
+ return d.conn.Close()
+}
diff --git a/jsonrpc2/conn.go b/jsonrpc2/conn.go
new file mode 100644
index 0000000..f0e527c
--- /dev/null
+++ b/jsonrpc2/conn.go
@@ -0,0 +1,476 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "sync/atomic"
+
+ "golang.org/x/exp/event"
+ errors "golang.org/x/xerrors"
+)
+
+// Binder builds a connection configuration.
+// This may be used in servers to generate a new configuration per connection.
+// ConnectionOptions itself implements Binder returning itself unmodified, to
+// allow for the simple cases where no per connection information is needed.
+type Binder interface {
+ // Bind is invoked when creating a new connection.
+ // The connection is not ready to use when Bind is called.
+ Bind(context.Context, *Connection) (ConnectionOptions, error)
+}
+
+// ConnectionOptions holds the options for new connections.
+type ConnectionOptions struct {
+ // Framer allows control over the message framing and encoding.
+ // If nil, HeaderFramer will be used.
+ Framer Framer
+ // Preempter allows registration of a pre-queue message handler.
+ // If nil, no messages will be preempted.
+ Preempter Preempter
+ // Handler is used as the queued message handler for inbound messages.
+ // If nil, all responses will be ErrNotHandled.
+ Handler Handler
+}
+
+// Connection manages the jsonrpc2 protocol, connecting responses back to their
+// calls.
+// Connection is bidirectional; it does not have a designated server or client
+// end.
+type Connection struct {
+ seq int64 // must only be accessed using atomic operations
+ closer io.Closer
+ writerBox chan Writer
+ outgoingBox chan map[ID]chan<- *Response
+ incomingBox chan map[ID]*incoming
+ async async
+}
+
+type AsyncCall struct {
+ id ID
+ response chan *Response // the channel a response will be delivered on
+ resultBox chan asyncResult
+ ctx context.Context
+}
+
+type asyncResult struct {
+ result []byte
+ err error
+}
+
+// incoming is used to track an incoming request as it is being handled
+type incoming struct {
+ request *Request // the request being processed
+ baseCtx context.Context // a base context for the message processing
+ handleCtx context.Context // the context for handling the message, child of baseCtx
+ cancel func() // a function that cancels the handling context
+}
+
+// Bind returns the options unmodified.
+func (o ConnectionOptions) Bind(context.Context, *Connection) (ConnectionOptions, error) {
+ return o, nil
+}
+
+// newConnection creates a new connection and runs it.
+// This is used by the Dial and Serve functions to build the actual connection.
+func newConnection(ctx context.Context, rwc io.ReadWriteCloser, binder Binder) (*Connection, error) {
+ c := &Connection{
+ closer: rwc,
+ writerBox: make(chan Writer, 1),
+ outgoingBox: make(chan map[ID]chan<- *Response, 1),
+ incomingBox: make(chan map[ID]*incoming, 1),
+ }
+ c.async.init()
+
+ options, err := binder.Bind(ctx, c)
+ if err != nil {
+ return nil, err
+ }
+ if options.Framer == nil {
+ options.Framer = HeaderFramer()
+ }
+ if options.Preempter == nil {
+ options.Preempter = defaultHandler{}
+ }
+ if options.Handler == nil {
+ options.Handler = defaultHandler{}
+ }
+ c.outgoingBox <- make(map[ID]chan<- *Response)
+ c.incomingBox <- make(map[ID]*incoming)
+ // the goroutines started here will continue until the underlying stream is closed
+ reader := options.Framer.Reader(rwc)
+ readToQueue := make(chan *incoming)
+ queueToDeliver := make(chan *incoming)
+ go c.readIncoming(ctx, reader, readToQueue)
+ go c.manageQueue(ctx, options.Preempter, readToQueue, queueToDeliver)
+ go c.deliverMessages(ctx, options.Handler, queueToDeliver)
+ // releaseing the writer must be the last thing we do in case any requests
+ // are blocked waiting for the connection to be ready
+ c.writerBox <- options.Framer.Writer(rwc)
+ return c, nil
+}
+
+// Notify invokes the target method but does not wait for a response.
+// The params will be marshaled to JSON before sending over the wire, and will
+// be handed to the method invoked.
+func (c *Connection) Notify(ctx context.Context, method string, params interface{}) error {
+ notify, err := NewNotification(method, params)
+ if err != nil {
+ return errors.Errorf("marshaling notify parameters: %v", err)
+ }
+ ctx = event.Start(ctx, method, RPCDirection(Outbound))
+ Started.Record(ctx, 1, Method(method))
+ var errLabel event.Label
+ if err = c.write(ctx, notify); err != nil {
+ errLabel = event.Value("error", err)
+ }
+ Finished.Record(ctx, 1, errLabel)
+ event.End(ctx)
+ return err
+}
+
+// Call invokes the target method and returns an object that can be used to await the response.
+// The params will be marshaled to JSON before sending over the wire, and will
+// be handed to the method invoked.
+// You do not have to wait for the response, it can just be ignored if not needed.
+// If sending the call failed, the response will be ready and have the error in it.
+func (c *Connection) Call(ctx context.Context, method string, params interface{}) *AsyncCall {
+ result := &AsyncCall{
+ id: Int64ID(atomic.AddInt64(&c.seq, 1)),
+ resultBox: make(chan asyncResult, 1),
+ }
+ // TODO: rewrite this using the new target/prototype stuff
+ ctx = event.Start(ctx, method,
+ Method(method), RPCDirection(Outbound), RPCID(fmt.Sprintf("%q", result.id)))
+ Started.Record(ctx, 1, Method(method))
+ result.ctx = ctx
+ // generate a new request identifier
+ call, err := NewCall(result.id, method, params)
+ if err != nil {
+ // set the result to failed
+ result.resultBox <- asyncResult{err: errors.Errorf("marshaling call parameters: %w", err)}
+ return result
+ }
+ // We have to add ourselves to the pending map before we send, otherwise we
+ // are racing the response.
+ // rchan is buffered in case the response arrives without a listener.
+ result.response = make(chan *Response, 1)
+ pending := <-c.outgoingBox
+ pending[result.id] = result.response
+ c.outgoingBox <- pending
+ // now we are ready to send
+ if err := c.write(ctx, call); err != nil {
+ // sending failed, we will never get a response, so deliver a fake one
+ r, _ := NewResponse(result.id, nil, err)
+ c.incomingResponse(r)
+ }
+ return result
+}
+
+// ID used for this call.
+// This can be used to cancel the call if needed.
+func (a *AsyncCall) ID() ID { return a.id }
+
+// IsReady can be used to check if the result is already prepared.
+// This is guaranteed to return true on a result for which Await has already
+// returned, or a call that failed to send in the first place.
+func (a *AsyncCall) IsReady() bool {
+ select {
+ case r := <-a.resultBox:
+ a.resultBox <- r
+ return true
+ default:
+ return false
+ }
+}
+
+// Await the results of a Call.
+// The response will be unmarshaled from JSON into the result.
+func (a *AsyncCall) Await(ctx context.Context, result interface{}) error {
+ status := "NONE"
+ defer event.End(a.ctx, StatusCode(status))
+ var r asyncResult
+ select {
+ case response := <-a.response:
+ // response just arrived, prepare the result
+ switch {
+ case response.Error != nil:
+ r.err = response.Error
+ status = "ERROR"
+ default:
+ r.result = response.Result
+ status = "OK"
+ }
+ case r = <-a.resultBox:
+ // result already available
+ case <-ctx.Done():
+ status = "CANCELLED"
+ return ctx.Err()
+ }
+ // refill the box for the next caller
+ a.resultBox <- r
+ // and unpack the result
+ if r.err != nil {
+ return r.err
+ }
+ if result == nil || len(r.result) == 0 {
+ return nil
+ }
+ return json.Unmarshal(r.result, result)
+}
+
+// Respond deliverers a response to an incoming Call.
+// It is an error to not call this exactly once for any message for which a
+// handler has previously returned ErrAsyncResponse. It is also an error to
+// call this for any other message.
+func (c *Connection) Respond(id ID, result interface{}, rerr error) error {
+ pending := <-c.incomingBox
+ defer func() { c.incomingBox <- pending }()
+ entry, found := pending[id]
+ if !found {
+ return nil
+ }
+ delete(pending, id)
+ return c.respond(entry, result, rerr)
+}
+
+// Cancel is used to cancel an inbound message by ID, it does not cancel
+// outgoing messages.
+// This is only used inside a message handler that is layering a
+// cancellation protocol on top of JSON RPC 2.
+// It will not complain if the ID is not a currently active message, and it will
+// not cause any messages that have not arrived yet with that ID to be
+// cancelled.
+func (c *Connection) Cancel(id ID) {
+ pending := <-c.incomingBox
+ defer func() { c.incomingBox <- pending }()
+ if entry, found := pending[id]; found && entry.cancel != nil {
+ entry.cancel()
+ entry.cancel = nil
+ }
+}
+
+// Wait blocks until the connection is fully closed, but does not close it.
+func (c *Connection) Wait() error {
+ return c.async.wait()
+}
+
+// Close can be used to close the underlying stream, and then wait for the connection to
+// fully shut down.
+// This does not cancel in flight requests, but waits for them to gracefully complete.
+func (c *Connection) Close() error {
+ // close the underlying stream
+ if err := c.closer.Close(); err != nil && !isClosingError(err) {
+ return err
+ }
+ // and then wait for it to cause the connection to close
+ if err := c.Wait(); err != nil && !isClosingError(err) {
+ return err
+ }
+ return nil
+}
+
+// readIncoming collects inbound messages from the reader and delivers them, either responding
+// to outgoing calls or feeding requests to the queue.
+func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue chan<- *incoming) {
+ defer close(toQueue)
+ for {
+ // get the next message
+ // no lock is needed, this is the only reader
+ msg, n, err := reader.Read(ctx)
+ if err != nil {
+ // The stream failed, we cannot continue
+ c.async.setError(err)
+ return
+ }
+ switch msg := msg.(type) {
+ case *Request:
+ entry := &incoming{
+ request: msg,
+ }
+ // add a span to the context for this request
+ var idLabel event.Label
+ if msg.IsCall() {
+ idLabel = RPCID(fmt.Sprintf("%q", msg.ID))
+ }
+ entry.baseCtx = event.Start(ctx, msg.Method,
+ Method(msg.Method), RPCDirection(Inbound), idLabel)
+ Started.Record(entry.baseCtx, 1, Method(msg.Method))
+ ReceivedBytes.Record(entry.baseCtx, n, Method(msg.Method))
+ // in theory notifications cannot be cancelled, but we build them a cancel context anyway
+ entry.handleCtx, entry.cancel = context.WithCancel(entry.baseCtx)
+ // if the request is a call, add it to the incoming map so it can be
+ // cancelled by id
+ if msg.IsCall() {
+ pending := <-c.incomingBox
+ pending[msg.ID] = entry
+ c.incomingBox <- pending
+ }
+ // send the message to the incoming queue
+ toQueue <- entry
+ case *Response:
+ // If method is not set, this should be a response, in which case we must
+ // have an id to send the response back to the caller.
+ c.incomingResponse(msg)
+ }
+ }
+}
+
+func (c *Connection) incomingResponse(msg *Response) {
+ pending := <-c.outgoingBox
+ response, ok := pending[msg.ID]
+ if ok {
+ delete(pending, msg.ID)
+ }
+ c.outgoingBox <- pending
+ if response != nil {
+ response <- msg
+ }
+}
+
+// manageQueue reads incoming requests, attempts to process them with the preempter, or queue them
+// up for normal handling.
+func (c *Connection) manageQueue(ctx context.Context, preempter Preempter, fromRead <-chan *incoming, toDeliver chan<- *incoming) {
+ defer close(toDeliver)
+ q := []*incoming{}
+ ok := true
+ for {
+ var nextReq *incoming
+ if len(q) == 0 {
+ // no messages in the queue
+ // if we were closing, then we are done
+ if !ok {
+ return
+ }
+ // not closing, but nothing in the queue, so just block waiting for a read
+ nextReq, ok = <-fromRead
+ } else {
+ // we have a non empty queue, so pick whichever of reading or delivering
+ // that we can make progress on
+ select {
+ case nextReq, ok = <-fromRead:
+ case toDeliver <- q[0]:
+ // TODO: this causes a lot of shuffling, should we use a growing ring buffer? compaction?
+ q = q[1:]
+ }
+ }
+ if nextReq != nil {
+ // TODO: should we allow to limit the queue size?
+ var result interface{}
+ rerr := nextReq.handleCtx.Err()
+ if rerr == nil {
+ // only preempt if not already cancelled
+ result, rerr = preempter.Preempt(nextReq.handleCtx, nextReq.request)
+ }
+ switch {
+ case rerr == ErrNotHandled:
+ // message not handled, add it to the queue for the main handler
+ q = append(q, nextReq)
+ case rerr == ErrAsyncResponse:
+ // message handled but the response will come later
+ default:
+ // anything else means the message is fully handled
+ c.reply(nextReq, result, rerr)
+ }
+ }
+ }
+}
+
+func (c *Connection) deliverMessages(ctx context.Context, handler Handler, fromQueue <-chan *incoming) {
+ defer c.async.done()
+ for entry := range fromQueue {
+ // cancel any messages in the queue that we have a pending cancel for
+ var result interface{}
+ rerr := entry.handleCtx.Err()
+ if rerr == nil {
+ // only deliver if not already cancelled
+ result, rerr = handler.Handle(entry.handleCtx, entry.request)
+ }
+ switch {
+ case rerr == ErrNotHandled:
+ // message not handled, report it back to the caller as an error
+ c.reply(entry, nil, errors.Errorf("%w: %q", ErrMethodNotFound, entry.request.Method))
+ case rerr == ErrAsyncResponse:
+ // message handled but the response will come later
+ default:
+ c.reply(entry, result, rerr)
+ }
+ }
+}
+
+// reply is used to reply to an incoming request that has just been handled
+func (c *Connection) reply(entry *incoming, result interface{}, rerr error) {
+ if entry.request.IsCall() {
+ // we have a call finishing, remove it from the incoming map
+ pending := <-c.incomingBox
+ defer func() { c.incomingBox <- pending }()
+ delete(pending, entry.request.ID)
+ }
+ if err := c.respond(entry, result, rerr); err != nil {
+ // no way to propagate this error
+ // TODO: should we do more than just log it?
+ event.Error(entry.baseCtx, "jsonrpc2 message delivery failed", err)
+ }
+}
+
+// respond sends a response.
+// This is the code shared between reply and SendResponse.
+func (c *Connection) respond(entry *incoming, result interface{}, rerr error) error {
+ var err error
+ if entry.request.IsCall() {
+ // send the response
+ if result == nil && rerr == nil {
+ // call with no response, send an error anyway
+ rerr = errors.Errorf("%w: %q produced no response", ErrInternal, entry.request.Method)
+ }
+ var response *Response
+ response, err = NewResponse(entry.request.ID, result, rerr)
+ if err == nil {
+ // we write the response with the base context, in case the message was cancelled
+ err = c.write(entry.baseCtx, response)
+ }
+ } else {
+ switch {
+ case rerr != nil:
+ // notification failed
+ err = errors.Errorf("%w: %q notification failed: %v", ErrInternal, entry.request.Method, rerr)
+ rerr = nil
+ case result != nil:
+ // notification produced a response, which is an error
+ err = errors.Errorf("%w: %q produced unwanted response", ErrInternal, entry.request.Method)
+ default:
+ // normal notification finish
+ }
+ }
+ var status string
+ switch {
+ case rerr != nil || err != nil:
+ status = "ERROR"
+ default:
+ status = "OK"
+ }
+ // and just to be clean, invoke and clear the cancel if needed
+ if entry.cancel != nil {
+ entry.cancel()
+ entry.cancel = nil
+ }
+ // mark the entire request processing as done
+ event.End(entry.baseCtx, StatusCode(status))
+ return err
+}
+
+// write is used by all things that write outgoing messages, including replies.
+// it makes sure that writes are atomic
+func (c *Connection) write(ctx context.Context, msg Message) error {
+ writer := <-c.writerBox
+ defer func() { c.writerBox <- writer }()
+ n, err := writer.Write(ctx, msg)
+ // TODO: get a method label in here somehow.
+ SentBytes.Record(ctx, n)
+ return err
+}
diff --git a/jsonrpc2/defs.go b/jsonrpc2/defs.go
new file mode 100644
index 0000000..e3677de
--- /dev/null
+++ b/jsonrpc2/defs.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "golang.org/x/exp/event"
+)
+
+func Method(v string) event.Label { return event.String("method", v) }
+func RPCID(v string) event.Label { return event.String("id", v) }
+func RPCDirection(v string) event.Label { return event.String("direction", v) }
+func StatusCode(v string) event.Label { return event.String("status.code", v) }
+
+var (
+ Started = event.NewCounter("started", &event.MetricOptions{Description: "Count of started RPCs."})
+ Finished = event.NewCounter("finished", &event.MetricOptions{Description: "Count of finished RPCs (includes error)."})
+ ReceivedBytes = event.NewIntDistribution("received_bytes", &event.MetricOptions{
+ Description: "Bytes received.",
+ Unit: event.UnitBytes,
+ })
+ SentBytes = event.NewIntDistribution("sent_bytes", &event.MetricOptions{
+ Description: "Bytes sent.",
+ Unit: event.UnitBytes,
+ })
+ Latency = event.NewDuration("latency", &event.MetricOptions{
+ Description: "Elapsed time of an RPC.",
+ Unit: event.UnitMilliseconds,
+ })
+)
+
+const (
+ Inbound = "in"
+ Outbound = "out"
+)
diff --git a/jsonrpc2/frame.go b/jsonrpc2/frame.go
new file mode 100644
index 0000000..634717c
--- /dev/null
+++ b/jsonrpc2/frame.go
@@ -0,0 +1,179 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+
+ errors "golang.org/x/xerrors"
+)
+
+// Reader abstracts the transport mechanics from the JSON RPC protocol.
+// A Conn reads messages from the reader it was provided on construction,
+// and assumes that each call to Read fully transfers a single message,
+// or returns an error.
+// A reader is not safe for concurrent use, it is expected it will be used by
+// a single Conn in a safe manner.
+type Reader interface {
+ // Read gets the next message from the stream.
+ Read(context.Context) (Message, int64, error)
+}
+
+// Writer abstracts the transport mechanics from the JSON RPC protocol.
+// A Conn writes messages using the writer it was provided on construction,
+// and assumes that each call to Write fully transfers a single message,
+// or returns an error.
+// A writer is not safe for concurrent use, it is expected it will be used by
+// a single Conn in a safe manner.
+type Writer interface {
+ // Write sends a message to the stream.
+ Write(context.Context, Message) (int64, error)
+}
+
+// Framer wraps low level byte readers and writers into jsonrpc2 message
+// readers and writers.
+// It is responsible for the framing and encoding of messages into wire form.
+type Framer interface {
+ // Reader wraps a byte reader into a message reader.
+ Reader(rw io.Reader) Reader
+ // Writer wraps a byte writer into a message writer.
+ Writer(rw io.Writer) Writer
+}
+
+// RawFramer returns a new Framer.
+// The messages are sent with no wrapping, and rely on json decode consistency
+// to determine message boundaries.
+func RawFramer() Framer { return rawFramer{} }
+
+type rawFramer struct{}
+type rawReader struct{ in *json.Decoder }
+type rawWriter struct{ out io.Writer }
+
+func (rawFramer) Reader(rw io.Reader) Reader {
+ return &rawReader{in: json.NewDecoder(rw)}
+}
+
+func (rawFramer) Writer(rw io.Writer) Writer {
+ return &rawWriter{out: rw}
+}
+
+func (r *rawReader) Read(ctx context.Context) (Message, int64, error) {
+ select {
+ case <-ctx.Done():
+ return nil, 0, ctx.Err()
+ default:
+ }
+ var raw json.RawMessage
+ if err := r.in.Decode(&raw); err != nil {
+ return nil, 0, err
+ }
+ msg, err := DecodeMessage(raw)
+ return msg, int64(len(raw)), err
+}
+
+func (w *rawWriter) Write(ctx context.Context, msg Message) (int64, error) {
+ select {
+ case <-ctx.Done():
+ return 0, ctx.Err()
+ default:
+ }
+ data, err := EncodeMessage(msg)
+ if err != nil {
+ return 0, errors.Errorf("marshaling message: %v", err)
+ }
+ n, err := w.out.Write(data)
+ return int64(n), err
+}
+
+// HeaderFramer returns a new Framer.
+// The messages are sent with HTTP content length and MIME type headers.
+// This is the format used by LSP and others.
+func HeaderFramer() Framer { return headerFramer{} }
+
+type headerFramer struct{}
+type headerReader struct{ in *bufio.Reader }
+type headerWriter struct{ out io.Writer }
+
+func (headerFramer) Reader(rw io.Reader) Reader {
+ return &headerReader{in: bufio.NewReader(rw)}
+}
+
+func (headerFramer) Writer(rw io.Writer) Writer {
+ return &headerWriter{out: rw}
+}
+
+func (r *headerReader) Read(ctx context.Context) (Message, int64, error) {
+ select {
+ case <-ctx.Done():
+ return nil, 0, ctx.Err()
+ default:
+ }
+ var total, length int64
+ // read the header, stop on the first empty line
+ for {
+ line, err := r.in.ReadString('\n')
+ total += int64(len(line))
+ if err != nil {
+ return nil, total, errors.Errorf("failed reading header line: %w", err)
+ }
+ line = strings.TrimSpace(line)
+ // check we have a header line
+ if line == "" {
+ break
+ }
+ colon := strings.IndexRune(line, ':')
+ if colon < 0 {
+ return nil, total, errors.Errorf("invalid header line %q", line)
+ }
+ name, value := line[:colon], strings.TrimSpace(line[colon+1:])
+ switch name {
+ case "Content-Length":
+ if length, err = strconv.ParseInt(value, 10, 32); err != nil {
+ return nil, total, errors.Errorf("failed parsing Content-Length: %v", value)
+ }
+ if length <= 0 {
+ return nil, total, errors.Errorf("invalid Content-Length: %v", length)
+ }
+ default:
+ // ignoring unknown headers
+ }
+ }
+ if length == 0 {
+ return nil, total, errors.Errorf("missing Content-Length header")
+ }
+ data := make([]byte, length)
+ n, err := io.ReadFull(r.in, data)
+ total += int64(n)
+ if err != nil {
+ return nil, total, err
+ }
+ msg, err := DecodeMessage(data)
+ return msg, total, err
+}
+
+func (w *headerWriter) Write(ctx context.Context, msg Message) (int64, error) {
+ select {
+ case <-ctx.Done():
+ return 0, ctx.Err()
+ default:
+ }
+ data, err := EncodeMessage(msg)
+ if err != nil {
+ return 0, errors.Errorf("marshaling message: %v", err)
+ }
+ n, err := fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data))
+ total := int64(n)
+ if err == nil {
+ n, err = w.out.Write(data)
+ total += int64(n)
+ }
+ return total, err
+}
diff --git a/jsonrpc2/go.mod b/jsonrpc2/go.mod
new file mode 100644
index 0000000..7951e8b
--- /dev/null
+++ b/jsonrpc2/go.mod
@@ -0,0 +1,10 @@
+module golang.org/x/exp/jsonrpc2
+
+go 1.18
+
+require (
+ golang.org/x/exp/event v0.0.0-20220217172124-1812c5b45e43
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
+)
+
+require github.com/google/go-cmp v0.5.7 // indirect
diff --git a/jsonrpc2/go.sum b/jsonrpc2/go.sum
new file mode 100644
index 0000000..cc0969f
--- /dev/null
+++ b/jsonrpc2/go.sum
@@ -0,0 +1,7 @@
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+golang.org/x/exp/event v0.0.0-20220217172124-1812c5b45e43 h1:Yn6OLQDombmcne/0Jf2GiY4qPS5ML2W4KYFyx2uYxGY=
+golang.org/x/exp/event v0.0.0-20220217172124-1812c5b45e43/go.mod h1:AVlZHjhWbW/3yOcmKMtJiObwBPJajBlUpQXRijFNrNc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/jsonrpc2/internal/stack/gostacks/gostacks.go b/jsonrpc2/internal/stack/gostacks/gostacks.go
new file mode 100644
index 0000000..5fa1974
--- /dev/null
+++ b/jsonrpc2/internal/stack/gostacks/gostacks.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The gostacks command processes stdin looking for things that look like
+// stack traces and simplifying them to make the log more readable.
+// It collates stack traces that have the same path as well as simplifying the
+// individual lines of the trace.
+// The processed log is printed to stdout.
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "golang.org/x/exp/jsonrpc2/internal/stack"
+)
+
+func main() {
+ if err := stack.Process(os.Stdout, os.Stdin); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ }
+}
diff --git a/jsonrpc2/internal/stack/parse.go b/jsonrpc2/internal/stack/parse.go
new file mode 100644
index 0000000..e01da8f
--- /dev/null
+++ b/jsonrpc2/internal/stack/parse.go
@@ -0,0 +1,175 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stack
+
+import (
+ "bufio"
+ "errors"
+ "io"
+ "regexp"
+ "strconv"
+)
+
+var (
+ reBlank = regexp.MustCompile(`^\s*$`)
+ reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`)
+ reCall = regexp.MustCompile(`^\s*` +
+ `(created by )?` + //marker
+ `(([\w/.]+/)?[\w]+)\.` + //package
+ `(\(([^:.)]*)\)\.)?` + //optional type
+ `([\w\.]+)` + //function
+ `(\(.*\))?` + // args
+ `\s*$`)
+ rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`)
+
+ errBreakParse = errors.New("break parse")
+)
+
+// Scanner splits an input stream into lines in a way that is consumable by
+// the parser.
+type Scanner struct {
+ lines *bufio.Scanner
+ done bool
+}
+
+// NewScanner creates a scanner on top of a reader.
+func NewScanner(r io.Reader) *Scanner {
+ s := &Scanner{
+ lines: bufio.NewScanner(r),
+ }
+ s.Skip() // prefill
+ return s
+}
+
+// Peek returns the next line without consuming it.
+func (s *Scanner) Peek() string {
+ if s.done {
+ return ""
+ }
+ return s.lines.Text()
+}
+
+// Skip consumes the next line without looking at it.
+// Normally used after it has already been looked at using Peek.
+func (s *Scanner) Skip() {
+ if !s.lines.Scan() {
+ s.done = true
+ }
+}
+
+// Next consumes and returns the next line.
+func (s *Scanner) Next() string {
+ line := s.Peek()
+ s.Skip()
+ return line
+}
+
+// Done returns true if the scanner has reached the end of the underlying
+// stream.
+func (s *Scanner) Done() bool {
+ return s.done
+}
+
+// Err returns true if the scanner has reached the end of the underlying
+// stream.
+func (s *Scanner) Err() error {
+ return s.lines.Err()
+}
+
+// Match returns the submatchs of the regular expression against the next line.
+// If it matched the line is also consumed.
+func (s *Scanner) Match(re *regexp.Regexp) []string {
+ if s.done {
+ return nil
+ }
+ match := re.FindStringSubmatch(s.Peek())
+ if match != nil {
+ s.Skip()
+ }
+ return match
+}
+
+// SkipBlank skips any number of pure whitespace lines.
+func (s *Scanner) SkipBlank() {
+ for !s.done {
+ line := s.Peek()
+ if len(line) != 0 && !reBlank.MatchString(line) {
+ return
+ }
+ s.Skip()
+ }
+}
+
+// Parse the current contiguous block of goroutine stack traces until the
+// scanned content no longer matches.
+func Parse(scanner *Scanner) (Dump, error) {
+ dump := Dump{}
+ for {
+ gr, ok := parseGoroutine(scanner)
+ if !ok {
+ return dump, nil
+ }
+ dump = append(dump, gr)
+ }
+}
+
+func parseGoroutine(scanner *Scanner) (Goroutine, bool) {
+ match := scanner.Match(reGoroutine)
+ if match == nil {
+ return Goroutine{}, false
+ }
+ id, _ := strconv.ParseInt(match[1], 0, 32)
+ gr := Goroutine{
+ ID: int(id),
+ State: match[2],
+ }
+ for {
+ frame, ok := parseFrame(scanner)
+ if !ok {
+ scanner.SkipBlank()
+ return gr, true
+ }
+ if frame.Position.Filename != "" {
+ gr.Stack = append(gr.Stack, frame)
+ }
+ }
+}
+
+func parseFrame(scanner *Scanner) (Frame, bool) {
+ fun, ok := parseFunction(scanner)
+ if !ok {
+ return Frame{}, false
+ }
+ frame := Frame{
+ Function: fun,
+ }
+ frame.Position, ok = parsePosition(scanner)
+ // if ok is false, then this is a broken state.
+ // we got the func but not the file that must follow
+ // the consumed line can be recovered from the frame
+ //TODO: push back the fun raw
+ return frame, ok
+}
+
+func parseFunction(scanner *Scanner) (Function, bool) {
+ match := scanner.Match(reCall)
+ if match == nil {
+ return Function{}, false
+ }
+ return Function{
+ Package: match[2],
+ Type: match[5],
+ Name: match[6],
+ }, true
+}
+
+func parsePosition(scanner *Scanner) (Position, bool) {
+ match := scanner.Match(rePos)
+ if match == nil {
+ return Position{}, false
+ }
+ line, _ := strconv.ParseInt(match[2], 0, 32)
+ return Position{Filename: match[1], Line: int(line)}, true
+}
diff --git a/jsonrpc2/internal/stack/process.go b/jsonrpc2/internal/stack/process.go
new file mode 100644
index 0000000..8812de9
--- /dev/null
+++ b/jsonrpc2/internal/stack/process.go
@@ -0,0 +1,112 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stack
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "runtime"
+ "sort"
+)
+
+// Capture get the current stack traces from the runtime.
+func Capture() Dump {
+ buf := make([]byte, 2<<20)
+ buf = buf[:runtime.Stack(buf, true)]
+ scanner := NewScanner(bytes.NewReader(buf))
+ dump, _ := Parse(scanner)
+ return dump
+}
+
+// Summarize a dump for easier consumption.
+// This collates goroutines with equivalent stacks.
+func Summarize(dump Dump) Summary {
+ s := Summary{
+ Total: len(dump),
+ }
+ for _, gr := range dump {
+ s.addGoroutine(gr)
+ }
+ return s
+}
+
+// Process and input stream to an output stream, summarizing any stacks that
+// are detected in place.
+func Process(out io.Writer, in io.Reader) error {
+ scanner := NewScanner(in)
+ for {
+ dump, err := Parse(scanner)
+ summary := Summarize(dump)
+ switch {
+ case len(dump) > 0:
+ fmt.Fprintf(out, "%+v\n\n", summary)
+ case err != nil:
+ return err
+ case scanner.Done():
+ return scanner.Err()
+ default:
+ // must have been a line that is not part of a dump
+ fmt.Fprintln(out, scanner.Next())
+ }
+ }
+}
+
+// Diff calculates the delta between two dumps.
+func Diff(before, after Dump) Delta {
+ result := Delta{}
+ processed := make(map[int]bool)
+ for _, gr := range before {
+ processed[gr.ID] = false
+ }
+ for _, gr := range after {
+ if _, found := processed[gr.ID]; found {
+ result.Shared = append(result.Shared, gr)
+ } else {
+ result.After = append(result.After, gr)
+ }
+ processed[gr.ID] = true
+ }
+ for _, gr := range before {
+ if done := processed[gr.ID]; !done {
+ result.Before = append(result.Before, gr)
+ }
+ }
+ return result
+}
+
+// TODO: do we want to allow contraction of stacks before comparison?
+func (s *Summary) addGoroutine(gr Goroutine) {
+ index := sort.Search(len(s.Calls), func(i int) bool {
+ return !s.Calls[i].Stack.less(gr.Stack)
+ })
+ if index >= len(s.Calls) || !s.Calls[index].Stack.equal(gr.Stack) {
+ // insert new stack, first increase the length
+ s.Calls = append(s.Calls, Call{})
+ // move the top part upward to make space
+ copy(s.Calls[index+1:], s.Calls[index:])
+ // insert the new call
+ s.Calls[index] = Call{
+ Stack: gr.Stack,
+ }
+ }
+ // merge the goroutine into the matched call
+ s.Calls[index].merge(gr)
+}
+
+// TODO: do we want other grouping strategies?
+func (c *Call) merge(gr Goroutine) {
+ for i := range c.Groups {
+ canditate := &c.Groups[i]
+ if canditate.State == gr.State {
+ canditate.Goroutines = append(canditate.Goroutines, gr)
+ return
+ }
+ }
+ c.Groups = append(c.Groups, Group{
+ State: gr.State,
+ Goroutines: []Goroutine{gr},
+ })
+}
diff --git a/jsonrpc2/internal/stack/stack.go b/jsonrpc2/internal/stack/stack.go
new file mode 100644
index 0000000..479301a
--- /dev/null
+++ b/jsonrpc2/internal/stack/stack.go
@@ -0,0 +1,170 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package stack provides support for parsing standard goroutine stack traces.
+package stack
+
+import (
+ "fmt"
+ "text/tabwriter"
+)
+
+// Dump is a raw set of goroutines and their stacks.
+type Dump []Goroutine
+
+// Goroutine is a single parsed goroutine dump.
+type Goroutine struct {
+ State string // state that the goroutine is in.
+ ID int // id of the goroutine.
+ Stack Stack // call frames that make up the stack
+}
+
+// Stack is a set of frames in a callstack.
+type Stack []Frame
+
+// Frame is a point in a call stack.
+type Frame struct {
+ Function Function
+ Position Position
+}
+
+// Function is the function called at a frame.
+type Function struct {
+ Package string // package name of function if known
+ Type string // if set function is a method of this type
+ Name string // function name of the frame
+}
+
+// Position is the file position for a frame.
+type Position struct {
+ Filename string // source filename
+ Line int // line number within file
+}
+
+// Summary is a set of stacks processed and collated into Calls.
+type Summary struct {
+ Total int // the total count of goroutines in the summary
+ Calls []Call // the collated stack traces
+}
+
+// Call is set of goroutines that all share the same callstack.
+// They will be grouped by state.
+type Call struct {
+ Stack Stack // the shared callstack information
+ Groups []Group // the sets of goroutines with the same state
+}
+
+// Group is a set of goroutines with the same stack that are in the same state.
+type Group struct {
+ State string // the shared state of the goroutines
+ Goroutines []Goroutine // the set of goroutines in this group
+}
+
+// Delta represents the difference between two stack dumps.
+type Delta struct {
+ Before Dump // The goroutines that were only in the before set.
+ Shared Dump // The goroutines that were in both sets.
+ After Dump // The goroutines that were only in the after set.
+}
+
+func (s Stack) equal(other Stack) bool {
+ if len(s) != len(other) {
+ return false
+ }
+ for i, frame := range s {
+ if !frame.equal(other[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+func (s Stack) less(other Stack) bool {
+ for i, frame := range s {
+ if i >= len(other) {
+ return false
+ }
+ if frame.less(other[i]) {
+ return true
+ }
+ if !frame.equal(other[i]) {
+ return false
+ }
+ }
+ return len(s) < len(other)
+}
+
+func (f Frame) equal(other Frame) bool {
+ return f.Position.equal(other.Position)
+}
+
+func (f Frame) less(other Frame) bool {
+ return f.Position.less(other.Position)
+}
+
+func (p Position) equal(other Position) bool {
+ return p.Filename == other.Filename && p.Line == other.Line
+}
+
+func (p Position) less(other Position) bool {
+ if p.Filename < other.Filename {
+ return true
+ }
+ if p.Filename > other.Filename {
+ return false
+ }
+ return p.Line < other.Line
+}
+
+func (s Summary) Format(w fmt.State, r rune) {
+ tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
+ for i, c := range s.Calls {
+ if i > 0 {
+ fmt.Fprintf(tw, "\n\n")
+ tw.Flush()
+ }
+ fmt.Fprint(tw, c)
+ }
+ tw.Flush()
+ if s.Total > 0 && w.Flag('+') {
+ fmt.Fprintf(w, "\n\n%d goroutines, %d unique", s.Total, len(s.Calls))
+ }
+}
+
+func (c Call) Format(w fmt.State, r rune) {
+ for i, g := range c.Groups {
+ if i > 0 {
+ fmt.Fprint(w, " ")
+ }
+ fmt.Fprint(w, g)
+ }
+ for _, f := range c.Stack {
+ fmt.Fprintf(w, "\n%v", f)
+ }
+}
+
+func (g Group) Format(w fmt.State, r rune) {
+ fmt.Fprintf(w, "[%v]: ", g.State)
+ for i, gr := range g.Goroutines {
+ if i > 0 {
+ fmt.Fprint(w, ", ")
+ }
+ fmt.Fprintf(w, "$%d", gr.ID)
+ }
+}
+
+func (f Frame) Format(w fmt.State, c rune) {
+ fmt.Fprintf(w, "%v:\t%v", f.Position, f.Function)
+}
+
+func (f Function) Format(w fmt.State, c rune) {
+ if f.Type != "" {
+ fmt.Fprintf(w, "(%v).", f.Type)
+ }
+ fmt.Fprintf(w, "%v", f.Name)
+}
+
+func (p Position) Format(w fmt.State, c rune) {
+ fmt.Fprintf(w, "%v:%v", p.Filename, p.Line)
+}
diff --git a/jsonrpc2/internal/stack/stack_test.go b/jsonrpc2/internal/stack/stack_test.go
new file mode 100644
index 0000000..900e4d1
--- /dev/null
+++ b/jsonrpc2/internal/stack/stack_test.go
@@ -0,0 +1,193 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stack_test
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+
+ "golang.org/x/exp/jsonrpc2/internal/stack"
+)
+
+func TestProcess(t *testing.T) {
+ for _, test := range []struct{ name, input, expect string }{{
+ name: `empty`,
+ input: ``,
+ expect: ``,
+ }, {
+ name: `no_frame`,
+ input: `goroutine 1 [running]:`,
+ expect: `
+[running]: $1
+
+1 goroutines, 1 unique
+`,
+ }, {
+ name: `one_frame`,
+ input: `
+goroutine 1 [running]:
+package.function(args)
+ file.go:10
+`,
+ expect: `
+[running]: $1
+file.go:10: function
+
+1 goroutines, 1 unique
+`,
+ }, {
+ name: `one_call`,
+ input: `
+goroutine 1 [running]:
+package1.functionA(args)
+ file1.go:10
+package2.functionB(args)
+ file2.go:20
+package3.functionC(args)
+ file3.go:30
+`,
+ expect: `
+[running]: $1
+file1.go:10: functionA
+file2.go:20: functionB
+file3.go:30: functionC
+
+1 goroutines, 1 unique
+`,
+ }, {
+ name: `two_call`,
+ input: `
+goroutine 1 [running]:
+package1.functionA(args)
+ file1.go:10
+goroutine 2 [running]:
+package2.functionB(args)
+ file2.go:20
+`,
+ expect: `
+[running]: $1
+file1.go:10: functionA
+
+[running]: $2
+file2.go:20: functionB
+
+2 goroutines, 2 unique
+`,
+ }, {
+ name: `merge_call`,
+ input: `
+goroutine 1 [running]:
+package1.functionA(args)
+ file1.go:10
+goroutine 2 [running]:
+package1.functionA(args)
+ file1.go:10
+`,
+ expect: `
+[running]: $1, $2
+file1.go:10: functionA
+
+2 goroutines, 1 unique
+`,
+ }, {
+ name: `alternating_call`,
+ input: `
+goroutine 1 [running]:
+package1.functionA(args)
+ file1.go:10
+goroutine 2 [running]:
+package2.functionB(args)
+ file2.go:20
+goroutine 3 [running]:
+package1.functionA(args)
+ file1.go:10
+goroutine 4 [running]:
+package2.functionB(args)
+ file2.go:20
+goroutine 5 [running]:
+package1.functionA(args)
+ file1.go:10
+goroutine 6 [running]:
+package2.functionB(args)
+ file2.go:20
+`,
+ expect: `
+[running]: $1, $3, $5
+file1.go:10: functionA
+
+[running]: $2, $4, $6
+file2.go:20: functionB
+
+6 goroutines, 2 unique
+`,
+ }, {
+ name: `sort_calls`,
+ input: `
+goroutine 1 [running]:
+package3.functionC(args)
+ file3.go:30
+goroutine 2 [running]:
+package2.functionB(args)
+ file2.go:20
+goroutine 3 [running]:
+package1.functionA(args)
+ file1.go:10
+`,
+ expect: `
+[running]: $3
+file1.go:10: functionA
+
+[running]: $2
+file2.go:20: functionB
+
+[running]: $1
+file3.go:30: functionC
+
+3 goroutines, 3 unique
+`,
+ }, {
+ name: `real_single`,
+ input: `
+panic: oops
+
+goroutine 53 [running]:
+golang.org/x/tools/internal/jsonrpc2_test.testHandler.func1(0x1240c20, 0xc000013350, 0xc0000133b0, 0x1240ca0, 0xc00002ab00, 0x3, 0x3)
+ /work/tools/internal/jsonrpc2/jsonrpc2_test.go:160 +0x74c
+golang.org/x/tools/internal/jsonrpc2.(*Conn).Run(0xc000204330, 0x1240c20, 0xc000204270, 0x1209570, 0xc000212120, 0x1242700)
+ /work/tools/internal/jsonrpc2/jsonrpc2.go:187 +0x777
+golang.org/x/tools/internal/jsonrpc2_test.run.func1(0x123ebe0, 0xc000206018, 0x123ec20, 0xc000206010, 0xc0002080a0, 0xc000204330, 0x1240c20, 0xc000204270, 0xc000212120)
+ /work/tools/internal/jsonrpc2/jsonrpc2_test.go:131 +0xe2
+created by golang.org/x/tools/internal/jsonrpc2_test.run
+ /work/tools/internal/jsonrpc2/jsonrpc2_test.go:121 +0x263
+FAIL golang.org/x/tools/internal/jsonrpc2 0.252s
+FAIL
+`,
+ expect: `
+panic: oops
+
+[running]: $53
+/work/tools/internal/jsonrpc2/jsonrpc2_test.go:160: testHandler.func1
+/work/tools/internal/jsonrpc2/jsonrpc2.go:187: (*Conn).Run
+/work/tools/internal/jsonrpc2/jsonrpc2_test.go:131: run.func1
+/work/tools/internal/jsonrpc2/jsonrpc2_test.go:121: run
+
+1 goroutines, 1 unique
+
+FAIL golang.org/x/tools/internal/jsonrpc2 0.252s
+FAIL
+`,
+ }} {
+ t.Run(test.name, func(t *testing.T) {
+ buf := &bytes.Buffer{}
+ stack.Process(buf, strings.NewReader(test.input))
+ expect := strings.TrimSpace(test.expect)
+ got := strings.TrimSpace(buf.String())
+ if got != expect {
+ t.Errorf("got:\n%s\nexpect:\n%s", got, expect)
+ }
+ })
+ }
+}
diff --git a/jsonrpc2/internal/stack/stacktest/stacktest.go b/jsonrpc2/internal/stack/stacktest/stacktest.go
new file mode 100644
index 0000000..bde66a5
--- /dev/null
+++ b/jsonrpc2/internal/stack/stacktest/stacktest.go
@@ -0,0 +1,50 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stacktest
+
+import (
+ "testing"
+ "time"
+
+ "golang.org/x/exp/jsonrpc2/internal/stack"
+)
+
+// this is only needed to support pre 1.14 when testing.TB did not have Cleanup
+type withCleanup interface {
+ Cleanup(func())
+}
+
+// the maximum amount of time to wait for goroutines to clean themselves up.
+const maxWait = time.Second
+
+// NoLeak checks that a test (or benchmark) does not leak any goroutines.
+func NoLeak(t testing.TB) {
+ c, ok := t.(withCleanup)
+ if !ok {
+ return
+ }
+ before := stack.Capture()
+ c.Cleanup(func() {
+ var delta stack.Delta
+ start := time.Now()
+ delay := time.Millisecond
+ for {
+ after := stack.Capture()
+ delta = stack.Diff(before, after)
+ if len(delta.After) == 0 {
+ // no leaks
+ return
+ }
+ if time.Since(start) > maxWait {
+ break
+ }
+ time.Sleep(delay)
+ delay *= 2
+ }
+ // it's been long enough, and leaks are still present
+ summary := stack.Summarize(delta.After)
+ t.Errorf("goroutine leak detected:\n%+v", summary)
+ })
+}
diff --git a/jsonrpc2/jsonrpc2.go b/jsonrpc2/jsonrpc2.go
new file mode 100644
index 0000000..4e853d5
--- /dev/null
+++ b/jsonrpc2/jsonrpc2.go
@@ -0,0 +1,99 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package jsonrpc2 is a minimal implementation of the JSON RPC 2 spec.
+// https://www.jsonrpc.org/specification
+// It is intended to be compatible with other implementations at the wire level.
+package jsonrpc2
+
+import (
+ "context"
+ "errors"
+)
+
+var (
+ // ErrIdleTimeout is returned when serving timed out waiting for new connections.
+ ErrIdleTimeout = errors.New("timed out waiting for new connections")
+ // ErrNotHandled is returned from a handler to indicate it did not handle the
+ // message.
+ ErrNotHandled = errors.New("JSON RPC not handled")
+ // ErrAsyncResponse is returned from a handler to indicate it will generate a
+ // response asynchronously.
+ ErrAsyncResponse = errors.New("JSON RPC asynchronous response")
+)
+
+// Preempter handles messages on a connection before they are queued to the main
+// handler.
+// Primarily this is used for cancel handlers or notifications for which out of
+// order processing is not an issue.
+type Preempter interface {
+ // Preempt is invoked for each incoming request before it is queued.
+ // If the request is a call, it must return a value or an error for the reply.
+ // Preempt should not block or start any new messages on the connection.
+ Preempt(ctx context.Context, req *Request) (interface{}, error)
+}
+
+// Handler handles messages on a connection.
+type Handler interface {
+ // Handle is invoked for each incoming request.
+ // If the request is a call, it must return a value or an error for the reply.
+ Handle(ctx context.Context, req *Request) (interface{}, error)
+}
+
+type defaultHandler struct{}
+
+func (defaultHandler) Preempt(context.Context, *Request) (interface{}, error) {
+ return nil, ErrNotHandled
+}
+
+func (defaultHandler) Handle(context.Context, *Request) (interface{}, error) {
+ return nil, ErrNotHandled
+}
+
+type HandlerFunc func(ctx context.Context, req *Request) (interface{}, error)
+
+func (f HandlerFunc) Handle(ctx context.Context, req *Request) (interface{}, error) {
+ return f(ctx, req)
+}
+
+// async is a small helper for things with an asynchronous result that you can
+// wait for.
+type async struct {
+ ready chan struct{}
+ errBox chan error
+}
+
+func (a *async) init() {
+ a.ready = make(chan struct{})
+ a.errBox = make(chan error, 1)
+ a.errBox <- nil
+}
+
+func (a *async) done() {
+ close(a.ready)
+}
+
+func (a *async) isDone() bool {
+ select {
+ case <-a.ready:
+ return true
+ default:
+ return false
+ }
+}
+
+func (a *async) wait() error {
+ <-a.ready
+ err := <-a.errBox
+ a.errBox <- err
+ return err
+}
+
+func (a *async) setError(err error) {
+ storedErr := <-a.errBox
+ if storedErr == nil {
+ storedErr = err
+ }
+ a.errBox <- storedErr
+}
diff --git a/jsonrpc2/jsonrpc2_test.go b/jsonrpc2/jsonrpc2_test.go
new file mode 100644
index 0000000..40df6ed
--- /dev/null
+++ b/jsonrpc2/jsonrpc2_test.go
@@ -0,0 +1,409 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2_test
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "path"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/event/eventtest"
+ "golang.org/x/exp/jsonrpc2"
+ "golang.org/x/exp/jsonrpc2/internal/stack/stacktest"
+ errors "golang.org/x/xerrors"
+)
+
+var callTests = []invoker{
+ call{"no_args", nil, true},
+ call{"one_string", "fish", "got:fish"},
+ call{"one_number", 10, "got:10"},
+ call{"join", []string{"a", "b", "c"}, "a/b/c"},
+ sequence{"notify", []invoker{
+ notify{"set", 3},
+ notify{"add", 5},
+ call{"get", nil, 8},
+ }},
+ sequence{"preempt", []invoker{
+ async{"a", "wait", "a"},
+ notify{"unblock", "a"},
+ collect{"a", true, false},
+ }},
+ sequence{"basic cancel", []invoker{
+ async{"b", "wait", "b"},
+ cancel{"b"},
+ collect{"b", nil, true},
+ }},
+ sequence{"queue", []invoker{
+ async{"a", "wait", "a"},
+ notify{"set", 1},
+ notify{"add", 2},
+ notify{"add", 3},
+ notify{"add", 4},
+ call{"peek", nil, 0}, // accumulator will not have any adds yet
+ notify{"unblock", "a"},
+ collect{"a", true, false},
+ call{"get", nil, 10}, // accumulator now has all the adds
+ }},
+ sequence{"fork", []invoker{
+ async{"a", "fork", "a"},
+ notify{"set", 1},
+ notify{"add", 2},
+ notify{"add", 3},
+ notify{"add", 4},
+ call{"get", nil, 10}, // fork will not have blocked the adds
+ notify{"unblock", "a"},
+ collect{"a", true, false},
+ }},
+ callErr{"error", func() {}, "marshaling call parameters: json: unsupported type"},
+}
+
+type binder struct {
+ framer jsonrpc2.Framer
+ runTest func(*handler)
+}
+
+type handler struct {
+ conn *jsonrpc2.Connection
+ accumulator int
+ waitersBox chan map[string]chan struct{}
+ calls map[string]*jsonrpc2.AsyncCall
+}
+
+type invoker interface {
+ Name() string
+ Invoke(t *testing.T, ctx context.Context, h *handler)
+}
+
+type notify struct {
+ method string
+ params interface{}
+}
+
+type call struct {
+ method string
+ params interface{}
+ expect interface{}
+}
+
+type callErr struct {
+ method string
+ params interface{}
+ expectErr string
+}
+
+type async struct {
+ name string
+ method string
+ params interface{}
+}
+
+type collect struct {
+ name string
+ expect interface{}
+ fails bool
+}
+
+type cancel struct {
+ name string
+}
+
+type sequence struct {
+ name string
+ tests []invoker
+}
+
+type echo call
+
+type cancelParams struct{ ID int64 }
+
+func TestConnectionRaw(t *testing.T) {
+ testConnection(t, jsonrpc2.RawFramer())
+}
+
+func TestConnectionHeader(t *testing.T) {
+ testConnection(t, jsonrpc2.HeaderFramer())
+}
+
+func testConnection(t *testing.T, framer jsonrpc2.Framer) {
+ stacktest.NoLeak(t)
+ ctx := eventtest.NewContext(context.Background(), t)
+ listener, err := jsonrpc2.NetPipe(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server, err := jsonrpc2.Serve(ctx, listener, binder{framer, nil})
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ listener.Close()
+ server.Wait()
+ }()
+
+ for _, test := range callTests {
+ t.Run(test.Name(), func(t *testing.T) {
+ client, err := jsonrpc2.Dial(ctx,
+ listener.Dialer(), binder{framer, func(h *handler) {
+ defer h.conn.Close()
+ ctx := eventtest.NewContext(ctx, t)
+ test.Invoke(t, ctx, h)
+ if call, ok := test.(*call); ok {
+ // also run all simple call tests in echo mode
+ (*echo)(call).Invoke(t, ctx, h)
+ }
+ }})
+ if err != nil {
+ t.Fatal(err)
+ }
+ client.Wait()
+ })
+ }
+}
+
+func (test notify) Name() string { return test.method }
+func (test notify) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ if err := h.conn.Notify(ctx, test.method, test.params); err != nil {
+ t.Fatalf("%v:Notify failed: %v", test.method, err)
+ }
+}
+
+func (test call) Name() string { return test.method }
+func (test call) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ results := newResults(test.expect)
+ if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, results); err != nil {
+ t.Fatalf("%v:Call failed: %v", test.method, err)
+ }
+ verifyResults(t, test.method, results, test.expect)
+}
+
+func (test callErr) Name() string { return test.method }
+func (test callErr) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ var results interface{}
+ if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, &results); err != nil {
+ if serr := err.Error(); !strings.Contains(serr, test.expectErr) {
+ t.Fatalf("%v:Call failed but with unexpected error: %q does not contain %q", test.method, serr, test.expectErr)
+ }
+ return
+ }
+ t.Fatalf("%v:Call succeeded (%v) but should have failed with error containing %q", test.method, results, test.expectErr)
+}
+
+func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ results := newResults(test.expect)
+ if err := h.conn.Call(ctx, "echo", []interface{}{test.method, test.params}).Await(ctx, results); err != nil {
+ t.Fatalf("%v:Echo failed: %v", test.method, err)
+ }
+ verifyResults(t, test.method, results, test.expect)
+}
+
+func (test async) Name() string { return test.name }
+func (test async) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ h.calls[test.name] = h.conn.Call(ctx, test.method, test.params)
+}
+
+func (test collect) Name() string { return test.name }
+func (test collect) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ o := h.calls[test.name]
+ results := newResults(test.expect)
+ err := o.Await(ctx, results)
+ switch {
+ case test.fails && err == nil:
+ t.Fatalf("%v:Collect was supposed to fail", test.name)
+ case !test.fails && err != nil:
+ t.Fatalf("%v:Collect failed: %v", test.name, err)
+ }
+ verifyResults(t, test.name, results, test.expect)
+}
+
+func (test cancel) Name() string { return test.name }
+func (test cancel) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ o := h.calls[test.name]
+ if err := h.conn.Notify(ctx, "cancel", &cancelParams{o.ID().Raw().(int64)}); err != nil {
+ t.Fatalf("%v:Collect failed: %v", test.name, err)
+ }
+}
+
+func (test sequence) Name() string { return test.name }
+func (test sequence) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ for _, child := range test.tests {
+ child.Invoke(t, ctx, h)
+ }
+}
+
+// newResults makes a new empty copy of the expected type to put the results into
+func newResults(expect interface{}) interface{} {
+ switch e := expect.(type) {
+ case []interface{}:
+ var r []interface{}
+ for _, v := range e {
+ r = append(r, reflect.New(reflect.TypeOf(v)).Interface())
+ }
+ return r
+ case nil:
+ return nil
+ default:
+ return reflect.New(reflect.TypeOf(expect)).Interface()
+ }
+}
+
+// verifyResults compares the results to the expected values
+func verifyResults(t *testing.T, method string, results interface{}, expect interface{}) {
+ if expect == nil {
+ if results != nil {
+ t.Errorf("%v:Got results %+v where none expeted", method, expect)
+ }
+ return
+ }
+ val := reflect.Indirect(reflect.ValueOf(results)).Interface()
+ if !reflect.DeepEqual(val, expect) {
+ t.Errorf("%v:Results are incorrect, got %+v expect %+v", method, val, expect)
+ }
+}
+
+func (b binder) Bind(ctx context.Context, conn *jsonrpc2.Connection) (jsonrpc2.ConnectionOptions, error) {
+ h := &handler{
+ conn: conn,
+ waitersBox: make(chan map[string]chan struct{}, 1),
+ calls: make(map[string]*jsonrpc2.AsyncCall),
+ }
+ h.waitersBox <- make(map[string]chan struct{})
+ if b.runTest != nil {
+ go b.runTest(h)
+ }
+ return jsonrpc2.ConnectionOptions{
+ Framer: b.framer,
+ Preempter: h,
+ Handler: h,
+ }, nil
+}
+
+func (h *handler) waiter(name string) chan struct{} {
+ waiters := <-h.waitersBox
+ defer func() { h.waitersBox <- waiters }()
+ waiter, found := waiters[name]
+ if !found {
+ waiter = make(chan struct{})
+ waiters[name] = waiter
+ }
+ return waiter
+}
+
+func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+ switch req.Method {
+ case "unblock":
+ var name string
+ if err := json.Unmarshal(req.Params, &name); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ close(h.waiter(name))
+ return nil, nil
+ case "peek":
+ if len(req.Params) > 0 {
+ return nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)
+ }
+ return h.accumulator, nil
+ case "cancel":
+ var params cancelParams
+ if err := json.Unmarshal(req.Params, &params); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ h.conn.Cancel(jsonrpc2.Int64ID(params.ID))
+ return nil, nil
+ default:
+ return nil, jsonrpc2.ErrNotHandled
+ }
+}
+
+func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+ switch req.Method {
+ case "no_args":
+ if len(req.Params) > 0 {
+ return nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)
+ }
+ return true, nil
+ case "one_string":
+ var v string
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ return "got:" + v, nil
+ case "one_number":
+ var v int
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ return fmt.Sprintf("got:%d", v), nil
+ case "set":
+ var v int
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ h.accumulator = v
+ return nil, nil
+ case "add":
+ var v int
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ h.accumulator += v
+ return nil, nil
+ case "get":
+ if len(req.Params) > 0 {
+ return nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)
+ }
+ return h.accumulator, nil
+ case "join":
+ var v []string
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ return path.Join(v...), nil
+ case "echo":
+ var v []interface{}
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ var result interface{}
+ err := h.conn.Call(ctx, v[0].(string), v[1]).Await(ctx, &result)
+ return result, err
+ case "wait":
+ var name string
+ if err := json.Unmarshal(req.Params, &name); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ select {
+ case <-h.waiter(name):
+ return true, nil
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-time.After(time.Second):
+ return nil, errors.Errorf("wait for %q timed out", name)
+ }
+ case "fork":
+ var name string
+ if err := json.Unmarshal(req.Params, &name); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ waitFor := h.waiter(name)
+ go func() {
+ select {
+ case <-waitFor:
+ h.conn.Respond(req.ID, true, nil)
+ case <-ctx.Done():
+ h.conn.Respond(req.ID, nil, ctx.Err())
+ case <-time.After(time.Second):
+ h.conn.Respond(req.ID, nil, errors.Errorf("wait for %q timed out", name))
+ }
+ }()
+ return nil, jsonrpc2.ErrAsyncResponse
+ default:
+ return nil, jsonrpc2.ErrNotHandled
+ }
+}
diff --git a/jsonrpc2/messages.go b/jsonrpc2/messages.go
new file mode 100644
index 0000000..652ac81
--- /dev/null
+++ b/jsonrpc2/messages.go
@@ -0,0 +1,181 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "encoding/json"
+
+ errors "golang.org/x/xerrors"
+)
+
+// ID is a Request identifier.
+type ID struct {
+ value interface{}
+}
+
+// Message is the interface to all jsonrpc2 message types.
+// They share no common functionality, but are a closed set of concrete types
+// that are allowed to implement this interface. The message types are *Request
+// and *Response.
+type Message interface {
+ // marshal builds the wire form from the API form.
+ // It is private, which makes the set of Message implementations closed.
+ marshal(to *wireCombined)
+}
+
+// Request is a Message sent to a peer to request behavior.
+// If it has an ID it is a call, otherwise it is a notification.
+type Request struct {
+ // ID of this request, used to tie the Response back to the request.
+ // This will be nil for notifications.
+ ID ID
+ // Method is a string containing the method name to invoke.
+ Method string
+ // Params is either a struct or an array with the parameters of the method.
+ Params json.RawMessage
+}
+
+// Response is a Message used as a reply to a call Request.
+// It will have the same ID as the call it is a response to.
+type Response struct {
+ // result is the content of the response.
+ Result json.RawMessage
+ // err is set only if the call failed.
+ Error error
+ // id of the request this is a response to.
+ ID ID
+}
+
+// StringID creates a new string request identifier.
+func StringID(s string) ID { return ID{value: s} }
+
+// Int64ID creates a new integer request identifier.
+func Int64ID(i int64) ID { return ID{value: i} }
+
+// IsValid returns true if the ID is a valid identifier.
+// The default value for ID will return false.
+func (id ID) IsValid() bool { return id.value != nil }
+
+// Raw returns the underlying value of the ID.
+func (id ID) Raw() interface{} { return id.value }
+
+// NewNotification constructs a new Notification message for the supplied
+// method and parameters.
+func NewNotification(method string, params interface{}) (*Request, error) {
+ p, merr := marshalToRaw(params)
+ return &Request{Method: method, Params: p}, merr
+}
+
+// NewCall constructs a new Call message for the supplied ID, method and
+// parameters.
+func NewCall(id ID, method string, params interface{}) (*Request, error) {
+ p, merr := marshalToRaw(params)
+ return &Request{ID: id, Method: method, Params: p}, merr
+}
+
+func (msg *Request) IsCall() bool { return msg.ID.IsValid() }
+
+func (msg *Request) marshal(to *wireCombined) {
+ to.ID = msg.ID.value
+ to.Method = msg.Method
+ to.Params = msg.Params
+}
+
+// NewResponse constructs a new Response message that is a reply to the
+// supplied. If err is set result may be ignored.
+func NewResponse(id ID, result interface{}, rerr error) (*Response, error) {
+ r, merr := marshalToRaw(result)
+ return &Response{ID: id, Result: r, Error: rerr}, merr
+}
+
+func (msg *Response) marshal(to *wireCombined) {
+ to.ID = msg.ID.value
+ to.Error = toWireError(msg.Error)
+ to.Result = msg.Result
+}
+
+func toWireError(err error) *wireError {
+ if err == nil {
+ // no error, the response is complete
+ return nil
+ }
+ if err, ok := err.(*wireError); ok {
+ // already a wire error, just use it
+ return err
+ }
+ result := &wireError{Message: err.Error()}
+ var wrapped *wireError
+ if errors.As(err, &wrapped) {
+ // if we wrapped a wire error, keep the code from the wrapped error
+ // but the message from the outer error
+ result.Code = wrapped.Code
+ }
+ return result
+}
+
+func EncodeMessage(msg Message) ([]byte, error) {
+ wire := wireCombined{VersionTag: wireVersion}
+ msg.marshal(&wire)
+ data, err := json.Marshal(&wire)
+ if err != nil {
+ return data, errors.Errorf("marshaling jsonrpc message: %w", err)
+ }
+ return data, nil
+}
+
+func DecodeMessage(data []byte) (Message, error) {
+ msg := wireCombined{}
+ if err := json.Unmarshal(data, &msg); err != nil {
+ return nil, errors.Errorf("unmarshaling jsonrpc message: %w", err)
+ }
+ if msg.VersionTag != wireVersion {
+ return nil, errors.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion)
+ }
+ id := ID{}
+ switch v := msg.ID.(type) {
+ case nil:
+ case float64:
+ // coerce the id type to int64 if it is float64, the spec does not allow fractional parts
+ id = Int64ID(int64(v))
+ case int64:
+ id = Int64ID(v)
+ case string:
+ id = StringID(v)
+ default:
+ return nil, errors.Errorf("invalid message id type <%T>%v", v, v)
+ }
+ if msg.Method != "" {
+ // has a method, must be a call
+ return &Request{
+ Method: msg.Method,
+ ID: id,
+ Params: msg.Params,
+ }, nil
+ }
+ // no method, should be a response
+ if !id.IsValid() {
+ return nil, ErrInvalidRequest
+ }
+ resp := &Response{
+ ID: id,
+ Result: msg.Result,
+ }
+ // we have to check if msg.Error is nil to avoid a typed error
+ if msg.Error != nil {
+ resp.Error = msg.Error
+ }
+ return resp, nil
+}
+
+func marshalToRaw(obj interface{}) (json.RawMessage, error) {
+ if obj == nil {
+ return nil, nil
+ }
+ data, err := json.Marshal(obj)
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(data), nil
+}
diff --git a/jsonrpc2/net.go b/jsonrpc2/net.go
new file mode 100644
index 0000000..c40c4d8
--- /dev/null
+++ b/jsonrpc2/net.go
@@ -0,0 +1,129 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "context"
+ "io"
+ "net"
+ "os"
+ "time"
+)
+
+// This file contains implementations of the transport primitives that use the standard network
+// package.
+
+// NetListenOptions is the optional arguments to the NetListen function.
+type NetListenOptions struct {
+ NetListenConfig net.ListenConfig
+ NetDialer net.Dialer
+}
+
+// NetListener returns a new Listener that listens on a socket using the net package.
+func NetListener(ctx context.Context, network, address string, options NetListenOptions) (Listener, error) {
+ ln, err := options.NetListenConfig.Listen(ctx, network, address)
+ if err != nil {
+ return nil, err
+ }
+ return &netListener{net: ln}, nil
+}
+
+// netListener is the implementation of Listener for connections made using the net package.
+type netListener struct {
+ net net.Listener
+}
+
+// Accept blocks waiting for an incoming connection to the listener.
+func (l *netListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
+ return l.net.Accept()
+}
+
+// Close will cause the listener to stop listening. It will not close any connections that have
+// already been accepted.
+func (l *netListener) Close() error {
+ addr := l.net.Addr()
+ err := l.net.Close()
+ if addr.Network() == "unix" {
+ rerr := os.Remove(addr.String())
+ if rerr != nil && err == nil {
+ err = rerr
+ }
+ }
+ return err
+}
+
+// Dialer returns a dialer that can be used to connect to the listener.
+func (l *netListener) Dialer() Dialer {
+ return NetDialer(l.net.Addr().Network(), l.net.Addr().String(), net.Dialer{
+ Timeout: 5 * time.Second,
+ })
+}
+
+// NetDialer returns a Dialer using the supplied standard network dialer.
+func NetDialer(network, address string, nd net.Dialer) Dialer {
+ return &netDialer{
+ network: network,
+ address: address,
+ dialer: nd,
+ }
+}
+
+type netDialer struct {
+ network string
+ address string
+ dialer net.Dialer
+}
+
+func (n *netDialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
+ return n.dialer.DialContext(ctx, n.network, n.address)
+}
+
+// NetPipe returns a new Listener that listens using net.Pipe.
+// It is only possibly to connect to it using the Dialier returned by the
+// Dialer method, each call to that method will generate a new pipe the other
+// side of which will be returned from the Accept call.
+func NetPipe(ctx context.Context) (Listener, error) {
+ return &netPiper{
+ done: make(chan struct{}),
+ dialed: make(chan io.ReadWriteCloser),
+ }, nil
+}
+
+// netPiper is the implementation of Listener build on top of net.Pipes.
+type netPiper struct {
+ done chan struct{}
+ dialed chan io.ReadWriteCloser
+}
+
+// Accept blocks waiting for an incoming connection to the listener.
+func (l *netPiper) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
+ // block until we have a listener, or are closed or cancelled
+ select {
+ case rwc := <-l.dialed:
+ return rwc, nil
+ case <-l.done:
+ return nil, io.EOF
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ }
+}
+
+// Close will cause the listener to stop listening. It will not close any connections that have
+// already been accepted.
+func (l *netPiper) Close() error {
+ // unblock any accept calls that are pending
+ close(l.done)
+ return nil
+}
+
+func (l *netPiper) Dialer() Dialer {
+ return l
+}
+
+func (l *netPiper) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
+ client, server := net.Pipe()
+ l.dialed <- server
+ return client, nil
+}
diff --git a/jsonrpc2/serve.go b/jsonrpc2/serve.go
new file mode 100644
index 0000000..f3b78f5
--- /dev/null
+++ b/jsonrpc2/serve.go
@@ -0,0 +1,283 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "context"
+ "io"
+ "runtime"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ errors "golang.org/x/xerrors"
+)
+
+// Listener is implemented by protocols to accept new inbound connections.
+type Listener interface {
+ // Accept an inbound connection to a server.
+ // It must block until an inbound connection is made, or the listener is
+ // shut down.
+ Accept(context.Context) (io.ReadWriteCloser, error)
+
+ // Close is used to ask a listener to stop accepting new connections.
+ Close() error
+
+ // Dialer returns a dialer that can be used to connect to this listener
+ // locally.
+ // If a listener does not implement this it will return a nil.
+ Dialer() Dialer
+}
+
+// Dialer is used by clients to dial a server.
+type Dialer interface {
+ // Dial returns a new communication byte stream to a listening server.
+ Dial(ctx context.Context) (io.ReadWriteCloser, error)
+}
+
+// Server is a running server that is accepting incoming connections.
+type Server struct {
+ listener Listener
+ binder Binder
+ async async
+}
+
+// Dial uses the dialer to make a new connection, wraps the returned
+// reader and writer using the framer to make a stream, and then builds
+// a connection on top of that stream using the binder.
+func Dial(ctx context.Context, dialer Dialer, binder Binder) (*Connection, error) {
+ // dial a server
+ rwc, err := dialer.Dial(ctx)
+ if err != nil {
+ return nil, err
+ }
+ return newConnection(ctx, rwc, binder)
+}
+
+// Serve starts a new server listening for incoming connections and returns
+// it.
+// This returns a fully running and connected server, it does not block on
+// the listener.
+// You can call Wait to block on the server, or Shutdown to get the sever to
+// terminate gracefully.
+// To notice incoming connections, use an intercepting Binder.
+func Serve(ctx context.Context, listener Listener, binder Binder) (*Server, error) {
+ server := &Server{
+ listener: listener,
+ binder: binder,
+ }
+ server.async.init()
+ go server.run(ctx)
+ return server, nil
+}
+
+// Wait returns only when the server has shut down.
+func (s *Server) Wait() error {
+ return s.async.wait()
+}
+
+// run accepts incoming connections from the listener,
+// If IdleTimeout is non-zero, run exits after there are no clients for this
+// duration, otherwise it exits only on error.
+func (s *Server) run(ctx context.Context) {
+ defer s.async.done()
+ var activeConns []*Connection
+ for {
+ // we never close the accepted connection, we rely on the other end
+ // closing or the socket closing itself naturally
+ rwc, err := s.listener.Accept(ctx)
+ if err != nil {
+ if !isClosingError(err) {
+ s.async.setError(err)
+ }
+ // we are done generating new connections for good
+ break
+ }
+
+ // see if any connections were closed while we were waiting
+ activeConns = onlyActive(activeConns)
+
+ // a new inbound connection,
+ conn, err := newConnection(ctx, rwc, s.binder)
+ if err != nil {
+ if !isClosingError(err) {
+ s.async.setError(err)
+ }
+ continue
+ }
+ activeConns = append(activeConns, conn)
+ }
+
+ // wait for all active conns to finish
+ for _, c := range activeConns {
+ c.Wait()
+ }
+}
+
+func onlyActive(conns []*Connection) []*Connection {
+ i := 0
+ for _, c := range conns {
+ if !c.async.isDone() {
+ conns[i] = c
+ i++
+ }
+ }
+ // trim the slice down
+ return conns[:i]
+}
+
+// isClosingError reports if the error occurs normally during the process of
+// closing a network connection. It uses imperfect heuristics that err on the
+// side of false negatives, and should not be used for anything critical.
+func isClosingError(err error) bool {
+ if err == nil {
+ return false
+ }
+ // Fully unwrap the error, so the following tests work.
+ for wrapped := err; wrapped != nil; wrapped = errors.Unwrap(err) {
+ err = wrapped
+ }
+
+ // Was it based on an EOF error?
+ if err == io.EOF {
+ return true
+ }
+
+ // Was it based on a closed pipe?
+ if err == io.ErrClosedPipe {
+ return true
+ }
+
+ // Per https://github.com/golang/go/issues/4373, this error string should not
+ // change. This is not ideal, but since the worst that could happen here is
+ // some superfluous logging, it is acceptable.
+ if err.Error() == "use of closed network connection" {
+ return true
+ }
+
+ if runtime.GOOS == "plan9" {
+ // Error reading from a closed connection.
+ if err == syscall.EINVAL {
+ return true
+ }
+ // Error trying to accept a new connection from a closed listener.
+ if strings.HasSuffix(err.Error(), " listen hungup") {
+ return true
+ }
+ }
+ return false
+}
+
+// NewIdleListener wraps a listener with an idle timeout.
+// When there are no active connections for at least the timeout duration a
+// call to accept will fail with ErrIdleTimeout.
+func NewIdleListener(timeout time.Duration, wrap Listener) Listener {
+ l := &idleListener{
+ timeout: timeout,
+ wrapped: wrap,
+ newConns: make(chan *idleCloser),
+ closed: make(chan struct{}),
+ wasTimeout: make(chan struct{}),
+ }
+ go l.run()
+ return l
+}
+
+type idleListener struct {
+ wrapped Listener
+ timeout time.Duration
+ newConns chan *idleCloser
+ closed chan struct{}
+ wasTimeout chan struct{}
+ closeOnce sync.Once
+}
+
+type idleCloser struct {
+ wrapped io.ReadWriteCloser
+ closed chan struct{}
+ closeOnce sync.Once
+}
+
+func (c *idleCloser) Read(p []byte) (int, error) {
+ n, err := c.wrapped.Read(p)
+ if err != nil && isClosingError(err) {
+ c.closeOnce.Do(func() { close(c.closed) })
+ }
+ return n, err
+}
+
+func (c *idleCloser) Write(p []byte) (int, error) {
+ // we do not close on write failure, we rely on the wrapped writer to do that
+ // if it is appropriate, which we will detect in the next read.
+ return c.wrapped.Write(p)
+}
+
+func (c *idleCloser) Close() error {
+ // we rely on closing the wrapped stream to signal to the next read that we
+ // are closed, rather than triggering the closed signal directly
+ return c.wrapped.Close()
+}
+
+func (l *idleListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
+ rwc, err := l.wrapped.Accept(ctx)
+ if err != nil {
+ if isClosingError(err) {
+ // underlying listener was closed
+ l.closeOnce.Do(func() { close(l.closed) })
+ // was it closed because of the idle timeout?
+ select {
+ case <-l.wasTimeout:
+ err = ErrIdleTimeout
+ default:
+ }
+ }
+ return nil, err
+ }
+ conn := &idleCloser{
+ wrapped: rwc,
+ closed: make(chan struct{}),
+ }
+ l.newConns <- conn
+ return conn, err
+}
+
+func (l *idleListener) Close() error {
+ defer l.closeOnce.Do(func() { close(l.closed) })
+ return l.wrapped.Close()
+}
+
+func (l *idleListener) Dialer() Dialer {
+ return l.wrapped.Dialer()
+}
+
+func (l *idleListener) run() {
+ var conns []*idleCloser
+ for {
+ var firstClosed chan struct{} // left at nil if there are no active conns
+ var timeout <-chan time.Time // left at nil if there are active conns
+ if len(conns) > 0 {
+ firstClosed = conns[0].closed
+ } else {
+ timeout = time.After(l.timeout)
+ }
+ select {
+ case <-l.closed:
+ // the main listener closed, no need to keep going
+ return
+ case conn := <-l.newConns:
+ // a new conn arrived, add it to the list
+ conns = append(conns, conn)
+ case <-timeout:
+ // we timed out, only happens when there are no active conns
+ // close the underlying listener, and allow the normal closing process to happen
+ close(l.wasTimeout)
+ l.wrapped.Close()
+ case <-firstClosed:
+ // a conn closed, remove it from the active list
+ conns = conns[:copy(conns, conns[1:])]
+ }
+ }
+}
diff --git a/jsonrpc2/serve_test.go b/jsonrpc2/serve_test.go
new file mode 100644
index 0000000..08e9e2e
--- /dev/null
+++ b/jsonrpc2/serve_test.go
@@ -0,0 +1,144 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2_test
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/jsonrpc2"
+ "golang.org/x/exp/jsonrpc2/internal/stack/stacktest"
+)
+
+func TestIdleTimeout(t *testing.T) {
+ stacktest.NoLeak(t)
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ listener, err := jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ listener = jsonrpc2.NewIdleListener(100*time.Millisecond, listener)
+ defer listener.Close()
+ server, err := jsonrpc2.Serve(ctx, listener, jsonrpc2.ConnectionOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ connect := func() *jsonrpc2.Connection {
+ client, err := jsonrpc2.Dial(ctx,
+ listener.Dialer(),
+ jsonrpc2.ConnectionOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ return client
+ }
+ // Exercise some connection/disconnection patterns, and then assert that when
+ // our timer fires, the server exits.
+ conn1 := connect()
+ conn2 := connect()
+ if err := conn1.Close(); err != nil {
+ t.Fatalf("conn1.Close failed with error: %v", err)
+ }
+ if err := conn2.Close(); err != nil {
+ t.Fatalf("conn2.Close failed with error: %v", err)
+ }
+ conn3 := connect()
+ if err := conn3.Close(); err != nil {
+ t.Fatalf("conn3.Close failed with error: %v", err)
+ }
+
+ serverError := server.Wait()
+
+ if !errors.Is(serverError, jsonrpc2.ErrIdleTimeout) {
+ t.Errorf("run() returned error %v, want %v", serverError, jsonrpc2.ErrIdleTimeout)
+ }
+}
+
+type msg struct {
+ Msg string
+}
+
+type fakeHandler struct{}
+
+func (fakeHandler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+ switch req.Method {
+ case "ping":
+ return &msg{"pong"}, nil
+ default:
+ return nil, jsonrpc2.ErrNotHandled
+ }
+}
+
+func TestServe(t *testing.T) {
+ stacktest.NoLeak(t)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ tests := []struct {
+ name string
+ factory func(context.Context) (jsonrpc2.Listener, error)
+ }{
+ {"tcp", func(ctx context.Context) (jsonrpc2.Listener, error) {
+ return jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{})
+ }},
+ {"pipe", func(ctx context.Context) (jsonrpc2.Listener, error) {
+ return jsonrpc2.NetPipe(ctx)
+ }},
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ fake, err := test.factory(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ conn, shutdown, err := newFake(t, ctx, fake)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer shutdown()
+ var got msg
+ if err := conn.Call(ctx, "ping", &msg{"ting"}).Await(ctx, &got); err != nil {
+ t.Fatal(err)
+ }
+ if want := "pong"; got.Msg != want {
+ t.Errorf("conn.Call(...): returned %q, want %q", got, want)
+ }
+ })
+ }
+}
+
+func newFake(t *testing.T, ctx context.Context, l jsonrpc2.Listener) (*jsonrpc2.Connection, func(), error) {
+ l = jsonrpc2.NewIdleListener(100*time.Millisecond, l)
+ server, err := jsonrpc2.Serve(ctx, l, jsonrpc2.ConnectionOptions{
+ Handler: fakeHandler{},
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
+ client, err := jsonrpc2.Dial(ctx,
+ l.Dialer(),
+ jsonrpc2.ConnectionOptions{
+ Handler: fakeHandler{},
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ return client, func() {
+ if err := l.Close(); err != nil {
+ t.Fatal(err)
+ }
+ if err := client.Close(); err != nil {
+ t.Fatal(err)
+ }
+ server.Wait()
+ }, nil
+}
diff --git a/jsonrpc2/wire.go b/jsonrpc2/wire.go
new file mode 100644
index 0000000..4971a92
--- /dev/null
+++ b/jsonrpc2/wire.go
@@ -0,0 +1,74 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "encoding/json"
+)
+
+// This file contains the go forms of the wire specification.
+// see http://www.jsonrpc.org/specification for details
+
+var (
+ // ErrUnknown should be used for all non coded errors.
+ ErrUnknown = NewError(-32001, "JSON RPC unknown error")
+ // ErrParse is used when invalid JSON was received by the server.
+ ErrParse = NewError(-32700, "JSON RPC parse error")
+ // ErrInvalidRequest is used when the JSON sent is not a valid Request object.
+ ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request")
+ // ErrMethodNotFound should be returned by the handler when the method does
+ // not exist / is not available.
+ ErrMethodNotFound = NewError(-32601, "JSON RPC method not found")
+ // ErrInvalidParams should be returned by the handler when method
+ // parameter(s) were invalid.
+ ErrInvalidParams = NewError(-32602, "JSON RPC invalid params")
+ // ErrInternal indicates a failure to process a call correctly
+ ErrInternal = NewError(-32603, "JSON RPC internal error")
+
+ // The following errors are not part of the json specification, but
+ // compliant extensions specific to this implementation.
+
+ // ErrServerOverloaded is returned when a message was refused due to a
+ // server being temporarily unable to accept any new messages.
+ ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded")
+)
+
+const wireVersion = "2.0"
+
+// wireCombined has all the fields of both Request and Response.
+// We can decode this and then work out which it is.
+type wireCombined struct {
+ VersionTag string `json:"jsonrpc"`
+ ID interface{} `json:"id,omitempty"`
+ Method string `json:"method,omitempty"`
+ Params json.RawMessage `json:"params,omitempty"`
+ Result json.RawMessage `json:"result,omitempty"`
+ Error *wireError `json:"error,omitempty"`
+}
+
+// wireError represents a structured error in a Response.
+type wireError struct {
+ // Code is an error code indicating the type of failure.
+ Code int64 `json:"code"`
+ // Message is a short description of the error.
+ Message string `json:"message"`
+ // Data is optional structured data containing additional information about the error.
+ Data json.RawMessage `json:"data,omitempty"`
+}
+
+// NewError returns an error that will encode on the wire correctly.
+// The standard codes are made available from this package, this function should
+// only be used to build errors for application specific codes as allowed by the
+// specification.
+func NewError(code int64, message string) error {
+ return &wireError{
+ Code: code,
+ Message: message,
+ }
+}
+
+func (err *wireError) Error() string {
+ return err.Message
+}
diff --git a/jsonrpc2/wire_test.go b/jsonrpc2/wire_test.go
new file mode 100644
index 0000000..905687d
--- /dev/null
+++ b/jsonrpc2/wire_test.go
@@ -0,0 +1,118 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "reflect"
+ "testing"
+
+ "golang.org/x/exp/jsonrpc2"
+)
+
+func TestWireMessage(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ msg jsonrpc2.Message
+ encoded []byte
+ }{{
+ name: "notification",
+ msg: newNotification("alive", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","method":"alive"}`),
+ }, {
+ name: "call",
+ msg: newCall("msg1", "ping", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","id":"msg1","method":"ping"}`),
+ }, {
+ name: "response",
+ msg: newResponse("msg2", "pong", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","id":"msg2","result":"pong"}`),
+ }, {
+ name: "numerical id",
+ msg: newCall(1, "poke", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","id":1,"method":"poke"}`),
+ }, {
+ // originally reported in #39719, this checks that result is not present if
+ // it is an error response
+ name: "computing fix edits",
+ msg: newResponse(3, nil, jsonrpc2.NewError(0, "computing fix edits")),
+ encoded: []byte(`{
+ "jsonrpc":"2.0",
+ "id":3,
+ "error":{
+ "code":0,
+ "message":"computing fix edits"
+ }
+ }`),
+ }} {
+ b, err := jsonrpc2.EncodeMessage(test.msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ checkJSON(t, b, test.encoded)
+ msg, err := jsonrpc2.DecodeMessage(test.encoded)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(msg, test.msg) {
+ t.Errorf("decoded message does not match\nGot:\n%+#v\nWant:\n%+#v", msg, test.msg)
+ }
+ }
+}
+
+func newNotification(method string, params interface{}) jsonrpc2.Message {
+ msg, err := jsonrpc2.NewNotification(method, params)
+ if err != nil {
+ panic(err)
+ }
+ return msg
+}
+
+func newID(id interface{}) jsonrpc2.ID {
+ switch v := id.(type) {
+ case nil:
+ return jsonrpc2.ID{}
+ case string:
+ return jsonrpc2.StringID(v)
+ case int:
+ return jsonrpc2.Int64ID(int64(v))
+ case int64:
+ return jsonrpc2.Int64ID(v)
+ default:
+ panic("invalid ID type")
+ }
+}
+
+func newCall(id interface{}, method string, params interface{}) jsonrpc2.Message {
+ msg, err := jsonrpc2.NewCall(newID(id), method, params)
+ if err != nil {
+ panic(err)
+ }
+ return msg
+}
+
+func newResponse(id interface{}, result interface{}, rerr error) jsonrpc2.Message {
+ msg, err := jsonrpc2.NewResponse(newID(id), result, rerr)
+ if err != nil {
+ panic(err)
+ }
+ return msg
+}
+
+func checkJSON(t *testing.T, got, want []byte) {
+ // compare the compact form, to allow for formatting differences
+ g := &bytes.Buffer{}
+ if err := json.Compact(g, []byte(got)); err != nil {
+ t.Fatal(err)
+ }
+ w := &bytes.Buffer{}
+ if err := json.Compact(w, []byte(want)); err != nil {
+ t.Fatal(err)
+ }
+ if g.String() != w.String() {
+ t.Errorf("encoded message does not match\nGot:\n%s\nWant:\n%s", g, w)
+ }
+}
diff --git a/maps/maps.go b/maps/maps.go
new file mode 100644
index 0000000..ecc0dab
--- /dev/null
+++ b/maps/maps.go
@@ -0,0 +1,94 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package maps defines various functions useful with maps of any type.
+package maps
+
+// Keys returns the keys of the map m.
+// The keys will be in an indeterminate order.
+func Keys[M ~map[K]V, K comparable, V any](m M) []K {
+ r := make([]K, 0, len(m))
+ for k := range m {
+ r = append(r, k)
+ }
+ return r
+}
+
+// Values returns the values of the map m.
+// The values will be in an indeterminate order.
+func Values[M ~map[K]V, K comparable, V any](m M) []V {
+ r := make([]V, 0, len(m))
+ for _, v := range m {
+ r = append(r, v)
+ }
+ return r
+}
+
+// Equal reports whether two maps contain the same key/value pairs.
+// Values are compared using ==.
+func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
+ if len(m1) != len(m2) {
+ return false
+ }
+ for k, v1 := range m1 {
+ if v2, ok := m2[k]; !ok || v1 != v2 {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualFunc is like Equal, but compares values using eq.
+// Keys are still compared with ==.
+func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
+ if len(m1) != len(m2) {
+ return false
+ }
+ for k, v1 := range m1 {
+ if v2, ok := m2[k]; !ok || !eq(v1, v2) {
+ return false
+ }
+ }
+ return true
+}
+
+// Clear removes all entries from m, leaving it empty.
+func Clear[M ~map[K]V, K comparable, V any](m M) {
+ for k := range m {
+ delete(m, k)
+ }
+}
+
+// Clone returns a copy of m. This is a shallow clone:
+// the new keys and values are set using ordinary assignment.
+func Clone[M ~map[K]V, K comparable, V any](m M) M {
+ // Preserve nil in case it matters.
+ if m == nil {
+ return nil
+ }
+ r := make(M, len(m))
+ for k, v := range m {
+ r[k] = v
+ }
+ return r
+}
+
+// Copy copies all key/value pairs in src adding them to dst.
+// When a key in src is already present in dst,
+// the value in dst will be overwritten by the value associated
+// with the key in src.
+func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
+ for k, v := range src {
+ dst[k] = v
+ }
+}
+
+// DeleteFunc deletes any key/value pairs from m for which del returns true.
+func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
+ for k, v := range m {
+ if del(k, v) {
+ delete(m, k)
+ }
+ }
+}
diff --git a/maps/maps_test.go b/maps/maps_test.go
new file mode 100644
index 0000000..bf7c6f4
--- /dev/null
+++ b/maps/maps_test.go
@@ -0,0 +1,181 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package maps
+
+import (
+ "math"
+ "sort"
+ "strconv"
+ "testing"
+
+ "golang.org/x/exp/slices"
+)
+
+var m1 = map[int]int{1: 2, 2: 4, 4: 8, 8: 16}
+var m2 = map[int]string{1: "2", 2: "4", 4: "8", 8: "16"}
+
+func TestKeys(t *testing.T) {
+ want := []int{1, 2, 4, 8}
+
+ got1 := Keys(m1)
+ sort.Ints(got1)
+ if !slices.Equal(got1, want) {
+ t.Errorf("Keys(%v) = %v, want %v", m1, got1, want)
+ }
+
+ got2 := Keys(m2)
+ sort.Ints(got2)
+ if !slices.Equal(got2, want) {
+ t.Errorf("Keys(%v) = %v, want %v", m2, got2, want)
+ }
+}
+
+func TestValues(t *testing.T) {
+ got1 := Values(m1)
+ want1 := []int{2, 4, 8, 16}
+ sort.Ints(got1)
+ if !slices.Equal(got1, want1) {
+ t.Errorf("Values(%v) = %v, want %v", m1, got1, want1)
+ }
+
+ got2 := Values(m2)
+ want2 := []string{"16", "2", "4", "8"}
+ sort.Strings(got2)
+ if !slices.Equal(got2, want2) {
+ t.Errorf("Values(%v) = %v, want %v", m2, got2, want2)
+ }
+}
+
+func TestEqual(t *testing.T) {
+ if !Equal(m1, m1) {
+ t.Errorf("Equal(%v, %v) = false, want true", m1, m1)
+ }
+ if Equal(m1, (map[int]int)(nil)) {
+ t.Errorf("Equal(%v, nil) = true, want false", m1)
+ }
+ if Equal((map[int]int)(nil), m1) {
+ t.Errorf("Equal(nil, %v) = true, want false", m1)
+ }
+ if !Equal[map[int]int, map[int]int](nil, nil) {
+ t.Error("Equal(nil, nil) = false, want true")
+ }
+ if ms := map[int]int{1: 2}; Equal(m1, ms) {
+ t.Errorf("Equal(%v, %v) = true, want false", m1, ms)
+ }
+
+ // Comparing NaN for equality is expected to fail.
+ mf := map[int]float64{1: 0, 2: math.NaN()}
+ if Equal(mf, mf) {
+ t.Errorf("Equal(%v, %v) = true, want false", mf, mf)
+ }
+}
+
+// equal is simply ==.
+func equal[T comparable](v1, v2 T) bool {
+ return v1 == v2
+}
+
+// equalNaN is like == except that all NaNs are equal.
+func equalNaN[T comparable](v1, v2 T) bool {
+ isNaN := func(f T) bool { return f != f }
+ return v1 == v2 || (isNaN(v1) && isNaN(v2))
+}
+
+// equalStr compares ints and strings.
+func equalIntStr(v1 int, v2 string) bool {
+ return strconv.Itoa(v1) == v2
+}
+
+func TestEqualFunc(t *testing.T) {
+ if !EqualFunc(m1, m1, equal[int]) {
+ t.Errorf("EqualFunc(%v, %v, equal) = false, want true", m1, m1)
+ }
+ if EqualFunc(m1, (map[int]int)(nil), equal[int]) {
+ t.Errorf("EqualFunc(%v, nil, equal) = true, want false", m1)
+ }
+ if EqualFunc((map[int]int)(nil), m1, equal[int]) {
+ t.Errorf("EqualFunc(nil, %v, equal) = true, want false", m1)
+ }
+ if !EqualFunc[map[int]int, map[int]int](nil, nil, equal[int]) {
+ t.Error("EqualFunc(nil, nil, equal) = false, want true")
+ }
+ if ms := map[int]int{1: 2}; EqualFunc(m1, ms, equal[int]) {
+ t.Errorf("EqualFunc(%v, %v, equal) = true, want false", m1, ms)
+ }
+
+ // Comparing NaN for equality is expected to fail.
+ mf := map[int]float64{1: 0, 2: math.NaN()}
+ if EqualFunc(mf, mf, equal[float64]) {
+ t.Errorf("EqualFunc(%v, %v, equal) = true, want false", mf, mf)
+ }
+ // But it should succeed using equalNaN.
+ if !EqualFunc(mf, mf, equalNaN[float64]) {
+ t.Errorf("EqualFunc(%v, %v, equalNaN) = false, want true", mf, mf)
+ }
+
+ if !EqualFunc(m1, m2, equalIntStr) {
+ t.Errorf("EqualFunc(%v, %v, equalIntStr) = false, want true", m1, m2)
+ }
+}
+
+func TestClear(t *testing.T) {
+ ml := map[int]int{1: 1, 2: 2, 3: 3}
+ Clear(ml)
+ if got := len(ml); got != 0 {
+ t.Errorf("len(%v) = %d after Clear, want 0", ml, got)
+ }
+ if !Equal(ml, (map[int]int)(nil)) {
+ t.Errorf("Equal(%v, nil) = false, want true", ml)
+ }
+}
+
+func TestClone(t *testing.T) {
+ mc := Clone(m1)
+ if !Equal(mc, m1) {
+ t.Errorf("Clone(%v) = %v, want %v", m1, mc, m1)
+ }
+ mc[16] = 32
+ if Equal(mc, m1) {
+ t.Errorf("Equal(%v, %v) = true, want false", mc, m1)
+ }
+}
+
+func TestCloneNil(t *testing.T) {
+ var m1 map[string]int
+ mc := Clone(m1)
+ if mc != nil {
+ t.Errorf("Clone(%v) = %v, want %v", m1, mc, m1)
+ }
+}
+
+func TestCopy(t *testing.T) {
+ mc := Clone(m1)
+ Copy(mc, mc)
+ if !Equal(mc, m1) {
+ t.Errorf("Copy(%v, %v) = %v, want %v", m1, m1, mc, m1)
+ }
+ Copy(mc, map[int]int{16: 32})
+ want := map[int]int{1: 2, 2: 4, 4: 8, 8: 16, 16: 32}
+ if !Equal(mc, want) {
+ t.Errorf("Copy result = %v, want %v", mc, want)
+ }
+
+ type M1 map[int]bool
+ type M2 map[int]bool
+ Copy(make(M1), make(M2))
+}
+
+func TestDeleteFunc(t *testing.T) {
+ mc := Clone(m1)
+ DeleteFunc(mc, func(int, int) bool { return false })
+ if !Equal(mc, m1) {
+ t.Errorf("DeleteFunc(%v, true) = %v, want %v", m1, mc, m1)
+ }
+ DeleteFunc(mc, func(k, v int) bool { return k > 3 })
+ want := map[int]int{1: 2, 2: 4}
+ if !Equal(mc, want) {
+ t.Errorf("DeleteFunc result = %v, want %v", mc, want)
+ }
+}
diff --git a/mmap/manual_test_program.go b/mmap/manual_test_program.go
new file mode 100644
index 0000000..b956b5c
--- /dev/null
+++ b/mmap/manual_test_program.go
@@ -0,0 +1,60 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+//
+// This build tag means that "go build" does not build this file. Use "go run
+// manual_test_program.go" to run it.
+//
+// You will also need to change "debug = false" to "debug = true" in mmap_*.go.
+
+package main
+
+import (
+ "log"
+ "math/rand"
+ "time"
+
+ "golang.org/x/exp/mmap"
+)
+
+var garbage []byte
+
+func main() {
+ // If you replace "manual_test_program.go" with the name of an empty (zero
+ // sized) file (and set "const debug = true") then you will not necessarily
+ // see two "munmap log messages", since some operating systems will not
+ // allow a zero sized mapping so there is no need for a finalizer to unmap.
+ const filename = "manual_test_program.go"
+
+ for _, explicitClose := range []bool{false, true} {
+ r, err := mmap.Open(filename)
+ if err != nil {
+ log.Fatalf("Open: %v", err)
+ }
+ println("Open succeeded; Len =", r.Len())
+ if explicitClose {
+ r.Close()
+ } else {
+ // Leak the *mmap.ReaderAt returned by mmap.Open. The finalizer
+ // should pick it up, if finalizers run at all.
+ }
+ }
+
+ println("Finished all explicit Close calls.")
+ println("Creating and collecting garbage.")
+ println("Look for two munmap log messages.")
+ println("Hit Ctrl-C to exit.")
+
+ rng := rand.New(rand.NewSource(1))
+ now := time.Now()
+ for {
+ garbage = make([]byte, rng.Intn(1<<20))
+ if time.Since(now) > 1*time.Second {
+ now = time.Now()
+ print(".")
+ }
+ }
+}
diff --git a/mmap/mmap_other.go b/mmap/mmap_other.go
new file mode 100644
index 0000000..799cd7c
--- /dev/null
+++ b/mmap/mmap_other.go
@@ -0,0 +1,75 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !linux && !windows && !darwin
+
+// Package mmap provides a way to memory-map a file.
+package mmap
+
+import (
+ "fmt"
+ "os"
+)
+
+// ReaderAt reads a memory-mapped file.
+//
+// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
+// not safe to call Close and reading methods concurrently.
+type ReaderAt struct {
+ f *os.File
+ len int
+}
+
+// Close closes the reader.
+func (r *ReaderAt) Close() error {
+ return r.f.Close()
+}
+
+// Len returns the length of the underlying memory-mapped file.
+func (r *ReaderAt) Len() int {
+ return r.len
+}
+
+// At returns the byte at index i.
+func (r *ReaderAt) At(i int) byte {
+ if i < 0 || r.len <= i {
+ panic("index out of range")
+ }
+ var b [1]byte
+ r.ReadAt(b[:], int64(i))
+ return b[0]
+}
+
+// ReadAt implements the io.ReaderAt interface.
+func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
+ return r.f.ReadAt(p, off)
+}
+
+// Open memory-maps the named file for reading.
+func Open(filename string) (*ReaderAt, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ fi, err := f.Stat()
+ if err != nil {
+ f.Close()
+ return nil, err
+ }
+
+ size := fi.Size()
+ if size < 0 {
+ f.Close()
+ return nil, fmt.Errorf("mmap: file %q has negative size", filename)
+ }
+ if size != int64(int(size)) {
+ f.Close()
+ return nil, fmt.Errorf("mmap: file %q is too large", filename)
+ }
+
+ return &ReaderAt{
+ f: f,
+ len: int(fi.Size()),
+ }, nil
+}
diff --git a/mmap/mmap_test.go b/mmap/mmap_test.go
new file mode 100644
index 0000000..0716cc4
--- /dev/null
+++ b/mmap/mmap_test.go
@@ -0,0 +1,34 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mmap
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "testing"
+)
+
+func TestOpen(t *testing.T) {
+ const filename = "mmap_test.go"
+ r, err := Open(filename)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+ got := make([]byte, r.Len())
+ if _, err := r.ReadAt(got, 0); err != nil && err != io.EOF {
+ t.Fatalf("ReadAt: %v", err)
+ }
+ want, err := os.ReadFile(filename)
+ if err != nil {
+ t.Fatalf("os.ReadFile: %v", err)
+ }
+ if len(got) != len(want) {
+ t.Fatalf("got %d bytes, want %d", len(got), len(want))
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatalf("\ngot %q\nwant %q", string(got), string(want))
+ }
+}
diff --git a/mmap/mmap_unix.go b/mmap/mmap_unix.go
new file mode 100644
index 0000000..0b92cdb
--- /dev/null
+++ b/mmap/mmap_unix.go
@@ -0,0 +1,126 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux || darwin
+
+// Package mmap provides a way to memory-map a file.
+package mmap
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "syscall"
+)
+
+// debug is whether to print debugging messages for manual testing.
+//
+// The runtime.SetFinalizer documentation says that, "The finalizer for x is
+// scheduled to run at some arbitrary time after x becomes unreachable. There
+// is no guarantee that finalizers will run before a program exits", so we
+// cannot automatically test that the finalizer runs. Instead, set this to true
+// when running the manual test.
+const debug = false
+
+// ReaderAt reads a memory-mapped file.
+//
+// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
+// not safe to call Close and reading methods concurrently.
+type ReaderAt struct {
+ data []byte
+}
+
+// Close closes the reader.
+func (r *ReaderAt) Close() error {
+ if r.data == nil {
+ return nil
+ } else if len(r.data) == 0 {
+ r.data = nil
+ return nil
+ }
+ data := r.data
+ r.data = nil
+ if debug {
+ var p *byte
+ if len(data) != 0 {
+ p = &data[0]
+ }
+ println("munmap", r, p)
+ }
+ runtime.SetFinalizer(r, nil)
+ return syscall.Munmap(data)
+}
+
+// Len returns the length of the underlying memory-mapped file.
+func (r *ReaderAt) Len() int {
+ return len(r.data)
+}
+
+// At returns the byte at index i.
+func (r *ReaderAt) At(i int) byte {
+ return r.data[i]
+}
+
+// ReadAt implements the io.ReaderAt interface.
+func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
+ if r.data == nil {
+ return 0, errors.New("mmap: closed")
+ }
+ if off < 0 || int64(len(r.data)) < off {
+ return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
+ }
+ n := copy(p, r.data[off:])
+ if n < len(p) {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// Open memory-maps the named file for reading.
+func Open(filename string) (*ReaderAt, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ size := fi.Size()
+ if size == 0 {
+ // Treat (size == 0) as a special case, avoiding the syscall, since
+ // "man 2 mmap" says "the length... must be greater than 0".
+ //
+ // As we do not call syscall.Mmap, there is no need to call
+ // runtime.SetFinalizer to enforce a balancing syscall.Munmap.
+ return &ReaderAt{
+ data: make([]byte, 0),
+ }, nil
+ }
+ if size < 0 {
+ return nil, fmt.Errorf("mmap: file %q has negative size", filename)
+ }
+ if size != int64(int(size)) {
+ return nil, fmt.Errorf("mmap: file %q is too large", filename)
+ }
+
+ data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
+ if err != nil {
+ return nil, err
+ }
+ r := &ReaderAt{data}
+ if debug {
+ var p *byte
+ if len(data) != 0 {
+ p = &data[0]
+ }
+ println("mmap", r, p)
+ }
+ runtime.SetFinalizer(r, (*ReaderAt).Close)
+ return r, nil
+}
diff --git a/mmap/mmap_windows.go b/mmap/mmap_windows.go
new file mode 100644
index 0000000..ea1d1cb
--- /dev/null
+++ b/mmap/mmap_windows.go
@@ -0,0 +1,133 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package mmap provides a way to memory-map a file.
+package mmap
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+// debug is whether to print debugging messages for manual testing.
+//
+// The runtime.SetFinalizer documentation says that, "The finalizer for x is
+// scheduled to run at some arbitrary time after x becomes unreachable. There
+// is no guarantee that finalizers will run before a program exits", so we
+// cannot automatically test that the finalizer runs. Instead, set this to true
+// when running the manual test.
+const debug = false
+
+// ReaderAt reads a memory-mapped file.
+//
+// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
+// not safe to call Close and reading methods concurrently.
+type ReaderAt struct {
+ data []byte
+}
+
+// Close closes the reader.
+func (r *ReaderAt) Close() error {
+ if r.data == nil {
+ return nil
+ } else if len(r.data) == 0 {
+ r.data = nil
+ return nil
+ }
+ data := r.data
+ r.data = nil
+ if debug {
+ var p *byte
+ if len(data) != 0 {
+ p = &data[0]
+ }
+ println("munmap", r, p)
+ }
+ runtime.SetFinalizer(r, nil)
+ return syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&data[0])))
+}
+
+// Len returns the length of the underlying memory-mapped file.
+func (r *ReaderAt) Len() int {
+ return len(r.data)
+}
+
+// At returns the byte at index i.
+func (r *ReaderAt) At(i int) byte {
+ return r.data[i]
+}
+
+// ReadAt implements the io.ReaderAt interface.
+func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
+ if r.data == nil {
+ return 0, errors.New("mmap: closed")
+ }
+ if off < 0 || int64(len(r.data)) < off {
+ return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
+ }
+ n := copy(p, r.data[off:])
+ if n < len(p) {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// Open memory-maps the named file for reading.
+func Open(filename string) (*ReaderAt, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ size := fi.Size()
+ if size == 0 {
+ // Treat (size == 0) as a special case, avoiding the syscall, to be
+ // consistent with mmap_unix.go.
+ //
+ // As we do not call syscall.MapViewOfFile, there is no need to call
+ // runtime.SetFinalizer to enforce a balancing syscall.UnmapViewOfFile.
+ return &ReaderAt{
+ data: make([]byte, 0),
+ }, nil
+ }
+ if size < 0 {
+ return nil, fmt.Errorf("mmap: file %q has negative size", filename)
+ }
+ if size != int64(int(size)) {
+ return nil, fmt.Errorf("mmap: file %q is too large", filename)
+ }
+
+ low, high := uint32(size), uint32(size>>32)
+ fmap, err := syscall.CreateFileMapping(syscall.Handle(f.Fd()), nil, syscall.PAGE_READONLY, high, low, nil)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.CloseHandle(fmap)
+ ptr, err := syscall.MapViewOfFile(fmap, syscall.FILE_MAP_READ, 0, 0, uintptr(size))
+ if err != nil {
+ return nil, err
+ }
+ data := unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size)
+
+ r := &ReaderAt{data: data}
+ if debug {
+ var p *byte
+ if len(data) != 0 {
+ p = &data[0]
+ }
+ println("mmap", r, p)
+ }
+ runtime.SetFinalizer(r, (*ReaderAt).Close)
+ return r, nil
+}
diff --git a/rand/arith128_test.go b/rand/arith128_test.go
new file mode 100644
index 0000000..eed655b
--- /dev/null
+++ b/rand/arith128_test.go
@@ -0,0 +1,126 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+ "math/big"
+ "math/rand"
+ "testing"
+)
+
+var bigMaxUint64 = big.NewInt(0).SetUint64(maxUint64)
+
+func bigInt(xHi, xLo uint64) *big.Int {
+ b := big.NewInt(0).SetUint64(xHi)
+ b.Lsh(b, 64)
+ b.Or(b, big.NewInt(0).SetUint64(xLo))
+ return b
+}
+
+func splitBigInt(b *big.Int) (outHi, outLo uint64) {
+ outHi = big.NewInt(0).Rsh(b, 64).Uint64()
+ outLo = big.NewInt(0).And(b, bigMaxUint64).Uint64()
+ return
+}
+
+func bigMulMod128bits(xHi, xLo, yHi, yLo uint64) (outHi, outLo uint64) {
+ bigX := bigInt(xHi, xLo)
+ bigY := bigInt(yHi, yLo)
+ return splitBigInt(bigX.Mul(bigX, bigY))
+}
+
+func bigAddMod128bits(xHi, xLo, yHi, yLo uint64) (outHi, outLo uint64) {
+ bigX := bigInt(xHi, xLo)
+ bigY := bigInt(yHi, yLo)
+ return splitBigInt(bigX.Add(bigX, bigY))
+}
+
+type arithTest struct {
+ xHi, xLo uint64
+}
+
+const (
+ iLo = increment & maxUint64
+ iHi = (increment >> 64) & maxUint64
+)
+
+var arithTests = []arithTest{
+ {0, 0},
+ {0, 1},
+ {1, 0},
+ {0, maxUint64},
+ {maxUint64, 0},
+ {maxUint64, maxUint64},
+ // Randomly generated 64-bit integers.
+ {3757956613005209672, 17983933746665545631},
+ {511324141977587414, 5626651684620191081},
+ {1534313104606153588, 2415006486399353367},
+ {6873586429837825902, 13854394671140464137},
+ {6617134480561088940, 18421520694158684312},
+}
+
+func TestPCGAdd(t *testing.T) {
+ for i, test := range arithTests {
+ p := &PCGSource{
+ low: test.xLo,
+ high: test.xHi,
+ }
+ p.add()
+ expectHi, expectLo := bigAddMod128bits(test.xHi, test.xLo, iHi, iLo)
+ if p.low != expectLo || p.high != expectHi {
+ t.Errorf("%d: got hi=%d lo=%d; expect hi=%d lo=%d", i, p.high, p.low, expectHi, expectLo)
+ }
+ }
+}
+
+const (
+ mLo = multiplier & maxUint64
+ mHi = (multiplier >> 64) & maxUint64
+)
+
+func TestPCGMultiply(t *testing.T) {
+ for i, test := range arithTests {
+ p := &PCGSource{
+ low: test.xLo,
+ high: test.xHi,
+ }
+ p.multiply()
+ expectHi, expectLo := bigMulMod128bits(test.xHi, test.xLo, mHi, mLo)
+ if p.low != expectLo || p.high != expectHi {
+ t.Errorf("%d: got hi=%d lo=%d; expect hi=%d lo=%d", i, p.high, p.low, expectHi, expectLo)
+ }
+ }
+}
+
+func TestPCGMultiplyLong(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ for i := 0; i < 1e6; i++ {
+ low := rand.Uint64()
+ high := rand.Uint64()
+ p := &PCGSource{
+ low: low,
+ high: high,
+ }
+ p.multiply()
+ expectHi, expectLo := bigMulMod128bits(high, low, mHi, mLo)
+ if p.low != expectLo || p.high != expectHi {
+ t.Fatalf("%d: (%d,%d): got hi=%d lo=%d; expect hi=%d lo=%d", i, high, low, p.high, p.low, expectHi, expectLo)
+ }
+ }
+}
+
+func BenchmarkPCGMultiply(b *testing.B) {
+ low := rand.Uint64()
+ high := rand.Uint64()
+ p := &PCGSource{
+ low: low,
+ high: high,
+ }
+ for i := 0; i < b.N; i++ {
+ p.multiply()
+ }
+}
diff --git a/rand/example_test.go b/rand/example_test.go
new file mode 100644
index 0000000..8faad51
--- /dev/null
+++ b/rand/example_test.go
@@ -0,0 +1,163 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand_test
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "text/tabwriter"
+
+ "golang.org/x/exp/rand"
+)
+
+// These tests serve as an example but also make sure we don't change
+// the output of the random number generator when given a fixed seed.
+
+func Example() {
+ rand.Seed(42) // Try changing this number!
+ answers := []string{
+ "It is certain",
+ "It is decidedly so",
+ "Without a doubt",
+ "Yes definitely",
+ "You may rely on it",
+ "As I see it yes",
+ "Most likely",
+ "Outlook good",
+ "Yes",
+ "Signs point to yes",
+ "Reply hazy try again",
+ "Ask again later",
+ "Better not tell you now",
+ "Cannot predict now",
+ "Concentrate and ask again",
+ "Don't count on it",
+ "My reply is no",
+ "My sources say no",
+ "Outlook not so good",
+ "Very doubtful",
+ }
+ fmt.Println("Magic 8-Ball says:", answers[rand.Intn(len(answers))])
+ // Output: Magic 8-Ball says: Most likely
+}
+
+// This example shows the use of each of the methods on a *Rand.
+// The use of the global functions is the same, without the receiver.
+func Example_rand() {
+ // Create and seed the generator.
+ // Typically a non-fixed seed should be used, such as time.Now().UnixNano().
+ // Using a fixed seed will produce the same output on every run.
+ r := rand.New(rand.NewSource(1234))
+
+ // The tabwriter here helps us generate aligned output.
+ w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
+ defer w.Flush()
+ show := func(name string, v1, v2, v3 interface{}) {
+ fmt.Fprintf(w, "%s\t%v\t%v\t%v\n", name, v1, v2, v3)
+ }
+
+ // Float32 and Float64 values are in [0, 1).
+ show("Float32", r.Float32(), r.Float32(), r.Float32())
+ show("Float64", r.Float64(), r.Float64(), r.Float64())
+
+ // ExpFloat64 values have an average of 1 but decay exponentially.
+ show("ExpFloat64", r.ExpFloat64(), r.ExpFloat64(), r.ExpFloat64())
+
+ // NormFloat64 values have an average of 0 and a standard deviation of 1.
+ show("NormFloat64", r.NormFloat64(), r.NormFloat64(), r.NormFloat64())
+
+ // Int31, Int63, and Uint32 generate values of the given width.
+ // The Int method (not shown) is like either Int31 or Int63
+ // depending on the size of 'int'.
+ show("Int31", r.Int31(), r.Int31(), r.Int31())
+ show("Int63", r.Int63(), r.Int63(), r.Int63())
+ show("Uint32", r.Uint32(), r.Uint32(), r.Uint32())
+ show("Uint64", r.Uint64(), r.Uint64(), r.Uint64())
+
+ // Intn, Int31n, Int63n and Uint64n limit their output to be < n.
+ // They do so more carefully than using r.Int()%n.
+ show("Intn(10)", r.Intn(10), r.Intn(10), r.Intn(10))
+ show("Int31n(10)", r.Int31n(10), r.Int31n(10), r.Int31n(10))
+ show("Int63n(10)", r.Int63n(10), r.Int63n(10), r.Int63n(10))
+ show("Uint64n(10)", r.Uint64n(10), r.Uint64n(10), r.Uint64n(10))
+
+ // Perm generates a random permutation of the numbers [0, n).
+ show("Perm", r.Perm(5), r.Perm(5), r.Perm(5))
+ // Output:
+ // Float32 0.030719291 0.47512934 0.031019364
+ // Float64 0.6906635660087743 0.9898818576905045 0.2683634639782333
+ // ExpFloat64 1.24979080914592 0.3451975160045876 0.5456817760595064
+ // NormFloat64 0.879221333732727 -0.01508980368383761 -1.962250558270421
+ // Int31 2043816560 1870670250 1334960143
+ // Int63 7860766611810691572 1466711535823962239 3836585920276818709
+ // Uint32 2051241581 751073909 1353986074
+ // Uint64 10802154207635843641 14398820303406316826 11052107950969057042
+ // Intn(10) 3 0 1
+ // Int31n(10) 3 8 1
+ // Int63n(10) 4 6 0
+ // Uint64n(10) 2 9 4
+ // Perm [1 3 4 0 2] [2 4 0 3 1] [3 2 0 4 1]
+}
+
+func ExampleShuffle() {
+ words := strings.Fields("ink runs from the corners of my mouth")
+ rand.Shuffle(len(words), func(i, j int) {
+ words[i], words[j] = words[j], words[i]
+ })
+ fmt.Println(words)
+
+ // Output:
+ // [ink corners of from mouth runs the my]
+}
+
+func ExampleShuffle_slicesInUnison() {
+ numbers := []byte("12345")
+ letters := []byte("ABCDE")
+ // Shuffle numbers, swapping corresponding entries in letters at the same time.
+ rand.Shuffle(len(numbers), func(i, j int) {
+ numbers[i], numbers[j] = numbers[j], numbers[i]
+ letters[i], letters[j] = letters[j], letters[i]
+ })
+ for i := range numbers {
+ fmt.Printf("%c: %c\n", letters[i], numbers[i])
+ }
+
+ // Output:
+ // D: 4
+ // A: 1
+ // E: 5
+ // B: 2
+ // C: 3
+}
+
+func ExampleLockedSource() {
+ r := rand.New(new(rand.LockedSource))
+ r.Seed(42) // Try changing this number!
+ answers := []string{
+ "It is certain",
+ "It is decidedly so",
+ "Without a doubt",
+ "Yes definitely",
+ "You may rely on it",
+ "As I see it yes",
+ "Most likely",
+ "Outlook good",
+ "Yes",
+ "Signs point to yes",
+ "Reply hazy try again",
+ "Ask again later",
+ "Better not tell you now",
+ "Cannot predict now",
+ "Concentrate and ask again",
+ "Don't count on it",
+ "My reply is no",
+ "My sources say no",
+ "Outlook not so good",
+ "Very doubtful",
+ }
+ fmt.Println("Magic 8-Ball says:", answers[r.Intn(len(answers))])
+ // Output: Magic 8-Ball says: Most likely
+}
diff --git a/rand/exp.go b/rand/exp.go
new file mode 100644
index 0000000..0838672
--- /dev/null
+++ b/rand/exp.go
@@ -0,0 +1,221 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+ "math"
+)
+
+/*
+ * Exponential distribution
+ *
+ * See "The Ziggurat Method for Generating Random Variables"
+ * (Marsaglia & Tsang, 2000)
+ * http://www.jstatsoft.org/v05/i08/paper [pdf]
+ */
+
+const (
+ re = 7.69711747013104972
+)
+
+// ExpFloat64 returns an exponentially distributed float64 in the range
+// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter
+// (lambda) is 1 and whose mean is 1/lambda (1).
+// To produce a distribution with a different rate parameter,
+// callers can adjust the output using:
+//
+// sample = ExpFloat64() / desiredRateParameter
+func (r *Rand) ExpFloat64() float64 {
+ for {
+ j := r.Uint32()
+ i := j & 0xFF
+ x := float64(j) * float64(we[i])
+ if j < ke[i] {
+ return x
+ }
+ if i == 0 {
+ return re - math.Log(r.Float64())
+ }
+ if fe[i]+float32(r.Float64())*(fe[i-1]-fe[i]) < float32(math.Exp(-x)) {
+ return x
+ }
+ }
+}
+
+var ke = [256]uint32{
+ 0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990,
+ 0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8,
+ 0xf0204efd, 0xf19bdb8e, 0xf2d458bb, 0xf3da104b, 0xf4b86d78,
+ 0xf577ad8a, 0xf61de83d, 0xf6afb784, 0xf730a573, 0xf7a37651,
+ 0xf80a5bb6, 0xf867189d, 0xf8bb1b4f, 0xf9079062, 0xf94d70ca,
+ 0xf98d8c7d, 0xf9c8928a, 0xf9ff175b, 0xfa319996, 0xfa6085f8,
+ 0xfa8c3a62, 0xfab5084e, 0xfadb36c8, 0xfaff0410, 0xfb20a6ea,
+ 0xfb404fb4, 0xfb5e2951, 0xfb7a59e9, 0xfb95038c, 0xfbae44ba,
+ 0xfbc638d8, 0xfbdcf892, 0xfbf29a30, 0xfc0731df, 0xfc1ad1ed,
+ 0xfc2d8b02, 0xfc3f6c4d, 0xfc5083ac, 0xfc60ddd1, 0xfc708662,
+ 0xfc7f8810, 0xfc8decb4, 0xfc9bbd62, 0xfca9027c, 0xfcb5c3c3,
+ 0xfcc20864, 0xfccdd70a, 0xfcd935e3, 0xfce42ab0, 0xfceebace,
+ 0xfcf8eb3b, 0xfd02c0a0, 0xfd0c3f59, 0xfd156b7b, 0xfd1e48d6,
+ 0xfd26daff, 0xfd2f2552, 0xfd372af7, 0xfd3eeee5, 0xfd4673e7,
+ 0xfd4dbc9e, 0xfd54cb85, 0xfd5ba2f2, 0xfd62451b, 0xfd68b415,
+ 0xfd6ef1da, 0xfd750047, 0xfd7ae120, 0xfd809612, 0xfd8620b4,
+ 0xfd8b8285, 0xfd90bcf5, 0xfd95d15e, 0xfd9ac10b, 0xfd9f8d36,
+ 0xfda43708, 0xfda8bf9e, 0xfdad2806, 0xfdb17141, 0xfdb59c46,
+ 0xfdb9a9fd, 0xfdbd9b46, 0xfdc170f6, 0xfdc52bd8, 0xfdc8ccac,
+ 0xfdcc542d, 0xfdcfc30b, 0xfdd319ef, 0xfdd6597a, 0xfdd98245,
+ 0xfddc94e5, 0xfddf91e6, 0xfde279ce, 0xfde54d1f, 0xfde80c52,
+ 0xfdeab7de, 0xfded5034, 0xfdefd5be, 0xfdf248e3, 0xfdf4aa06,
+ 0xfdf6f984, 0xfdf937b6, 0xfdfb64f4, 0xfdfd818d, 0xfdff8dd0,
+ 0xfe018a08, 0xfe03767a, 0xfe05536c, 0xfe07211c, 0xfe08dfc9,
+ 0xfe0a8fab, 0xfe0c30fb, 0xfe0dc3ec, 0xfe0f48b1, 0xfe10bf76,
+ 0xfe122869, 0xfe1383b4, 0xfe14d17c, 0xfe1611e7, 0xfe174516,
+ 0xfe186b2a, 0xfe19843e, 0xfe1a9070, 0xfe1b8fd6, 0xfe1c8289,
+ 0xfe1d689b, 0xfe1e4220, 0xfe1f0f26, 0xfe1fcfbc, 0xfe2083ed,
+ 0xfe212bc3, 0xfe21c745, 0xfe225678, 0xfe22d95f, 0xfe234ffb,
+ 0xfe23ba4a, 0xfe241849, 0xfe2469f2, 0xfe24af3c, 0xfe24e81e,
+ 0xfe25148b, 0xfe253474, 0xfe2547c7, 0xfe254e70, 0xfe25485a,
+ 0xfe25356a, 0xfe251586, 0xfe24e88f, 0xfe24ae64, 0xfe2466e1,
+ 0xfe2411df, 0xfe23af34, 0xfe233eb4, 0xfe22c02c, 0xfe22336b,
+ 0xfe219838, 0xfe20ee58, 0xfe20358c, 0xfe1f6d92, 0xfe1e9621,
+ 0xfe1daef0, 0xfe1cb7ac, 0xfe1bb002, 0xfe1a9798, 0xfe196e0d,
+ 0xfe1832fd, 0xfe16e5fe, 0xfe15869d, 0xfe141464, 0xfe128ed3,
+ 0xfe10f565, 0xfe0f478c, 0xfe0d84b1, 0xfe0bac36, 0xfe09bd73,
+ 0xfe07b7b5, 0xfe059a40, 0xfe03644c, 0xfe011504, 0xfdfeab88,
+ 0xfdfc26e9, 0xfdf98629, 0xfdf6c83b, 0xfdf3ec01, 0xfdf0f04a,
+ 0xfdedd3d1, 0xfdea953d, 0xfde7331e, 0xfde3abe9, 0xfddffdfb,
+ 0xfddc2791, 0xfdd826cd, 0xfdd3f9a8, 0xfdcf9dfc, 0xfdcb1176,
+ 0xfdc65198, 0xfdc15bb3, 0xfdbc2ce2, 0xfdb6c206, 0xfdb117be,
+ 0xfdab2a63, 0xfda4f5fd, 0xfd9e7640, 0xfd97a67a, 0xfd908192,
+ 0xfd8901f2, 0xfd812182, 0xfd78d98e, 0xfd7022bb, 0xfd66f4ed,
+ 0xfd5d4732, 0xfd530f9c, 0xfd48432b, 0xfd3cd59a, 0xfd30b936,
+ 0xfd23dea4, 0xfd16349e, 0xfd07a7a3, 0xfcf8219b, 0xfce7895b,
+ 0xfcd5c220, 0xfcc2aadb, 0xfcae1d5e, 0xfc97ed4e, 0xfc7fe6d4,
+ 0xfc65ccf3, 0xfc495762, 0xfc2a2fc8, 0xfc07ee19, 0xfbe213c1,
+ 0xfbb8051a, 0xfb890078, 0xfb5411a5, 0xfb180005, 0xfad33482,
+ 0xfa839276, 0xfa263b32, 0xf9b72d1c, 0xf930a1a2, 0xf889f023,
+ 0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d,
+ 0xe6da6ecf,
+}
+var we = [256]float32{
+ 2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11,
+ 3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11,
+ 5.905944e-11, 6.344942e-11, 6.7643814e-11, 7.1672945e-11,
+ 7.556032e-11, 7.932458e-11, 8.298079e-11, 8.654132e-11,
+ 9.0016515e-11, 9.3415074e-11, 9.674443e-11, 1.0001099e-10,
+ 1.03220314e-10, 1.06377254e-10, 1.09486115e-10, 1.1255068e-10,
+ 1.1557435e-10, 1.1856015e-10, 1.2151083e-10, 1.2442886e-10,
+ 1.2731648e-10, 1.3017575e-10, 1.3300853e-10, 1.3581657e-10,
+ 1.3860142e-10, 1.4136457e-10, 1.4410738e-10, 1.4683108e-10,
+ 1.4953687e-10, 1.5222583e-10, 1.54899e-10, 1.5755733e-10,
+ 1.6020171e-10, 1.6283301e-10, 1.6545203e-10, 1.6805951e-10,
+ 1.7065617e-10, 1.732427e-10, 1.7581973e-10, 1.7838787e-10,
+ 1.8094774e-10, 1.8349985e-10, 1.8604476e-10, 1.8858298e-10,
+ 1.9111498e-10, 1.9364126e-10, 1.9616223e-10, 1.9867835e-10,
+ 2.0119004e-10, 2.0369768e-10, 2.0620168e-10, 2.087024e-10,
+ 2.1120022e-10, 2.136955e-10, 2.1618855e-10, 2.1867974e-10,
+ 2.2116936e-10, 2.2365775e-10, 2.261452e-10, 2.2863202e-10,
+ 2.311185e-10, 2.3360494e-10, 2.360916e-10, 2.3857874e-10,
+ 2.4106667e-10, 2.4355562e-10, 2.4604588e-10, 2.485377e-10,
+ 2.5103128e-10, 2.5352695e-10, 2.560249e-10, 2.585254e-10,
+ 2.6102867e-10, 2.6353494e-10, 2.6604446e-10, 2.6855745e-10,
+ 2.7107416e-10, 2.7359479e-10, 2.761196e-10, 2.7864877e-10,
+ 2.8118255e-10, 2.8372119e-10, 2.8626485e-10, 2.888138e-10,
+ 2.9136826e-10, 2.939284e-10, 2.9649452e-10, 2.9906677e-10,
+ 3.016454e-10, 3.0423064e-10, 3.0682268e-10, 3.0942177e-10,
+ 3.1202813e-10, 3.1464195e-10, 3.1726352e-10, 3.19893e-10,
+ 3.2253064e-10, 3.251767e-10, 3.2783135e-10, 3.3049485e-10,
+ 3.3316744e-10, 3.3584938e-10, 3.3854083e-10, 3.4124212e-10,
+ 3.4395342e-10, 3.46675e-10, 3.4940711e-10, 3.5215003e-10,
+ 3.5490397e-10, 3.5766917e-10, 3.6044595e-10, 3.6323455e-10,
+ 3.660352e-10, 3.6884823e-10, 3.7167386e-10, 3.745124e-10,
+ 3.773641e-10, 3.802293e-10, 3.8310827e-10, 3.860013e-10,
+ 3.8890866e-10, 3.918307e-10, 3.9476775e-10, 3.9772008e-10,
+ 4.0068804e-10, 4.0367196e-10, 4.0667217e-10, 4.09689e-10,
+ 4.1272286e-10, 4.1577405e-10, 4.1884296e-10, 4.2192994e-10,
+ 4.250354e-10, 4.281597e-10, 4.313033e-10, 4.3446652e-10,
+ 4.3764986e-10, 4.408537e-10, 4.4407847e-10, 4.4732465e-10,
+ 4.5059267e-10, 4.5388301e-10, 4.571962e-10, 4.6053267e-10,
+ 4.6389292e-10, 4.6727755e-10, 4.70687e-10, 4.741219e-10,
+ 4.7758275e-10, 4.810702e-10, 4.845848e-10, 4.8812715e-10,
+ 4.9169796e-10, 4.9529775e-10, 4.989273e-10, 5.0258725e-10,
+ 5.0627835e-10, 5.100013e-10, 5.1375687e-10, 5.1754584e-10,
+ 5.21369e-10, 5.2522725e-10, 5.2912136e-10, 5.330522e-10,
+ 5.370208e-10, 5.4102806e-10, 5.45075e-10, 5.491625e-10,
+ 5.532918e-10, 5.5746385e-10, 5.616799e-10, 5.6594107e-10,
+ 5.7024857e-10, 5.746037e-10, 5.7900773e-10, 5.834621e-10,
+ 5.8796823e-10, 5.925276e-10, 5.971417e-10, 6.018122e-10,
+ 6.065408e-10, 6.113292e-10, 6.1617933e-10, 6.2109295e-10,
+ 6.260722e-10, 6.3111916e-10, 6.3623595e-10, 6.4142497e-10,
+ 6.4668854e-10, 6.5202926e-10, 6.5744976e-10, 6.6295286e-10,
+ 6.6854156e-10, 6.742188e-10, 6.79988e-10, 6.858526e-10,
+ 6.9181616e-10, 6.978826e-10, 7.04056e-10, 7.103407e-10,
+ 7.167412e-10, 7.2326256e-10, 7.2990985e-10, 7.366886e-10,
+ 7.4360473e-10, 7.5066453e-10, 7.5787476e-10, 7.6524265e-10,
+ 7.7277595e-10, 7.80483e-10, 7.883728e-10, 7.9645507e-10,
+ 8.047402e-10, 8.1323964e-10, 8.219657e-10, 8.309319e-10,
+ 8.401528e-10, 8.496445e-10, 8.594247e-10, 8.6951274e-10,
+ 8.799301e-10, 8.9070046e-10, 9.018503e-10, 9.134092e-10,
+ 9.254101e-10, 9.378904e-10, 9.508923e-10, 9.644638e-10,
+ 9.786603e-10, 9.935448e-10, 1.0091913e-09, 1.025686e-09,
+ 1.0431306e-09, 1.0616465e-09, 1.08138e-09, 1.1025096e-09,
+ 1.1252564e-09, 1.1498986e-09, 1.1767932e-09, 1.206409e-09,
+ 1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09,
+ 1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09,
+}
+var fe = [256]float32{
+ 1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933,
+ 0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686,
+ 0.7350381, 0.72286767, 0.71127474, 0.70019263, 0.6895665,
+ 0.67935055, 0.6695063, 0.66000086, 0.65080583, 0.6418967,
+ 0.63325197, 0.6248527, 0.6166822, 0.60872537, 0.60096896,
+ 0.5934009, 0.58601034, 0.5787874, 0.57172304, 0.5648092,
+ 0.5580383, 0.5514034, 0.5448982, 0.5385169, 0.53225386,
+ 0.5261042, 0.52006316, 0.5141264, 0.50828975, 0.5025495,
+ 0.496902, 0.49134386, 0.485872, 0.48048335, 0.4751752,
+ 0.46994483, 0.46478975, 0.45970762, 0.45469615, 0.44975325,
+ 0.44487688, 0.44006512, 0.43531612, 0.43062815, 0.42599955,
+ 0.42142874, 0.4169142, 0.41245446, 0.40804818, 0.403694,
+ 0.3993907, 0.39513698, 0.39093173, 0.38677382, 0.38266218,
+ 0.37859577, 0.37457356, 0.37059465, 0.3666581, 0.362763,
+ 0.35890847, 0.35509375, 0.351318, 0.3475805, 0.34388044,
+ 0.34021714, 0.3365899, 0.33299807, 0.32944095, 0.32591796,
+ 0.3224285, 0.3189719, 0.31554767, 0.31215525, 0.30879408,
+ 0.3054636, 0.3021634, 0.29889292, 0.2956517, 0.29243928,
+ 0.28925523, 0.28609908, 0.28297043, 0.27986884, 0.27679393,
+ 0.2737453, 0.2707226, 0.2677254, 0.26475343, 0.26180625,
+ 0.25888354, 0.25598502, 0.2531103, 0.25025907, 0.24743107,
+ 0.24462597, 0.24184346, 0.23908329, 0.23634516, 0.23362878,
+ 0.23093392, 0.2282603, 0.22560766, 0.22297576, 0.22036438,
+ 0.21777324, 0.21520215, 0.21265087, 0.21011916, 0.20760682,
+ 0.20511365, 0.20263945, 0.20018397, 0.19774707, 0.19532852,
+ 0.19292815, 0.19054577, 0.1881812, 0.18583426, 0.18350479,
+ 0.1811926, 0.17889754, 0.17661946, 0.17435817, 0.17211354,
+ 0.1698854, 0.16767362, 0.16547804, 0.16329853, 0.16113494,
+ 0.15898713, 0.15685499, 0.15473837, 0.15263714, 0.15055119,
+ 0.14848037, 0.14642459, 0.14438373, 0.14235765, 0.14034624,
+ 0.13834943, 0.13636707, 0.13439907, 0.13244532, 0.13050574,
+ 0.1285802, 0.12666863, 0.12477092, 0.12288698, 0.12101672,
+ 0.119160056, 0.1173169, 0.115487166, 0.11367077, 0.11186763,
+ 0.11007768, 0.10830083, 0.10653701, 0.10478614, 0.10304816,
+ 0.101323, 0.09961058, 0.09791085, 0.09622374, 0.09454919,
+ 0.09288713, 0.091237515, 0.08960028, 0.087975375, 0.08636274,
+ 0.08476233, 0.083174095, 0.081597984, 0.08003395, 0.07848195,
+ 0.076941945, 0.07541389, 0.07389775, 0.072393484, 0.07090106,
+ 0.069420435, 0.06795159, 0.066494495, 0.06504912, 0.063615434,
+ 0.062193416, 0.060783047, 0.059384305, 0.057997175,
+ 0.05662164, 0.05525769, 0.053905312, 0.052564494, 0.051235236,
+ 0.049917534, 0.048611384, 0.047316793, 0.046033762, 0.0447623,
+ 0.043502413, 0.042254124, 0.041017443, 0.039792392,
+ 0.038578995, 0.037377283, 0.036187284, 0.035009038,
+ 0.033842582, 0.032687962, 0.031545233, 0.030414443, 0.02929566,
+ 0.02818895, 0.027094385, 0.026012046, 0.024942026, 0.023884421,
+ 0.022839336, 0.021806888, 0.020787204, 0.019780423, 0.0187867,
+ 0.0178062, 0.016839107, 0.015885621, 0.014945968, 0.014020392,
+ 0.013109165, 0.012212592, 0.011331013, 0.01046481, 0.009614414,
+ 0.008780315, 0.007963077, 0.0071633533, 0.006381906,
+ 0.0056196423, 0.0048776558, 0.004157295, 0.0034602648,
+ 0.0027887989, 0.0021459677, 0.0015362998, 0.0009672693,
+ 0.00045413437,
+}
diff --git a/rand/modulo_test.go b/rand/modulo_test.go
new file mode 100644
index 0000000..da963c7
--- /dev/null
+++ b/rand/modulo_test.go
@@ -0,0 +1,50 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file validates that the calculation in Uint64n corrects for
+// possible bias.
+
+package rand
+
+import (
+ "testing"
+)
+
+// modSource is used to probe the upper region of uint64 space. It
+// generates values sequentially in [maxUint64-15,maxUint64]. With
+// modEdge == 15 and maxUint64 == 1<<64-1 == 18446744073709551615,
+// this means that Uint64n(10) will repeatedly probe the top range.
+// We thus expect a bias to result unless the calculation in Uint64n
+// gets the edge condition right. We test this by calling Uint64n 100
+// times; the results should be perfectly evenly distributed across
+// [0,10).
+type modSource uint64
+
+const modEdge = 15
+
+func (m *modSource) Seed(uint64) {}
+
+// Uint64 returns a non-pseudo-random 64-bit unsigned integer as a uint64.
+func (m *modSource) Uint64() uint64 {
+ if *m > modEdge {
+ *m = 0
+ }
+ r := maxUint64 - *m
+ *m++
+ return uint64(r)
+}
+
+func TestUint64Modulo(t *testing.T) {
+ var src modSource
+ rng := New(&src)
+ var result [10]uint64
+ for i := 0; i < 100; i++ {
+ result[rng.Uint64n(10)]++
+ }
+ for _, r := range result {
+ if r != 10 {
+ t.Fatal(result)
+ }
+ }
+}
diff --git a/rand/normal.go b/rand/normal.go
new file mode 100644
index 0000000..b66da3a
--- /dev/null
+++ b/rand/normal.go
@@ -0,0 +1,156 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+ "math"
+)
+
+/*
+ * Normal distribution
+ *
+ * See "The Ziggurat Method for Generating Random Variables"
+ * (Marsaglia & Tsang, 2000)
+ * http://www.jstatsoft.org/v05/i08/paper [pdf]
+ */
+
+const (
+ rn = 3.442619855899
+)
+
+func absInt32(i int32) uint32 {
+ if i < 0 {
+ return uint32(-i)
+ }
+ return uint32(i)
+}
+
+// NormFloat64 returns a normally distributed float64 in the range
+// [-math.MaxFloat64, +math.MaxFloat64] with
+// standard normal distribution (mean = 0, stddev = 1).
+// To produce a different normal distribution, callers can
+// adjust the output using:
+//
+// sample = NormFloat64() * desiredStdDev + desiredMean
+func (r *Rand) NormFloat64() float64 {
+ for {
+ j := int32(r.Uint32()) // Possibly negative
+ i := j & 0x7F
+ x := float64(j) * float64(wn[i])
+ if absInt32(j) < kn[i] {
+ // This case should be hit better than 99% of the time.
+ return x
+ }
+
+ if i == 0 {
+ // This extra work is only required for the base strip.
+ for {
+ x = -math.Log(r.Float64()) * (1.0 / rn)
+ y := -math.Log(r.Float64())
+ if y+y >= x*x {
+ break
+ }
+ }
+ if j > 0 {
+ return rn + x
+ }
+ return -rn - x
+ }
+ if fn[i]+float32(r.Float64())*(fn[i-1]-fn[i]) < float32(math.Exp(-.5*x*x)) {
+ return x
+ }
+ }
+}
+
+var kn = [128]uint32{
+ 0x76ad2212, 0x0, 0x600f1b53, 0x6ce447a6, 0x725b46a2,
+ 0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d,
+ 0x7adf629f, 0x7b5682a6, 0x7bb8a8c6, 0x7c0ae722, 0x7c50cce7,
+ 0x7c8cec5b, 0x7cc12cd6, 0x7ceefed2, 0x7d177e0b, 0x7d3b8883,
+ 0x7d5bce6c, 0x7d78dd64, 0x7d932886, 0x7dab0e57, 0x7dc0dd30,
+ 0x7dd4d688, 0x7de73185, 0x7df81cea, 0x7e07c0a3, 0x7e163efa,
+ 0x7e23b587, 0x7e303dfd, 0x7e3beec2, 0x7e46db77, 0x7e51155d,
+ 0x7e5aabb3, 0x7e63abf7, 0x7e6c222c, 0x7e741906, 0x7e7b9a18,
+ 0x7e82adfa, 0x7e895c63, 0x7e8fac4b, 0x7e95a3fb, 0x7e9b4924,
+ 0x7ea0a0ef, 0x7ea5b00d, 0x7eaa7ac3, 0x7eaf04f3, 0x7eb3522a,
+ 0x7eb765a5, 0x7ebb4259, 0x7ebeeafd, 0x7ec2620a, 0x7ec5a9c4,
+ 0x7ec8c441, 0x7ecbb365, 0x7ece78ed, 0x7ed11671, 0x7ed38d62,
+ 0x7ed5df12, 0x7ed80cb4, 0x7eda175c, 0x7edc0005, 0x7eddc78e,
+ 0x7edf6ebf, 0x7ee0f647, 0x7ee25ebe, 0x7ee3a8a9, 0x7ee4d473,
+ 0x7ee5e276, 0x7ee6d2f5, 0x7ee7a620, 0x7ee85c10, 0x7ee8f4cd,
+ 0x7ee97047, 0x7ee9ce59, 0x7eea0eca, 0x7eea3147, 0x7eea3568,
+ 0x7eea1aab, 0x7ee9e071, 0x7ee98602, 0x7ee90a88, 0x7ee86d08,
+ 0x7ee7ac6a, 0x7ee6c769, 0x7ee5bc9c, 0x7ee48a67, 0x7ee32efc,
+ 0x7ee1a857, 0x7edff42f, 0x7ede0ffa, 0x7edbf8d9, 0x7ed9ab94,
+ 0x7ed7248d, 0x7ed45fae, 0x7ed1585c, 0x7ece095f, 0x7eca6ccb,
+ 0x7ec67be2, 0x7ec22eee, 0x7ebd7d1a, 0x7eb85c35, 0x7eb2c075,
+ 0x7eac9c20, 0x7ea5df27, 0x7e9e769f, 0x7e964c16, 0x7e8d44ba,
+ 0x7e834033, 0x7e781728, 0x7e6b9933, 0x7e5d8a1a, 0x7e4d9ded,
+ 0x7e3b737a, 0x7e268c2f, 0x7e0e3ff5, 0x7df1aa5d, 0x7dcf8c72,
+ 0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a,
+ 0x7ba90bdc, 0x7a722176, 0x77d664e5,
+}
+var wn = [128]float32{
+ 1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10,
+ 2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10,
+ 2.9073963e-10, 3.042997e-10, 3.1699796e-10, 3.289802e-10,
+ 3.4035738e-10, 3.5121603e-10, 3.616251e-10, 3.7164058e-10,
+ 3.8130857e-10, 3.9066758e-10, 3.9975012e-10, 4.08584e-10,
+ 4.1719309e-10, 4.2559822e-10, 4.338176e-10, 4.418672e-10,
+ 4.497613e-10, 4.5751258e-10, 4.651324e-10, 4.7263105e-10,
+ 4.8001775e-10, 4.87301e-10, 4.944885e-10, 5.015873e-10,
+ 5.0860405e-10, 5.155446e-10, 5.2241467e-10, 5.2921934e-10,
+ 5.359635e-10, 5.426517e-10, 5.4928817e-10, 5.5587696e-10,
+ 5.624219e-10, 5.6892646e-10, 5.753941e-10, 5.818282e-10,
+ 5.882317e-10, 5.946077e-10, 6.00959e-10, 6.072884e-10,
+ 6.135985e-10, 6.19892e-10, 6.2617134e-10, 6.3243905e-10,
+ 6.386974e-10, 6.449488e-10, 6.511956e-10, 6.5744005e-10,
+ 6.6368433e-10, 6.699307e-10, 6.7618144e-10, 6.824387e-10,
+ 6.8870465e-10, 6.949815e-10, 7.012715e-10, 7.075768e-10,
+ 7.1389966e-10, 7.202424e-10, 7.266073e-10, 7.329966e-10,
+ 7.394128e-10, 7.4585826e-10, 7.5233547e-10, 7.58847e-10,
+ 7.653954e-10, 7.719835e-10, 7.7861395e-10, 7.852897e-10,
+ 7.920138e-10, 7.987892e-10, 8.0561924e-10, 8.125073e-10,
+ 8.194569e-10, 8.2647167e-10, 8.3355556e-10, 8.407127e-10,
+ 8.479473e-10, 8.55264e-10, 8.6266755e-10, 8.7016316e-10,
+ 8.777562e-10, 8.8545243e-10, 8.932582e-10, 9.0117996e-10,
+ 9.09225e-10, 9.174008e-10, 9.2571584e-10, 9.341788e-10,
+ 9.427997e-10, 9.515889e-10, 9.605579e-10, 9.697193e-10,
+ 9.790869e-10, 9.88676e-10, 9.985036e-10, 1.0085882e-09,
+ 1.0189509e-09, 1.0296151e-09, 1.0406069e-09, 1.0519566e-09,
+ 1.063698e-09, 1.0758702e-09, 1.0885183e-09, 1.1016947e-09,
+ 1.1154611e-09, 1.1298902e-09, 1.1450696e-09, 1.1611052e-09,
+ 1.1781276e-09, 1.1962995e-09, 1.2158287e-09, 1.2369856e-09,
+ 1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09,
+ 1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09,
+}
+var fn = [128]float32{
+ 1, 0.9635997, 0.9362827, 0.9130436, 0.89228165, 0.87324303,
+ 0.8555006, 0.8387836, 0.8229072, 0.8077383, 0.793177,
+ 0.7791461, 0.7655842, 0.7524416, 0.73967725, 0.7272569,
+ 0.7151515, 0.7033361, 0.69178915, 0.68049186, 0.6694277,
+ 0.658582, 0.6479418, 0.63749546, 0.6272325, 0.6171434,
+ 0.6072195, 0.5974532, 0.58783704, 0.5783647, 0.56903,
+ 0.5598274, 0.5507518, 0.54179835, 0.5329627, 0.52424055,
+ 0.5156282, 0.50712204, 0.49871865, 0.49041483, 0.48220766,
+ 0.4740943, 0.46607214, 0.4581387, 0.45029163, 0.44252872,
+ 0.43484783, 0.427247, 0.41972435, 0.41227803, 0.40490642,
+ 0.39760786, 0.3903808, 0.3832238, 0.37613547, 0.36911446,
+ 0.3621595, 0.35526937, 0.34844297, 0.34167916, 0.33497685,
+ 0.3283351, 0.3217529, 0.3152294, 0.30876362, 0.30235484,
+ 0.29600215, 0.28970486, 0.2834622, 0.2772735, 0.27113807,
+ 0.2650553, 0.25902456, 0.2530453, 0.24711695, 0.241239,
+ 0.23541094, 0.22963232, 0.2239027, 0.21822165, 0.21258877,
+ 0.20700371, 0.20146611, 0.19597565, 0.19053204, 0.18513499,
+ 0.17978427, 0.17447963, 0.1692209, 0.16400786, 0.15884037,
+ 0.15371831, 0.14864157, 0.14361008, 0.13862377, 0.13368265,
+ 0.12878671, 0.12393598, 0.119130544, 0.11437051, 0.10965602,
+ 0.104987256, 0.10036444, 0.095787846, 0.0912578, 0.08677467,
+ 0.0823389, 0.077950984, 0.073611505, 0.06932112, 0.06508058,
+ 0.06089077, 0.056752663, 0.0526674, 0.048636295, 0.044660863,
+ 0.040742867, 0.03688439, 0.033087887, 0.029356318,
+ 0.025693292, 0.022103304, 0.018592102, 0.015167298,
+ 0.011839478, 0.008624485, 0.005548995, 0.0026696292,
+}
diff --git a/rand/race_test.go b/rand/race_test.go
new file mode 100644
index 0000000..376224f
--- /dev/null
+++ b/rand/race_test.go
@@ -0,0 +1,48 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+ "sync"
+ "testing"
+)
+
+// TestConcurrent exercises the rand API concurrently, triggering situations
+// where the race detector is likely to detect issues.
+func TestConcurrent(t *testing.T) {
+ const (
+ numRoutines = 10
+ numCycles = 10
+ )
+ var wg sync.WaitGroup
+ defer wg.Wait()
+ wg.Add(numRoutines)
+ for i := 0; i < numRoutines; i++ {
+ go func(i int) {
+ defer wg.Done()
+ buf := make([]byte, 997)
+ for j := 0; j < numCycles; j++ {
+ var seed uint64
+ seed += uint64(ExpFloat64())
+ seed += uint64(Float32())
+ seed += uint64(Float64())
+ seed += uint64(Intn(Int()))
+ seed += uint64(Int31n(Int31()))
+ seed += uint64(Int63n(Int63()))
+ seed += uint64(NormFloat64())
+ seed += uint64(Uint32())
+ seed += uint64(Uint64())
+ for _, p := range Perm(10) {
+ seed += uint64(p)
+ }
+ Read(buf)
+ for _, b := range buf {
+ seed += uint64(b)
+ }
+ Seed(uint64(i*j) * seed)
+ }
+ }(i)
+ }
+}
diff --git a/rand/rand.go b/rand/rand.go
new file mode 100644
index 0000000..ee6161b
--- /dev/null
+++ b/rand/rand.go
@@ -0,0 +1,372 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package rand implements pseudo-random number generators.
+//
+// Random numbers are generated by a Source. Top-level functions, such as
+// Float64 and Int, use a default shared Source that produces a deterministic
+// sequence of values each time a program is run. Use the Seed function to
+// initialize the default Source if different behavior is required for each run.
+// The default Source, a LockedSource, is safe for concurrent use by multiple
+// goroutines, but Sources created by NewSource are not. However, Sources are small
+// and it is reasonable to have a separate Source for each goroutine, seeded
+// differently, to avoid locking.
+//
+// For random numbers suitable for security-sensitive work, see the crypto/rand
+// package.
+package rand
+
+import "sync"
+
+// A Source represents a source of uniformly-distributed
+// pseudo-random int64 values in the range [0, 1<<64).
+type Source interface {
+ Uint64() uint64
+ Seed(seed uint64)
+}
+
+// NewSource returns a new pseudo-random Source seeded with the given value.
+func NewSource(seed uint64) Source {
+ var rng PCGSource
+ rng.Seed(seed)
+ return &rng
+}
+
+// A Rand is a source of random numbers.
+type Rand struct {
+ src Source
+
+ // readVal contains remainder of 64-bit integer used for bytes
+ // generation during most recent Read call.
+ // It is saved so next Read call can start where the previous
+ // one finished.
+ readVal uint64
+ // readPos indicates the number of low-order bytes of readVal
+ // that are still valid.
+ readPos int8
+}
+
+// New returns a new Rand that uses random values from src
+// to generate other random values.
+func New(src Source) *Rand {
+ return &Rand{src: src}
+}
+
+// Seed uses the provided seed value to initialize the generator to a deterministic state.
+// Seed should not be called concurrently with any other Rand method.
+func (r *Rand) Seed(seed uint64) {
+ if lk, ok := r.src.(*LockedSource); ok {
+ lk.seedPos(seed, &r.readPos)
+ return
+ }
+
+ r.src.Seed(seed)
+ r.readPos = 0
+}
+
+// Uint64 returns a pseudo-random 64-bit integer as a uint64.
+func (r *Rand) Uint64() uint64 { return r.src.Uint64() }
+
+// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
+func (r *Rand) Int63() int64 { return int64(r.src.Uint64() &^ (1 << 63)) }
+
+// Uint32 returns a pseudo-random 32-bit value as a uint32.
+func (r *Rand) Uint32() uint32 { return uint32(r.Uint64() >> 32) }
+
+// Int31 returns a non-negative pseudo-random 31-bit integer as an int32.
+func (r *Rand) Int31() int32 { return int32(r.Uint64() >> 33) }
+
+// Int returns a non-negative pseudo-random int.
+func (r *Rand) Int() int {
+ u := uint(r.Uint64())
+ return int(u << 1 >> 1) // clear sign bit.
+}
+
+const maxUint64 = (1 << 64) - 1
+
+// Uint64n returns, as a uint64, a pseudo-random number in [0,n).
+// It is guaranteed more uniform than taking a Source value mod n
+// for any n that is not a power of 2.
+func (r *Rand) Uint64n(n uint64) uint64 {
+ if n&(n-1) == 0 { // n is power of two, can mask
+ if n == 0 {
+ panic("invalid argument to Uint64n")
+ }
+ return r.Uint64() & (n - 1)
+ }
+ // If n does not divide v, to avoid bias we must not use
+ // a v that is within maxUint64%n of the top of the range.
+ v := r.Uint64()
+ if v > maxUint64-n { // Fast check.
+ ceiling := maxUint64 - maxUint64%n
+ for v >= ceiling {
+ v = r.Uint64()
+ }
+ }
+
+ return v % n
+}
+
+// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n).
+// It panics if n <= 0.
+func (r *Rand) Int63n(n int64) int64 {
+ if n <= 0 {
+ panic("invalid argument to Int63n")
+ }
+ return int64(r.Uint64n(uint64(n)))
+}
+
+// Int31n returns, as an int32, a non-negative pseudo-random number in [0,n).
+// It panics if n <= 0.
+func (r *Rand) Int31n(n int32) int32 {
+ if n <= 0 {
+ panic("invalid argument to Int31n")
+ }
+ // TODO: Avoid some 64-bit ops to make it more efficient on 32-bit machines.
+ return int32(r.Uint64n(uint64(n)))
+}
+
+// Intn returns, as an int, a non-negative pseudo-random number in [0,n).
+// It panics if n <= 0.
+func (r *Rand) Intn(n int) int {
+ if n <= 0 {
+ panic("invalid argument to Intn")
+ }
+ // TODO: Avoid some 64-bit ops to make it more efficient on 32-bit machines.
+ return int(r.Uint64n(uint64(n)))
+}
+
+// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0).
+func (r *Rand) Float64() float64 {
+ // There is one bug in the value stream: r.Int63() may be so close
+ // to 1<<63 that the division rounds up to 1.0, and we've guaranteed
+ // that the result is always less than 1.0.
+ //
+ // We tried to fix this by mapping 1.0 back to 0.0, but since float64
+ // values near 0 are much denser than near 1, mapping 1 to 0 caused
+ // a theoretically significant overshoot in the probability of returning 0.
+ // Instead of that, if we round up to 1, just try again.
+ // Getting 1 only happens 1/2⁡³ of the time, so most clients
+ // will not observe it anyway.
+again:
+ f := float64(r.Uint64n(1<<53)) / (1 << 53)
+ if f == 1.0 {
+ goto again // resample; this branch is taken O(never)
+ }
+ return f
+}
+
+// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0).
+func (r *Rand) Float32() float32 {
+ // We do not want to return 1.0.
+ // This only happens 1/2²⁴ of the time (plus the 1/2⁡³ of the time in Float64).
+again:
+ f := float32(r.Float64())
+ if f == 1 {
+ goto again // resample; this branch is taken O(very rarely)
+ }
+ return f
+}
+
+// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
+func (r *Rand) Perm(n int) []int {
+ m := make([]int, n)
+ // In the following loop, the iteration when i=0 always swaps m[0] with m[0].
+ // A change to remove this useless iteration is to assign 1 to i in the init
+ // statement. But Perm also effects r. Making this change will affect
+ // the final state of r. So this change can't be made for compatibility
+ // reasons for Go 1.
+ for i := 0; i < n; i++ {
+ j := r.Intn(i + 1)
+ m[i] = m[j]
+ m[j] = i
+ }
+ return m
+}
+
+// Shuffle pseudo-randomizes the order of elements.
+// n is the number of elements. Shuffle panics if n < 0.
+// swap swaps the elements with indexes i and j.
+func (r *Rand) Shuffle(n int, swap func(i, j int)) {
+ if n < 0 {
+ panic("invalid argument to Shuffle")
+ }
+
+ // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
+ // Shuffle really ought not be called with n that doesn't fit in 32 bits.
+ // Not only will it take a very long time, but with 2Β³ΒΉ! possible permutations,
+ // there's no way that any PRNG can have a big enough internal state to
+ // generate even a minuscule percentage of the possible permutations.
+ // Nevertheless, the right API signature accepts an int n, so handle it as best we can.
+ i := n - 1
+ for ; i > 1<<31-1-1; i-- {
+ j := int(r.Int63n(int64(i + 1)))
+ swap(i, j)
+ }
+ for ; i > 0; i-- {
+ j := int(r.Int31n(int32(i + 1)))
+ swap(i, j)
+ }
+}
+
+// Read generates len(p) random bytes and writes them into p. It
+// always returns len(p) and a nil error.
+// Read should not be called concurrently with any other Rand method unless
+// the underlying source is a LockedSource.
+func (r *Rand) Read(p []byte) (n int, err error) {
+ if lk, ok := r.src.(*LockedSource); ok {
+ return lk.Read(p, &r.readVal, &r.readPos)
+ }
+ return read(p, r.src, &r.readVal, &r.readPos)
+}
+
+func read(p []byte, src Source, readVal *uint64, readPos *int8) (n int, err error) {
+ pos := *readPos
+ val := *readVal
+ rng, _ := src.(*PCGSource)
+ for n = 0; n < len(p); n++ {
+ if pos == 0 {
+ if rng != nil {
+ val = rng.Uint64()
+ } else {
+ val = src.Uint64()
+ }
+ pos = 8
+ }
+ p[n] = byte(val)
+ val >>= 8
+ pos--
+ }
+ *readPos = pos
+ *readVal = val
+ return
+}
+
+/*
+ * Top-level convenience functions
+ */
+
+var globalRand = New(&LockedSource{src: *NewSource(1).(*PCGSource)})
+
+// Type assert that globalRand's source is a LockedSource whose src is a PCGSource.
+var _ PCGSource = globalRand.src.(*LockedSource).src
+
+// Seed uses the provided seed value to initialize the default Source to a
+// deterministic state. If Seed is not called, the generator behaves as
+// if seeded by Seed(1).
+// Seed, unlike the Rand.Seed method, is safe for concurrent use.
+func Seed(seed uint64) { globalRand.Seed(seed) }
+
+// Int63 returns a non-negative pseudo-random 63-bit integer as an int64
+// from the default Source.
+func Int63() int64 { return globalRand.Int63() }
+
+// Uint32 returns a pseudo-random 32-bit value as a uint32
+// from the default Source.
+func Uint32() uint32 { return globalRand.Uint32() }
+
+// Uint64 returns a pseudo-random 64-bit value as a uint64
+// from the default Source.
+func Uint64() uint64 { return globalRand.Uint64() }
+
+// Int31 returns a non-negative pseudo-random 31-bit integer as an int32
+// from the default Source.
+func Int31() int32 { return globalRand.Int31() }
+
+// Int returns a non-negative pseudo-random int from the default Source.
+func Int() int { return globalRand.Int() }
+
+// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n)
+// from the default Source.
+// It panics if n <= 0.
+func Int63n(n int64) int64 { return globalRand.Int63n(n) }
+
+// Int31n returns, as an int32, a non-negative pseudo-random number in [0,n)
+// from the default Source.
+// It panics if n <= 0.
+func Int31n(n int32) int32 { return globalRand.Int31n(n) }
+
+// Intn returns, as an int, a non-negative pseudo-random number in [0,n)
+// from the default Source.
+// It panics if n <= 0.
+func Intn(n int) int { return globalRand.Intn(n) }
+
+// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0)
+// from the default Source.
+func Float64() float64 { return globalRand.Float64() }
+
+// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0)
+// from the default Source.
+func Float32() float32 { return globalRand.Float32() }
+
+// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n)
+// from the default Source.
+func Perm(n int) []int { return globalRand.Perm(n) }
+
+// Shuffle pseudo-randomizes the order of elements using the default Source.
+// n is the number of elements. Shuffle panics if n < 0.
+// swap swaps the elements with indexes i and j.
+func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) }
+
+// Read generates len(p) random bytes from the default Source and
+// writes them into p. It always returns len(p) and a nil error.
+// Read, unlike the Rand.Read method, is safe for concurrent use.
+func Read(p []byte) (n int, err error) { return globalRand.Read(p) }
+
+// NormFloat64 returns a normally distributed float64 in the range
+// [-math.MaxFloat64, +math.MaxFloat64] with
+// standard normal distribution (mean = 0, stddev = 1)
+// from the default Source.
+// To produce a different normal distribution, callers can
+// adjust the output using:
+//
+// sample = NormFloat64() * desiredStdDev + desiredMean
+func NormFloat64() float64 { return globalRand.NormFloat64() }
+
+// ExpFloat64 returns an exponentially distributed float64 in the range
+// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter
+// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source.
+// To produce a distribution with a different rate parameter,
+// callers can adjust the output using:
+//
+// sample = ExpFloat64() / desiredRateParameter
+func ExpFloat64() float64 { return globalRand.ExpFloat64() }
+
+// LockedSource is an implementation of Source that is concurrency-safe.
+// A Rand using a LockedSource is safe for concurrent use.
+//
+// The zero value of LockedSource is valid, but should be seeded before use.
+type LockedSource struct {
+ lk sync.Mutex
+ src PCGSource
+}
+
+func (s *LockedSource) Uint64() (n uint64) {
+ s.lk.Lock()
+ n = s.src.Uint64()
+ s.lk.Unlock()
+ return
+}
+
+func (s *LockedSource) Seed(seed uint64) {
+ s.lk.Lock()
+ s.src.Seed(seed)
+ s.lk.Unlock()
+}
+
+// seedPos implements Seed for a LockedSource without a race condiiton.
+func (s *LockedSource) seedPos(seed uint64, readPos *int8) {
+ s.lk.Lock()
+ s.src.Seed(seed)
+ *readPos = 0
+ s.lk.Unlock()
+}
+
+// Read implements Read for a LockedSource.
+func (s *LockedSource) Read(p []byte, readVal *uint64, readPos *int8) (n int, err error) {
+ s.lk.Lock()
+ n, err = read(p, &s.src, readVal, readPos)
+ s.lk.Unlock()
+ return
+}
diff --git a/rand/rand_test.go b/rand/rand_test.go
new file mode 100644
index 0000000..4383e4f
--- /dev/null
+++ b/rand/rand_test.go
@@ -0,0 +1,617 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "runtime"
+ "testing"
+ "testing/iotest"
+ "time"
+)
+
+const (
+ numTestSamples = 10000
+)
+
+type statsResults struct {
+ mean float64
+ stddev float64
+ closeEnough float64
+ maxError float64
+}
+
+func max(a, b float64) float64 {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func nearEqual(a, b, closeEnough, maxError float64) bool {
+ absDiff := math.Abs(a - b)
+ if absDiff < closeEnough { // Necessary when one value is zero and one value is close to zero.
+ return true
+ }
+ return absDiff/max(math.Abs(a), math.Abs(b)) < maxError
+}
+
+var testSeeds = []uint64{1, 1754801282, 1698661970, 1550503961}
+
+// checkSimilarDistribution returns success if the mean and stddev of the
+// two statsResults are similar.
+func (this *statsResults) checkSimilarDistribution(expected *statsResults) error {
+ if !nearEqual(this.mean, expected.mean, expected.closeEnough, expected.maxError) {
+ s := fmt.Sprintf("mean %v != %v (allowed error %v, %v)", this.mean, expected.mean, expected.closeEnough, expected.maxError)
+ fmt.Println(s)
+ return errors.New(s)
+ }
+ if !nearEqual(this.stddev, expected.stddev, 0, expected.maxError) {
+ s := fmt.Sprintf("stddev %v != %v (allowed error %v, %v)", this.stddev, expected.stddev, expected.closeEnough, expected.maxError)
+ fmt.Println(s)
+ return errors.New(s)
+ }
+ return nil
+}
+
+func getStatsResults(samples []float64) *statsResults {
+ res := new(statsResults)
+ var sum, squaresum float64
+ for _, s := range samples {
+ sum += s
+ squaresum += s * s
+ }
+ res.mean = sum / float64(len(samples))
+ res.stddev = math.Sqrt(squaresum/float64(len(samples)) - res.mean*res.mean)
+ return res
+}
+
+func checkSampleDistribution(t *testing.T, samples []float64, expected *statsResults) {
+ t.Helper()
+ actual := getStatsResults(samples)
+ err := actual.checkSimilarDistribution(expected)
+ if err != nil {
+ t.Errorf(err.Error())
+ }
+}
+
+func checkSampleSliceDistributions(t *testing.T, samples []float64, nslices int, expected *statsResults) {
+ t.Helper()
+ chunk := len(samples) / nslices
+ for i := 0; i < nslices; i++ {
+ low := i * chunk
+ var high int
+ if i == nslices-1 {
+ high = len(samples) - 1
+ } else {
+ high = (i + 1) * chunk
+ }
+ checkSampleDistribution(t, samples[low:high], expected)
+ }
+}
+
+//
+// Normal distribution tests
+//
+
+func generateNormalSamples(nsamples int, mean, stddev float64, seed uint64) []float64 {
+ r := New(NewSource(seed))
+ samples := make([]float64, nsamples)
+ for i := range samples {
+ samples[i] = r.NormFloat64()*stddev + mean
+ }
+ return samples
+}
+
+func testNormalDistribution(t *testing.T, nsamples int, mean, stddev float64, seed uint64) {
+ //fmt.Printf("testing nsamples=%v mean=%v stddev=%v seed=%v\n", nsamples, mean, stddev, seed);
+
+ samples := generateNormalSamples(nsamples, mean, stddev, seed)
+ errorScale := max(1.0, stddev) // Error scales with stddev
+ expected := &statsResults{mean, stddev, 0.10 * errorScale, 0.08 * errorScale}
+
+ // Make sure that the entire set matches the expected distribution.
+ checkSampleDistribution(t, samples, expected)
+
+ // Make sure that each half of the set matches the expected distribution.
+ checkSampleSliceDistributions(t, samples, 2, expected)
+
+ // Make sure that each 7th of the set matches the expected distribution.
+ checkSampleSliceDistributions(t, samples, 7, expected)
+}
+
+// Actual tests
+
+func TestStandardNormalValues(t *testing.T) {
+ for _, seed := range testSeeds {
+ testNormalDistribution(t, numTestSamples, 0, 1, seed)
+ }
+}
+
+func TestNonStandardNormalValues(t *testing.T) {
+ sdmax := 1000.0
+ mmax := 1000.0
+ if testing.Short() {
+ sdmax = 5
+ mmax = 5
+ }
+ for sd := 0.5; sd < sdmax; sd *= 2 {
+ for m := 0.5; m < mmax; m *= 2 {
+ for _, seed := range testSeeds {
+ testNormalDistribution(t, numTestSamples, m, sd, seed)
+ if testing.Short() {
+ break
+ }
+ }
+ }
+ }
+}
+
+//
+// Exponential distribution tests
+//
+
+func generateExponentialSamples(nsamples int, rate float64, seed uint64) []float64 {
+ r := New(NewSource(seed))
+ samples := make([]float64, nsamples)
+ for i := range samples {
+ samples[i] = r.ExpFloat64() / rate
+ }
+ return samples
+}
+
+func testExponentialDistribution(t *testing.T, nsamples int, rate float64, seed uint64) {
+ //fmt.Printf("testing nsamples=%v rate=%v seed=%v\n", nsamples, rate, seed)
+
+ mean := 1 / rate
+ stddev := mean
+
+ samples := generateExponentialSamples(nsamples, rate, seed)
+ errorScale := max(1.0, 1/rate) // Error scales with the inverse of the rate
+ expected := &statsResults{mean, stddev, 0.10 * errorScale, 0.20 * errorScale}
+
+ // Make sure that the entire set matches the expected distribution.
+ checkSampleDistribution(t, samples, expected)
+
+ // Make sure that each half of the set matches the expected distribution.
+ checkSampleSliceDistributions(t, samples, 2, expected)
+
+ // Make sure that each 7th of the set matches the expected distribution.
+ checkSampleSliceDistributions(t, samples, 7, expected)
+}
+
+// Actual tests
+
+func TestStandardExponentialValues(t *testing.T) {
+ for _, seed := range testSeeds {
+ testExponentialDistribution(t, numTestSamples, 1, seed)
+ }
+}
+
+func TestNonStandardExponentialValues(t *testing.T) {
+ for rate := 0.05; rate < 10; rate *= 2 {
+ for _, seed := range testSeeds {
+ testExponentialDistribution(t, numTestSamples, rate, seed)
+ if testing.Short() {
+ break
+ }
+ }
+ }
+}
+
+//
+// Table generation tests
+//
+
+func initNorm() (testKn []uint32, testWn, testFn []float32) {
+ const m1 = 1 << 31
+ var (
+ dn float64 = rn
+ tn = dn
+ vn float64 = 9.91256303526217e-3
+ )
+
+ testKn = make([]uint32, 128)
+ testWn = make([]float32, 128)
+ testFn = make([]float32, 128)
+
+ q := vn / math.Exp(-0.5*dn*dn)
+ testKn[0] = uint32((dn / q) * m1)
+ testKn[1] = 0
+ testWn[0] = float32(q / m1)
+ testWn[127] = float32(dn / m1)
+ testFn[0] = 1.0
+ testFn[127] = float32(math.Exp(-0.5 * dn * dn))
+ for i := 126; i >= 1; i-- {
+ dn = math.Sqrt(-2.0 * math.Log(vn/dn+math.Exp(-0.5*dn*dn)))
+ testKn[i+1] = uint32((dn / tn) * m1)
+ tn = dn
+ testFn[i] = float32(math.Exp(-0.5 * dn * dn))
+ testWn[i] = float32(dn / m1)
+ }
+ return
+}
+
+func initExp() (testKe []uint32, testWe, testFe []float32) {
+ const m2 = 1 << 32
+ var (
+ de float64 = re
+ te = de
+ ve float64 = 3.9496598225815571993e-3
+ )
+
+ testKe = make([]uint32, 256)
+ testWe = make([]float32, 256)
+ testFe = make([]float32, 256)
+
+ q := ve / math.Exp(-de)
+ testKe[0] = uint32((de / q) * m2)
+ testKe[1] = 0
+ testWe[0] = float32(q / m2)
+ testWe[255] = float32(de / m2)
+ testFe[0] = 1.0
+ testFe[255] = float32(math.Exp(-de))
+ for i := 254; i >= 1; i-- {
+ de = -math.Log(ve/de + math.Exp(-de))
+ testKe[i+1] = uint32((de / te) * m2)
+ te = de
+ testFe[i] = float32(math.Exp(-de))
+ testWe[i] = float32(de / m2)
+ }
+ return
+}
+
+// compareUint32Slices returns the first index where the two slices
+// disagree, or <0 if the lengths are the same and all elements
+// are identical.
+func compareUint32Slices(s1, s2 []uint32) int {
+ if len(s1) != len(s2) {
+ if len(s1) > len(s2) {
+ return len(s2) + 1
+ }
+ return len(s1) + 1
+ }
+ for i := range s1 {
+ if s1[i] != s2[i] {
+ return i
+ }
+ }
+ return -1
+}
+
+// compareFloat32Slices returns the first index where the two slices
+// disagree, or <0 if the lengths are the same and all elements
+// are identical.
+func compareFloat32Slices(s1, s2 []float32) int {
+ if len(s1) != len(s2) {
+ if len(s1) > len(s2) {
+ return len(s2) + 1
+ }
+ return len(s1) + 1
+ }
+ for i := range s1 {
+ if !nearEqual(float64(s1[i]), float64(s2[i]), 0, 1e-7) {
+ return i
+ }
+ }
+ return -1
+}
+
+func TestNormTables(t *testing.T) {
+ testKn, testWn, testFn := initNorm()
+ if i := compareUint32Slices(kn[0:], testKn); i >= 0 {
+ t.Errorf("kn disagrees at index %v; %v != %v", i, kn[i], testKn[i])
+ }
+ if i := compareFloat32Slices(wn[0:], testWn); i >= 0 {
+ t.Errorf("wn disagrees at index %v; %v != %v", i, wn[i], testWn[i])
+ }
+ if i := compareFloat32Slices(fn[0:], testFn); i >= 0 {
+ t.Errorf("fn disagrees at index %v; %v != %v", i, fn[i], testFn[i])
+ }
+}
+
+func TestExpTables(t *testing.T) {
+ testKe, testWe, testFe := initExp()
+ if i := compareUint32Slices(ke[0:], testKe); i >= 0 {
+ t.Errorf("ke disagrees at index %v; %v != %v", i, ke[i], testKe[i])
+ }
+ if i := compareFloat32Slices(we[0:], testWe); i >= 0 {
+ t.Errorf("we disagrees at index %v; %v != %v", i, we[i], testWe[i])
+ }
+ if i := compareFloat32Slices(fe[0:], testFe); i >= 0 {
+ t.Errorf("fe disagrees at index %v; %v != %v", i, fe[i], testFe[i])
+ }
+}
+
+func hasSlowFloatingPoint() bool {
+ switch runtime.GOARCH {
+ case "arm":
+ return os.Getenv("GOARM") == "5"
+ case "mips", "mipsle", "mips64", "mips64le":
+ // Be conservative and assume that all mips boards
+ // have emulated floating point.
+ // TODO: detect what it actually has.
+ return true
+ }
+ return false
+}
+
+func TestFloat32(t *testing.T) {
+ // For issue 6721, the problem came after 7533753 calls, so check 10e6.
+ num := int(10e6)
+ // But do the full amount only on builders (not locally).
+ // But ARM5 floating point emulation is slow (Issue 10749), so
+ // do less for that builder:
+ if testing.Short() && hasSlowFloatingPoint() { // TODO: (testenv.Builder() == "" || hasSlowFloatingPoint())
+ num /= 100 // 1.72 seconds instead of 172 seconds
+ }
+
+ r := New(NewSource(1))
+ for ct := 0; ct < num; ct++ {
+ f := r.Float32()
+ if f >= 1 {
+ t.Fatal("Float32() should be in range [0,1). ct:", ct, "f:", f)
+ }
+ }
+}
+
+func testReadUniformity(t *testing.T, n int, seed uint64) {
+ r := New(NewSource(seed))
+ buf := make([]byte, n)
+ nRead, err := r.Read(buf)
+ if err != nil {
+ t.Errorf("Read err %v", err)
+ }
+ if nRead != n {
+ t.Errorf("Read returned unexpected n; %d != %d", nRead, n)
+ }
+
+ // Expect a uniform distribution of byte values, which lie in [0, 255].
+ var (
+ mean = 255.0 / 2
+ stddev = 256.0 / math.Sqrt(12.0)
+ errorScale = stddev / math.Sqrt(float64(n))
+ )
+
+ expected := &statsResults{mean, stddev, 0.10 * errorScale, 0.08 * errorScale}
+
+ // Cast bytes as floats to use the common distribution-validity checks.
+ samples := make([]float64, n)
+ for i, val := range buf {
+ samples[i] = float64(val)
+ }
+ // Make sure that the entire set matches the expected distribution.
+ checkSampleDistribution(t, samples, expected)
+}
+
+func TestReadUniformity(t *testing.T) {
+ testBufferSizes := []int{
+ 2, 4, 7, 64, 1024, 1 << 16, 1 << 20,
+ }
+ for _, seed := range testSeeds {
+ for _, n := range testBufferSizes {
+ testReadUniformity(t, n, seed)
+ }
+ }
+}
+
+func TestReadEmpty(t *testing.T) {
+ r := New(NewSource(1))
+ buf := make([]byte, 0)
+ n, err := r.Read(buf)
+ if err != nil {
+ t.Errorf("Read err into empty buffer; %v", err)
+ }
+ if n != 0 {
+ t.Errorf("Read into empty buffer returned unexpected n of %d", n)
+ }
+}
+
+func TestReadByOneByte(t *testing.T) {
+ r := New(NewSource(1))
+ b1 := make([]byte, 100)
+ _, err := io.ReadFull(iotest.OneByteReader(r), b1)
+ if err != nil {
+ t.Errorf("read by one byte: %v", err)
+ }
+ r = New(NewSource(1))
+ b2 := make([]byte, 100)
+ _, err = r.Read(b2)
+ if err != nil {
+ t.Errorf("read: %v", err)
+ }
+ if !bytes.Equal(b1, b2) {
+ t.Errorf("read by one byte vs single read:\n%x\n%x", b1, b2)
+ }
+}
+
+func TestReadSeedReset(t *testing.T) {
+ r := New(NewSource(42))
+ b1 := make([]byte, 128)
+ _, err := r.Read(b1)
+ if err != nil {
+ t.Errorf("read: %v", err)
+ }
+ r.Seed(42)
+ b2 := make([]byte, 128)
+ _, err = r.Read(b2)
+ if err != nil {
+ t.Errorf("read: %v", err)
+ }
+ if !bytes.Equal(b1, b2) {
+ t.Errorf("mismatch after re-seed:\n%x\n%x", b1, b2)
+ }
+}
+
+func TestShuffleSmall(t *testing.T) {
+ // Check that Shuffle allows n=0 and n=1, but that swap is never called for them.
+ r := New(NewSource(1))
+ for n := 0; n <= 1; n++ {
+ r.Shuffle(n, func(i, j int) { t.Fatalf("swap called, n=%d i=%d j=%d", n, i, j) })
+ }
+}
+
+func TestPCGSourceRoundTrip(t *testing.T) {
+ var src PCGSource
+ src.Seed(uint64(time.Now().Unix()))
+
+ src.Uint64() // Step PRNG once to makes sure high and low are different.
+
+ buf, err := src.MarshalBinary()
+ if err != nil {
+ t.Errorf("unexpected error marshaling state: %v", err)
+ }
+
+ var dst PCGSource
+ // Get dst into a non-zero state.
+ dst.Seed(1)
+ for i := 0; i < 10; i++ {
+ dst.Uint64()
+ }
+
+ err = dst.UnmarshalBinary(buf)
+ if err != nil {
+ t.Errorf("unexpected error unmarshaling state: %v", err)
+ }
+
+ if dst != src {
+ t.Errorf("mismatch between generator states: got:%+v want:%+v", dst, src)
+ }
+}
+
+// Benchmarks
+
+func BenchmarkSource(b *testing.B) {
+ rng := NewSource(0)
+ for n := b.N; n > 0; n-- {
+ rng.Uint64()
+ }
+}
+
+func BenchmarkInt63Threadsafe(b *testing.B) {
+ for n := b.N; n > 0; n-- {
+ Int63()
+ }
+}
+
+func BenchmarkInt63ThreadsafeParallel(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Int63()
+ }
+ })
+}
+
+func BenchmarkInt63Unthreadsafe(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Int63()
+ }
+}
+
+func BenchmarkIntn1000(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Intn(1000)
+ }
+}
+
+func BenchmarkInt63n1000(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Int63n(1000)
+ }
+}
+
+func BenchmarkInt31n1000(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Int31n(1000)
+ }
+}
+
+func BenchmarkFloat32(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Float32()
+ }
+}
+
+func BenchmarkFloat64(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Float64()
+ }
+}
+
+func BenchmarkPerm3(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Perm(3)
+ }
+}
+
+func BenchmarkPerm30(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Perm(30)
+ }
+}
+
+func BenchmarkPerm30ViaShuffle(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ p := make([]int, 30)
+ for i := range p {
+ p[i] = i
+ }
+ r.Shuffle(30, func(i, j int) { p[i], p[j] = p[j], p[i] })
+ }
+}
+
+// BenchmarkShuffleOverhead uses a minimal swap function
+// to measure just the shuffling overhead.
+func BenchmarkShuffleOverhead(b *testing.B) {
+ r := New(NewSource(1))
+ for n := b.N; n > 0; n-- {
+ r.Shuffle(52, func(i, j int) {
+ if i < 0 || i >= 52 || j < 0 || j >= 52 {
+ b.Fatalf("bad swap(%d, %d)", i, j)
+ }
+ })
+ }
+}
+
+func BenchmarkRead3(b *testing.B) {
+ r := New(NewSource(1))
+ buf := make([]byte, 3)
+ b.ResetTimer()
+ for n := b.N; n > 0; n-- {
+ r.Read(buf)
+ }
+}
+
+func BenchmarkRead64(b *testing.B) {
+ r := New(NewSource(1))
+ buf := make([]byte, 64)
+ b.ResetTimer()
+ for n := b.N; n > 0; n-- {
+ r.Read(buf)
+ }
+}
+
+func BenchmarkRead1000(b *testing.B) {
+ r := New(NewSource(1))
+ buf := make([]byte, 1000)
+ b.ResetTimer()
+ for n := b.N; n > 0; n-- {
+ r.Read(buf)
+ }
+}
diff --git a/rand/regress_test.go b/rand/regress_test.go
new file mode 100644
index 0000000..e3a8f6e
--- /dev/null
+++ b/rand/regress_test.go
@@ -0,0 +1,490 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test that random number sequences generated by a specific seed
+// do not change from version to version.
+//
+// If the generator changes, the golden outputs need updating, and
+// client programs may break. Although the desire for compatibility
+// is not as stringent as in the original math/rand package,
+// when possible avoid changing the generator.
+
+package rand_test
+
+import (
+ "flag"
+ "fmt"
+ "reflect"
+ "testing"
+
+ . "golang.org/x/exp/rand"
+)
+
+var printgolden = flag.Bool("printgolden", false, "print golden results for regression test")
+
+// TestSource verifies that the output of the default Source is locked down.
+func TestSourceRegress(t *testing.T) {
+ src := NewSource(1)
+ var got [20]uint64
+ for i := range got {
+ got[i] = src.Uint64()
+ }
+ want := [20]uint64{
+ 0x34e936394905d167,
+ 0x817c0ef62fe4c731,
+ 0x937987e6e24f5a40,
+ 0x0c0a8307fe226199,
+ 0xf96363568d8bab56,
+ 0xbaef3af36bd02620,
+ 0x8f18e416eb6b936b,
+ 0x05a43fc149f3a67a,
+ 0xdab012eb3ce01697,
+ 0xf76c495a133c6aa9,
+ 0x304b24c5040ce457,
+ 0x47d77e0abb413159,
+ 0x52a810fa9e452f04,
+ 0x2d24b66380cf4780,
+ 0x5ec7691b92018ef5,
+ 0x5076dfa749261ea0,
+ 0xac8f11ad3941d213,
+ 0x13fa8d67de91db25,
+ 0xb50883a9893274eb,
+ 0xeb8f59263f9109ac,
+ }
+ if got != want {
+ t.Errorf("got:\n\t%#016x\nwant:\n\t%#016x", got, want)
+ if *printgolden {
+ for _, x := range got {
+ fmt.Printf("\t\t%#016x,\n", x)
+ }
+ }
+ }
+}
+
+// TestRegress validates that the output stream is locked down, for instance so
+// optimizations do not change the output. It iterates over methods of the
+// Rand type to find functions to evaluate and checks the first 20 results
+// against the golden results.
+func TestRegress(t *testing.T) {
+ var int32s = []int32{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1}
+ var int64s = []int64{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1, 1000000000000000000, 1 << 60, 1<<63 - 2, 1<<63 - 1}
+ var uint64s = []uint64{1, 10, 32, 1 << 20, 1<<20 + 1, 1000000000, 1 << 30, 1<<31 - 2, 1<<31 - 1, 1000000000000000000, 1 << 60, 1<<64 - 2, 1<<64 - 1}
+ var permSizes = []int{0, 1, 5, 8, 9, 10, 16}
+ var readBufferSizes = []int{1, 7, 8, 9, 10}
+ r := New(NewSource(0))
+
+ rv := reflect.ValueOf(r)
+ n := rv.NumMethod()
+ p := 0
+ if *printgolden {
+ fmt.Printf("var regressGolden = []interface{}{\n")
+ }
+ for i := 0; i < n; i++ {
+ m := rv.Type().Method(i)
+ mv := rv.Method(i)
+ mt := mv.Type()
+ if mt.NumOut() == 0 {
+ continue
+ }
+ r.Seed(0)
+ if *printgolden && i > 0 {
+ fmt.Println()
+ }
+ for repeat := 0; repeat < 20; repeat++ {
+ var args []reflect.Value
+ var argstr string
+ if mt.NumIn() == 1 {
+ var x interface{}
+ switch mt.In(0).Kind() {
+ default:
+ t.Fatalf("unexpected argument type for r.%s", m.Name)
+
+ case reflect.Int:
+ if m.Name == "Perm" {
+ x = permSizes[repeat%len(permSizes)]
+ break
+ }
+ big := int64s[repeat%len(int64s)]
+ if int64(int(big)) != big {
+ r.Int63n(big) // what would happen on 64-bit machine, to keep stream in sync
+ if *printgolden {
+ fmt.Printf("\tskipped, // must run printgolden on 64-bit machine\n")
+ }
+ p++
+ continue
+ }
+ x = int(big)
+
+ case reflect.Int32:
+ x = int32s[repeat%len(int32s)]
+
+ case reflect.Int64:
+ x = int64s[repeat%len(int64s)]
+
+ case reflect.Uint64:
+ x = uint64s[repeat%len(uint64s)]
+
+ case reflect.Slice:
+ if m.Name == "Read" {
+ n := readBufferSizes[repeat%len(readBufferSizes)]
+ x = make([]byte, n)
+ }
+ }
+ argstr = fmt.Sprint(x)
+ args = append(args, reflect.ValueOf(x))
+ }
+
+ var out interface{}
+ out = mv.Call(args)[0].Interface()
+ if m.Name == "Int" || m.Name == "Intn" {
+ out = int64(out.(int))
+ }
+ if m.Name == "Read" {
+ out = args[0].Interface().([]byte)
+ }
+ if *printgolden {
+ var val string
+ big := int64(1 << 60)
+ if int64(int(big)) != big && (m.Name == "Int" || m.Name == "Intn") {
+ // 32-bit machine cannot print 64-bit results
+ val = "truncated"
+ } else if reflect.TypeOf(out).Kind() == reflect.Slice {
+ val = fmt.Sprintf("%#v", out)
+ } else {
+ val = fmt.Sprintf("%T(%v)", out, out)
+ }
+ fmt.Printf("\t%s, // %s(%s)\n", val, m.Name, argstr)
+ } else {
+ want := regressGolden[p]
+ if m.Name == "Int" {
+ want = int64(int(uint(want.(int64)) << 1 >> 1))
+ }
+ if !reflect.DeepEqual(out, want) {
+ t.Errorf("r.%s(%s) = %v, want %v", m.Name, argstr, out, want)
+ }
+ }
+ p++
+ }
+ }
+ if *printgolden {
+ fmt.Printf("}\n")
+ }
+}
+
+var regressGolden = []interface{}{
+ float64(0.6279600685109523), // ExpFloat64()
+ float64(0.16198826513357806), // ExpFloat64()
+ float64(0.007880404652650552), // ExpFloat64()
+ float64(0.41649788761745654), // ExpFloat64()
+ float64(1.6958707787276301), // ExpFloat64()
+ float64(2.7227327706138036), // ExpFloat64()
+ float64(2.4235600263079657), // ExpFloat64()
+ float64(1.277967771105418), // ExpFloat64()
+ float64(0.7111660437031769), // ExpFloat64()
+ float64(0.23090401427981888), // ExpFloat64()
+ float64(1.4746763588379928), // ExpFloat64()
+ float64(1.4868726779832278), // ExpFloat64()
+ float64(0.1686257242078103), // ExpFloat64()
+ float64(0.2732721816228957), // ExpFloat64()
+ float64(0.4644536065869748), // ExpFloat64()
+ float64(0.01319850986379164), // ExpFloat64()
+ float64(0.7184492551742854), // ExpFloat64()
+ float64(0.1913536422195827), // ExpFloat64()
+ float64(0.16034475958495667), // ExpFloat64()
+ float64(0.40599859014785644), // ExpFloat64()
+
+ float32(0.7979972), // Float32()
+ float32(0.7725961), // Float32()
+ float32(0.21894403), // Float32()
+ float32(0.96194494), // Float32()
+ float32(0.2915732), // Float32()
+ float32(0.59569645), // Float32()
+ float32(0.99596655), // Float32()
+ float32(0.4979039), // Float32()
+ float32(0.98148686), // Float32()
+ float32(0.01380035), // Float32()
+ float32(0.086487144), // Float32()
+ float32(0.6114401), // Float32()
+ float32(0.71081316), // Float32()
+ float32(0.6342346), // Float32()
+ float32(0.008082573), // Float32()
+ float32(0.33020085), // Float32()
+ float32(0.032625034), // Float32()
+ float32(0.9278005), // Float32()
+ float32(0.34497985), // Float32()
+ float32(0.66506875), // Float32()
+
+ float64(0.797997151016231), // Float64()
+ float64(0.7725961454373316), // Float64()
+ float64(0.21894402538580782), // Float64()
+ float64(0.9619449481780457), // Float64()
+ float64(0.2915731877602916), // Float64()
+ float64(0.5956964580775652), // Float64()
+ float64(0.9959665347028619), // Float64()
+ float64(0.49790390966591147), // Float64()
+ float64(0.9814868602566785), // Float64()
+ float64(0.013800350332924483), // Float64()
+ float64(0.08648714463652596), // Float64()
+ float64(0.6114401479210267), // Float64()
+ float64(0.7108131531183706), // Float64()
+ float64(0.6342346133706837), // Float64()
+ float64(0.008082572853887138), // Float64()
+ float64(0.3302008651926287), // Float64()
+ float64(0.03262503454637655), // Float64()
+ float64(0.9278004634858956), // Float64()
+ float64(0.3449798628384906), // Float64()
+ float64(0.665068719316529), // Float64()
+
+ int64(5474557666971700975), // Int()
+ int64(5591422465364813936), // Int()
+ int64(74029666500212977), // Int()
+ int64(8088122161323000979), // Int()
+ int64(7298457654139700474), // Int()
+ int64(1590632625527662686), // Int()
+ int64(9052198920789078554), // Int()
+ int64(7381380909356947872), // Int()
+ int64(1738222704626512495), // Int()
+ int64(3278744831230954970), // Int()
+ int64(7062423222661652521), // Int()
+ int64(6715870808026712034), // Int()
+ int64(528819992478005418), // Int()
+ int64(2284534088986354339), // Int()
+ int64(945828723091990082), // Int()
+ int64(3813019469742317492), // Int()
+ int64(1369388146907482806), // Int()
+ int64(7367238674766648970), // Int()
+ int64(8217673022687244206), // Int()
+ int64(3185531743396549562), // Int()
+
+ int32(1711064216), // Int31()
+ int32(650927245), // Int31()
+ int32(8618187), // Int31()
+ int32(941581344), // Int31()
+ int32(1923394120), // Int31()
+ int32(1258915833), // Int31()
+ int32(1053814650), // Int31()
+ int32(859305834), // Int31()
+ int32(1276097579), // Int31()
+ int32(1455437958), // Int31()
+ int32(1895916096), // Int31()
+ int32(781830261), // Int31()
+ int32(61562749), // Int31()
+ int32(265954771), // Int31()
+ int32(1183850779), // Int31()
+ int32(443893888), // Int31()
+ int32(1233159585), // Int31()
+ int32(857659461), // Int31()
+ int32(956663049), // Int31()
+ int32(370844703), // Int31()
+
+ int32(0), // Int31n(1)
+ int32(6), // Int31n(10)
+ int32(17), // Int31n(32)
+ int32(1000595), // Int31n(1048576)
+ int32(424333), // Int31n(1048577)
+ int32(382438494), // Int31n(1000000000)
+ int32(902738458), // Int31n(1073741824)
+ int32(1204933878), // Int31n(2147483646)
+ int32(1376191263), // Int31n(2147483647)
+ int32(0), // Int31n(1)
+ int32(9), // Int31n(10)
+ int32(2), // Int31n(32)
+ int32(440490), // Int31n(1048576)
+ int32(176312), // Int31n(1048577)
+ int32(946765890), // Int31n(1000000000)
+ int32(665034676), // Int31n(1073741824)
+ int32(1947285452), // Int31n(2147483646)
+ int32(1702344608), // Int31n(2147483647)
+ int32(0), // Int31n(1)
+ int32(2), // Int31n(10)
+
+ int64(5474557666971700975), // Int63()
+ int64(5591422465364813936), // Int63()
+ int64(74029666500212977), // Int63()
+ int64(8088122161323000979), // Int63()
+ int64(7298457654139700474), // Int63()
+ int64(1590632625527662686), // Int63()
+ int64(9052198920789078554), // Int63()
+ int64(7381380909356947872), // Int63()
+ int64(1738222704626512495), // Int63()
+ int64(3278744831230954970), // Int63()
+ int64(7062423222661652521), // Int63()
+ int64(6715870808026712034), // Int63()
+ int64(528819992478005418), // Int63()
+ int64(2284534088986354339), // Int63()
+ int64(945828723091990082), // Int63()
+ int64(3813019469742317492), // Int63()
+ int64(1369388146907482806), // Int63()
+ int64(7367238674766648970), // Int63()
+ int64(8217673022687244206), // Int63()
+ int64(3185531743396549562), // Int63()
+
+ int64(0), // Int63n(1)
+ int64(6), // Int63n(10)
+ int64(17), // Int63n(32)
+ int64(1000595), // Int63n(1048576)
+ int64(424333), // Int63n(1048577)
+ int64(382438494), // Int63n(1000000000)
+ int64(902738458), // Int63n(1073741824)
+ int64(1204933878), // Int63n(2147483646)
+ int64(1376191263), // Int63n(2147483647)
+ int64(502116868085730778), // Int63n(1000000000000000000)
+ int64(144894195020570665), // Int63n(1152921504606846976)
+ int64(6715870808026712034), // Int63n(9223372036854775806)
+ int64(528819992478005418), // Int63n(9223372036854775807)
+ int64(0), // Int63n(1)
+ int64(0), // Int63n(10)
+ int64(20), // Int63n(32)
+ int64(854710), // Int63n(1048576)
+ int64(649893), // Int63n(1048577)
+ int64(687244206), // Int63n(1000000000)
+ int64(836883386), // Int63n(1073741824)
+
+ int64(0), // Intn(1)
+ int64(6), // Intn(10)
+ int64(17), // Intn(32)
+ int64(1000595), // Intn(1048576)
+ int64(424333), // Intn(1048577)
+ int64(382438494), // Intn(1000000000)
+ int64(902738458), // Intn(1073741824)
+ int64(1204933878), // Intn(2147483646)
+ int64(1376191263), // Intn(2147483647)
+ int64(502116868085730778), // Intn(1000000000000000000)
+ int64(144894195020570665), // Intn(1152921504606846976)
+ int64(6715870808026712034), // Intn(9223372036854775806)
+ int64(528819992478005418), // Intn(9223372036854775807)
+ int64(0), // Intn(1)
+ int64(0), // Intn(10)
+ int64(20), // Intn(32)
+ int64(854710), // Intn(1048576)
+ int64(649893), // Intn(1048577)
+ int64(687244206), // Intn(1000000000)
+ int64(836883386), // Intn(1073741824)
+
+ float64(-0.5410658516792047), // NormFloat64()
+ float64(0.615296849055287), // NormFloat64()
+ float64(0.007477442280032887), // NormFloat64()
+ float64(1.3443892057169684), // NormFloat64()
+ float64(-0.17508902754863512), // NormFloat64()
+ float64(-2.03494397556937), // NormFloat64()
+ float64(2.5213558871972306), // NormFloat64()
+ float64(1.4572921639613627), // NormFloat64()
+ float64(-1.5164961164210644), // NormFloat64()
+ float64(-0.4861150771891445), // NormFloat64()
+ float64(-0.8699409548614199), // NormFloat64()
+ float64(1.6271559815452794), // NormFloat64()
+ float64(0.1659465769926195), // NormFloat64()
+ float64(0.2921716191987018), // NormFloat64()
+ float64(-1.2550269636927838), // NormFloat64()
+ float64(0.11257973349467548), // NormFloat64()
+ float64(0.5437525915836436), // NormFloat64()
+ float64(0.781754430770282), // NormFloat64()
+ float64(0.5201256313962235), // NormFloat64()
+ float64(1.3826174159276245), // NormFloat64()
+
+ []int{}, // Perm(0)
+ []int{0}, // Perm(1)
+ []int{0, 2, 3, 1, 4}, // Perm(5)
+ []int{5, 6, 3, 7, 4, 2, 0, 1}, // Perm(8)
+ []int{8, 4, 5, 2, 7, 3, 0, 6, 1}, // Perm(9)
+ []int{6, 1, 5, 3, 2, 9, 7, 0, 8, 4}, // Perm(10)
+ []int{12, 5, 1, 9, 15, 7, 13, 6, 10, 11, 8, 0, 4, 2, 14, 3}, // Perm(16)
+ []int{}, // Perm(0)
+ []int{0}, // Perm(1)
+ []int{0, 2, 3, 4, 1}, // Perm(5)
+ []int{3, 2, 7, 4, 0, 6, 5, 1}, // Perm(8)
+ []int{0, 6, 2, 1, 3, 7, 5, 8, 4}, // Perm(9)
+ []int{2, 5, 6, 4, 7, 3, 0, 8, 1, 9}, // Perm(10)
+ []int{3, 6, 5, 4, 9, 15, 13, 7, 1, 11, 10, 8, 12, 0, 2, 14}, // Perm(16)
+ []int{}, // Perm(0)
+ []int{0}, // Perm(1)
+ []int{2, 4, 3, 1, 0}, // Perm(5)
+ []int{1, 6, 7, 5, 4, 3, 2, 0}, // Perm(8)
+ []int{7, 6, 8, 2, 0, 1, 3, 4, 5}, // Perm(9)
+ []int{2, 9, 7, 1, 5, 4, 0, 6, 8, 3}, // Perm(10)
+
+ []byte{0xef}, // Read([0])
+ []byte{0x4e, 0x3d, 0x52, 0x31, 0x89, 0xf9, 0xcb}, // Read([0 0 0 0 0 0 0])
+ []byte{0x70, 0x68, 0x35, 0x8d, 0x1b, 0xb9, 0x98, 0x4d}, // Read([0 0 0 0 0 0 0 0])
+ []byte{0xf1, 0xf8, 0x95, 0xe6, 0x96, 0x1, 0x7, 0x1, 0x93}, // Read([0 0 0 0 0 0 0 0 0])
+ []byte{0x44, 0x9f, 0xc5, 0x40, 0xc8, 0x3e, 0x70, 0xfa, 0x44, 0x3a}, // Read([0 0 0 0 0 0 0 0 0 0])
+ []byte{0x4b}, // Read([0])
+ []byte{0x91, 0x54, 0x49, 0xe5, 0x5e, 0x28, 0xb9}, // Read([0 0 0 0 0 0 0])
+ []byte{0x4, 0xf2, 0xf, 0x13, 0x96, 0x1a, 0xb2, 0xce}, // Read([0 0 0 0 0 0 0 0])
+ []byte{0x35, 0xf5, 0xde, 0x9f, 0x7d, 0xa0, 0x19, 0x12, 0x2e}, // Read([0 0 0 0 0 0 0 0 0])
+ []byte{0xd4, 0xee, 0x6f, 0x66, 0x6f, 0x32, 0xc8, 0x21, 0x57, 0x68}, // Read([0 0 0 0 0 0 0 0 0 0])
+ []byte{0x1f}, // Read([0])
+ []byte{0x98, 0xda, 0x4d, 0xab, 0x6e, 0xd, 0x71}, // Read([0 0 0 0 0 0 0])
+ []byte{0x80, 0xad, 0x29, 0xa0, 0x37, 0xb0, 0x80, 0xc4}, // Read([0 0 0 0 0 0 0 0])
+ []byte{0x2, 0xe2, 0xe2, 0x7, 0xd9, 0xed, 0xea, 0x90, 0x33}, // Read([0 0 0 0 0 0 0 0 0])
+ []byte{0x5d, 0xaa, 0xb8, 0xc6, 0x39, 0xfb, 0xbe, 0x56, 0x7, 0xa3}, // Read([0 0 0 0 0 0 0 0 0 0])
+ []byte{0x62}, // Read([0])
+ []byte{0x4d, 0x63, 0xa6, 0x4b, 0xb4, 0x1f, 0x42}, // Read([0 0 0 0 0 0 0])
+ []byte{0x66, 0x42, 0x62, 0x36, 0x42, 0x20, 0x8d, 0xb4}, // Read([0 0 0 0 0 0 0 0])
+ []byte{0x9f, 0xa3, 0x67, 0x1, 0x91, 0xea, 0x34, 0xb6, 0xa}, // Read([0 0 0 0 0 0 0 0 0])
+ []byte{0xd, 0xa8, 0x43, 0xb, 0x1, 0x93, 0x8a, 0x56, 0xfc, 0x98}, // Read([0 0 0 0 0 0 0 0 0 0])
+
+ uint32(3422128433), // Uint32()
+ uint32(1301854491), // Uint32()
+ uint32(17236374), // Uint32()
+ uint32(1883162688), // Uint32()
+ uint32(3846788241), // Uint32()
+ uint32(2517831666), // Uint32()
+ uint32(2107629301), // Uint32()
+ uint32(1718611668), // Uint32()
+ uint32(2552195159), // Uint32()
+ uint32(2910875917), // Uint32()
+ uint32(3791832192), // Uint32()
+ uint32(1563660522), // Uint32()
+ uint32(123125499), // Uint32()
+ uint32(531909542), // Uint32()
+ uint32(2367701558), // Uint32()
+ uint32(887787777), // Uint32()
+ uint32(2466319171), // Uint32()
+ uint32(1715318922), // Uint32()
+ uint32(1913326099), // Uint32()
+ uint32(741689406), // Uint32()
+
+ uint64(14697929703826476783), // Uint64()
+ uint64(5591422465364813936), // Uint64()
+ uint64(74029666500212977), // Uint64()
+ uint64(8088122161323000979), // Uint64()
+ uint64(16521829690994476282), // Uint64()
+ uint64(10814004662382438494), // Uint64()
+ uint64(9052198920789078554), // Uint64()
+ uint64(7381380909356947872), // Uint64()
+ uint64(10961594741481288303), // Uint64()
+ uint64(12502116868085730778), // Uint64()
+ uint64(16285795259516428329), // Uint64()
+ uint64(6715870808026712034), // Uint64()
+ uint64(528819992478005418), // Uint64()
+ uint64(2284534088986354339), // Uint64()
+ uint64(10169200759946765890), // Uint64()
+ uint64(3813019469742317492), // Uint64()
+ uint64(10592760183762258614), // Uint64()
+ uint64(7367238674766648970), // Uint64()
+ uint64(8217673022687244206), // Uint64()
+ uint64(3185531743396549562), // Uint64()
+
+ uint64(0), // Uint64n(1)
+ uint64(6), // Uint64n(10)
+ uint64(17), // Uint64n(32)
+ uint64(1000595), // Uint64n(1048576)
+ uint64(424333), // Uint64n(1048577)
+ uint64(382438494), // Uint64n(1000000000)
+ uint64(902738458), // Uint64n(1073741824)
+ uint64(1204933878), // Uint64n(2147483646)
+ uint64(1376191263), // Uint64n(2147483647)
+ uint64(502116868085730778), // Uint64n(1000000000000000000)
+ uint64(144894195020570665), // Uint64n(1152921504606846976)
+ uint64(6715870808026712034), // Uint64n(18446744073709551614)
+ uint64(528819992478005418), // Uint64n(18446744073709551615)
+ uint64(0), // Uint64n(1)
+ uint64(0), // Uint64n(10)
+ uint64(20), // Uint64n(32)
+ uint64(854710), // Uint64n(1048576)
+ uint64(649893), // Uint64n(1048577)
+ uint64(687244206), // Uint64n(1000000000)
+ uint64(836883386), // Uint64n(1073741824)
+}
diff --git a/rand/rng.go b/rand/rng.go
new file mode 100644
index 0000000..9b79108
--- /dev/null
+++ b/rand/rng.go
@@ -0,0 +1,91 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+ "encoding/binary"
+ "io"
+ "math/bits"
+)
+
+// PCGSource is an implementation of a 64-bit permuted congruential
+// generator as defined in
+//
+// PCG: A Family of Simple Fast Space-Efficient Statistically Good
+// Algorithms for Random Number Generation
+// Melissa E. O’Neill, Harvey Mudd College
+// http://www.pcg-random.org/pdf/toms-oneill-pcg-family-v1.02.pdf
+//
+// The generator here is the congruential generator PCG XSL RR 128/64 (LCG)
+// as found in the software available at http://www.pcg-random.org/.
+// It has period 2^128 with 128 bits of state, producing 64-bit values.
+// Is state is represented by two uint64 words.
+type PCGSource struct {
+ low uint64
+ high uint64
+}
+
+const (
+ maxUint32 = (1 << 32) - 1
+
+ multiplier = 47026247687942121848144207491837523525
+ mulHigh = multiplier >> 64
+ mulLow = multiplier & maxUint64
+
+ increment = 117397592171526113268558934119004209487
+ incHigh = increment >> 64
+ incLow = increment & maxUint64
+
+ // TODO: Use these?
+ initializer = 245720598905631564143578724636268694099
+ initHigh = initializer >> 64
+ initLow = initializer & maxUint64
+)
+
+// Seed uses the provided seed value to initialize the generator to a deterministic state.
+func (pcg *PCGSource) Seed(seed uint64) {
+ pcg.low = seed
+ pcg.high = seed // TODO: What is right?
+}
+
+// Uint64 returns a pseudo-random 64-bit unsigned integer as a uint64.
+func (pcg *PCGSource) Uint64() uint64 {
+ pcg.multiply()
+ pcg.add()
+ // XOR high and low 64 bits together and rotate right by high 6 bits of state.
+ return bits.RotateLeft64(pcg.high^pcg.low, -int(pcg.high>>58))
+}
+
+func (pcg *PCGSource) add() {
+ var carry uint64
+ pcg.low, carry = bits.Add64(pcg.low, incLow, 0)
+ pcg.high, _ = bits.Add64(pcg.high, incHigh, carry)
+}
+
+func (pcg *PCGSource) multiply() {
+ hi, lo := bits.Mul64(pcg.low, mulLow)
+ hi += pcg.high * mulLow
+ hi += pcg.low * mulHigh
+ pcg.low = lo
+ pcg.high = hi
+}
+
+// MarshalBinary returns the binary representation of the current state of the generator.
+func (pcg *PCGSource) MarshalBinary() ([]byte, error) {
+ var buf [16]byte
+ binary.BigEndian.PutUint64(buf[:8], pcg.high)
+ binary.BigEndian.PutUint64(buf[8:], pcg.low)
+ return buf[:], nil
+}
+
+// UnmarshalBinary sets the state of the generator to the state represented in data.
+func (pcg *PCGSource) UnmarshalBinary(data []byte) error {
+ if len(data) < 16 {
+ return io.ErrUnexpectedEOF
+ }
+ pcg.low = binary.BigEndian.Uint64(data[8:])
+ pcg.high = binary.BigEndian.Uint64(data[:8])
+ return nil
+}
diff --git a/rand/zipf.go b/rand/zipf.go
new file mode 100644
index 0000000..f04c814
--- /dev/null
+++ b/rand/zipf.go
@@ -0,0 +1,77 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// W.Hormann, G.Derflinger:
+// "Rejection-Inversion to Generate Variates
+// from Monotone Discrete Distributions"
+// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz
+
+package rand
+
+import "math"
+
+// A Zipf generates Zipf distributed variates.
+type Zipf struct {
+ r *Rand
+ imax float64
+ v float64
+ q float64
+ s float64
+ oneminusQ float64
+ oneminusQinv float64
+ hxm float64
+ hx0minusHxm float64
+}
+
+func (z *Zipf) h(x float64) float64 {
+ return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv
+}
+
+func (z *Zipf) hinv(x float64) float64 {
+ return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v
+}
+
+// NewZipf returns a Zipf variate generator.
+// The generator generates values k ∈ [0, imax]
+// such that P(k) is proportional to (v + k) ** (-s).
+// Requirements: s > 1 and v >= 1.
+func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf {
+ z := new(Zipf)
+ if s <= 1.0 || v < 1 {
+ return nil
+ }
+ z.r = r
+ z.imax = float64(imax)
+ z.v = v
+ z.q = s
+ z.oneminusQ = 1.0 - z.q
+ z.oneminusQinv = 1.0 / z.oneminusQ
+ z.hxm = z.h(z.imax + 0.5)
+ z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm
+ z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0)))
+ return z
+}
+
+// Uint64 returns a value drawn from the Zipf distribution described
+// by the Zipf object.
+func (z *Zipf) Uint64() uint64 {
+ if z == nil {
+ panic("rand: nil Zipf")
+ }
+ k := 0.0
+
+ for {
+ r := z.r.Float64() // r on [0,1]
+ ur := z.hxm + r*z.hx0minusHxm
+ x := z.hinv(ur)
+ k = math.Floor(x + 0.5)
+ if k-x <= z.s {
+ break
+ }
+ if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) {
+ break
+ }
+ }
+ return uint64(k)
+}
diff --git a/shiny/driver/driver.go b/shiny/driver/driver.go
new file mode 100644
index 0000000..cf2b68c
--- /dev/null
+++ b/shiny/driver/driver.go
@@ -0,0 +1,25 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package driver provides the default driver for accessing a screen.
+package driver // import "golang.org/x/exp/shiny/driver"
+
+// TODO: figure out what to say about the responsibility for users of this
+// package to check any implicit dependencies' LICENSEs. For example, the
+// driver might use third party software outside of golang.org/x, like an X11
+// or OpenGL library.
+
+import (
+ "golang.org/x/exp/shiny/screen"
+)
+
+// Main is called by the program's main function to run the graphical
+// application.
+//
+// It calls f on the Screen, possibly in a separate goroutine, as some OS-
+// specific libraries require being on 'the main thread'. It returns when f
+// returns.
+func Main(f func(screen.Screen)) {
+ main(f)
+}
diff --git a/shiny/driver/driver_darwin.go b/shiny/driver/driver_darwin.go
new file mode 100644
index 0000000..43f79cf
--- /dev/null
+++ b/shiny/driver/driver_darwin.go
@@ -0,0 +1,16 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && !metal
+
+package driver
+
+import (
+ "golang.org/x/exp/shiny/driver/gldriver"
+ "golang.org/x/exp/shiny/screen"
+)
+
+func main(f func(screen.Screen)) {
+ gldriver.Main(f)
+}
diff --git a/shiny/driver/driver_fallback.go b/shiny/driver/driver_fallback.go
new file mode 100644
index 0000000..93b0c19
--- /dev/null
+++ b/shiny/driver/driver_fallback.go
@@ -0,0 +1,18 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !darwin && (!linux || android) && !windows && !dragonfly && !openbsd
+
+package driver
+
+import (
+ "errors"
+
+ "golang.org/x/exp/shiny/driver/internal/errscreen"
+ "golang.org/x/exp/shiny/screen"
+)
+
+func main(f func(screen.Screen)) {
+ f(errscreen.Stub(errors.New("no driver for accessing a screen")))
+}
diff --git a/shiny/driver/driver_windows.go b/shiny/driver/driver_windows.go
new file mode 100644
index 0000000..4ae016c
--- /dev/null
+++ b/shiny/driver/driver_windows.go
@@ -0,0 +1,14 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driver
+
+import (
+ "golang.org/x/exp/shiny/driver/windriver"
+ "golang.org/x/exp/shiny/screen"
+)
+
+func main(f func(screen.Screen)) {
+ windriver.Main(f)
+}
diff --git a/shiny/driver/driver_x11.go b/shiny/driver/driver_x11.go
new file mode 100644
index 0000000..5f9b839
--- /dev/null
+++ b/shiny/driver/driver_x11.go
@@ -0,0 +1,16 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (linux && !android) || dragonfly || openbsd
+
+package driver
+
+import (
+ "golang.org/x/exp/shiny/driver/x11driver"
+ "golang.org/x/exp/shiny/screen"
+)
+
+func main(f func(screen.Screen)) {
+ x11driver.Main(f)
+}
diff --git a/shiny/driver/gldriver/buffer.go b/shiny/driver/gldriver/buffer.go
new file mode 100644
index 0000000..94b4b9a
--- /dev/null
+++ b/shiny/driver/gldriver/buffer.go
@@ -0,0 +1,32 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gldriver
+
+import "image"
+
+type bufferImpl struct {
+ // buf should always be equal to (i.e. the same ptr, len, cap as) rgba.Pix.
+ // It is a separate, redundant field in order to detect modifications to
+ // the rgba field that are invalid as per the screen.Buffer documentation.
+ buf []byte
+ rgba image.RGBA
+ size image.Point
+}
+
+func (b *bufferImpl) Release() {}
+func (b *bufferImpl) Size() image.Point { return b.size }
+func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
+func (b *bufferImpl) RGBA() *image.RGBA { return &b.rgba }
+
+func (b *bufferImpl) preUpload() {
+ // Check that the program hasn't tried to modify the rgba field via the
+ // pointer returned by the bufferImpl.RGBA method. This check doesn't catch
+ // 100% of all cases; it simply tries to detect some invalid uses of a
+ // screen.Buffer such as:
+ // *buffer.RGBA() = anotherImageRGBA
+ if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] {
+ panic("gldriver: invalid Buffer.RGBA modification")
+ }
+}
diff --git a/shiny/driver/gldriver/cocoa.go b/shiny/driver/gldriver/cocoa.go
new file mode 100644
index 0000000..99dad13
--- /dev/null
+++ b/shiny/driver/gldriver/cocoa.go
@@ -0,0 +1,670 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && !ios
+
+package gldriver
+
+/*
+#cgo CFLAGS: -x objective-c -DGL_SILENCE_DEPRECATION
+#cgo LDFLAGS: -framework Cocoa -framework OpenGL
+#include <OpenGL/gl3.h>
+#import <Carbon/Carbon.h> // for HIToolbox/Events.h
+#import <Cocoa/Cocoa.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+void startDriver();
+void stopDriver();
+void makeCurrentContext(uintptr_t ctx);
+void flushContext(uintptr_t ctx);
+uintptr_t doNewWindow(int width, int height, char* title);
+void doShowWindow(uintptr_t id);
+void doCloseWindow(uintptr_t id);
+uint64_t threadID();
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "runtime"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/internal/lifecycler"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+ "golang.org/x/mobile/geom"
+ "golang.org/x/mobile/gl"
+)
+
+const useLifecycler = true
+
+// TODO: change this to true, after manual testing on OS X.
+const handleSizeEventsAtChannelReceive = false
+
+var initThreadID C.uint64_t
+
+func init() {
+ // Lock the goroutine responsible for initialization to an OS thread.
+ // This means the goroutine running main (and calling startDriver below)
+ // is locked to the OS thread that started the program. This is
+ // necessary for the correct delivery of Cocoa events to the process.
+ //
+ // A discussion on this topic:
+ // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
+ runtime.LockOSThread()
+ initThreadID = C.threadID()
+}
+
+func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
+ width, height := optsSize(opts)
+
+ title := C.CString(opts.GetTitle())
+ defer C.free(unsafe.Pointer(title))
+
+ return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil
+}
+
+func initWindow(w *windowImpl) {
+ w.glctx, w.worker = gl.NewContext()
+}
+
+func showWindow(w *windowImpl) {
+ C.doShowWindow(C.uintptr_t(w.id))
+}
+
+//export preparedOpenGL
+func preparedOpenGL(id, ctx, vba uintptr) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ w.ctx = ctx
+ go drawLoop(w, vba)
+}
+
+func closeWindow(id uintptr) {
+ C.doCloseWindow(C.uintptr_t(id))
+}
+
+var mainCallback func(screen.Screen)
+
+func main(f func(screen.Screen)) error {
+ if tid := C.threadID(); tid != initThreadID {
+ log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID)
+ }
+
+ mainCallback = f
+ C.startDriver()
+ return nil
+}
+
+//export driverStarted
+func driverStarted() {
+ go func() {
+ mainCallback(theScreen)
+ C.stopDriver()
+ }()
+}
+
+//export drawgl
+func drawgl(id uintptr) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return // closing window
+ }
+
+ // TODO: is this necessary?
+ w.lifecycler.SetVisible(true)
+ w.lifecycler.SendEvent(w, w.glctx)
+
+ w.Send(paint.Event{External: true})
+ <-w.drawDone
+}
+
+// drawLoop is the primary drawing loop.
+//
+// After Cocoa has created an NSWindow and called prepareOpenGL,
+// it starts drawLoop on a locked goroutine to handle OpenGL calls.
+//
+// The screen is drawn every time a paint.Event is received, which can be
+// triggered either by the user or by Cocoa via drawgl (for example, when
+// the window is resized).
+func drawLoop(w *windowImpl, vba uintptr) {
+ runtime.LockOSThread()
+ C.makeCurrentContext(C.uintptr_t(w.ctx.(uintptr)))
+
+ // Starting in OS X 10.11 (El Capitan), the vertex array is
+ // occasionally getting unbound when the context changes threads.
+ //
+ // Avoid this by binding it again.
+ C.glBindVertexArray(C.GLuint(vba))
+ if errno := C.glGetError(); errno != 0 {
+ panic(fmt.Sprintf("gldriver: glBindVertexArray failed: %d", errno))
+ }
+
+ workAvailable := w.worker.WorkAvailable()
+
+ // TODO(crawshaw): exit this goroutine on Release.
+ for {
+ select {
+ case <-workAvailable:
+ w.worker.DoWork()
+ case <-w.publish:
+ loop:
+ for {
+ select {
+ case <-workAvailable:
+ w.worker.DoWork()
+ default:
+ break loop
+ }
+ }
+ C.flushContext(C.uintptr_t(w.ctx.(uintptr)))
+ w.publishDone <- screen.PublishResult{}
+ }
+ }
+}
+
+//export setGeom
+func setGeom(id uintptr, ppp float32, widthPx, heightPx int) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return // closing window
+ }
+
+ sz := size.Event{
+ WidthPx: widthPx,
+ HeightPx: heightPx,
+ WidthPt: geom.Pt(float32(widthPx) / ppp),
+ HeightPt: geom.Pt(float32(heightPx) / ppp),
+ PixelsPerPt: ppp,
+ }
+
+ if !handleSizeEventsAtChannelReceive {
+ w.szMu.Lock()
+ w.sz = sz
+ w.szMu.Unlock()
+ }
+
+ w.Send(sz)
+}
+
+//export windowClosing
+func windowClosing(id uintptr) {
+ sendLifecycle(id, (*lifecycler.State).SetDead, true)
+}
+
+func sendWindowEvent(id uintptr, e interface{}) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return // closing window
+ }
+ w.Send(e)
+}
+
+var mods = [...]struct {
+ flags uint32
+ code uint16
+ mod key.Modifiers
+}{
+ // Left and right variants of modifier keys have their own masks,
+ // but they are not documented. These were determined empirically.
+ {1<<17 | 0x102, C.kVK_Shift, key.ModShift},
+ {1<<17 | 0x104, C.kVK_RightShift, key.ModShift},
+ {1<<18 | 0x101, C.kVK_Control, key.ModControl},
+ {33<<13 | 0x100, C.kVK_RightControl, key.ModControl},
+ {1<<19 | 0x120, C.kVK_Option, key.ModAlt},
+ {1<<19 | 0x140, C.kVK_RightOption, key.ModAlt},
+ {1<<20 | 0x108, C.kVK_Command, key.ModMeta},
+ {1<<20 | 0x110, 0x36 /* kVK_RightCommand */, key.ModMeta},
+}
+
+func cocoaMods(flags uint32) (m key.Modifiers) {
+ for _, mod := range mods {
+ if flags&mod.flags == mod.flags {
+ m |= mod.mod
+ }
+ }
+ return m
+}
+
+func cocoaMouseDir(ty int32) mouse.Direction {
+ switch ty {
+ case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown:
+ return mouse.DirPress
+ case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp:
+ return mouse.DirRelease
+ default: // dragged
+ return mouse.DirNone
+ }
+}
+
+func cocoaMouseButton(button int32) mouse.Button {
+ switch button {
+ case 0:
+ return mouse.ButtonLeft
+ case 1:
+ return mouse.ButtonRight
+ case 2:
+ return mouse.ButtonMiddle
+ default:
+ return mouse.ButtonNone
+ }
+}
+
+//export mouseEvent
+func mouseEvent(id uintptr, x, y, dx, dy float32, ty, button int32, flags uint32) {
+ cmButton := mouse.ButtonNone
+ switch ty {
+ default:
+ cmButton = cocoaMouseButton(button)
+ case C.NSMouseMoved, C.NSLeftMouseDragged, C.NSRightMouseDragged, C.NSOtherMouseDragged:
+ // No-op.
+ case C.NSScrollWheel:
+ // Note that the direction of scrolling is inverted by default
+ // on OS X by the "natural scrolling" setting. At the Cocoa
+ // level this inversion is applied to trackpads and mice behind
+ // the scenes, and the value of dy goes in the direction the OS
+ // wants scrolling to go.
+ //
+ // This means the same trackpad/mouse motion on OS X and Linux
+ // can produce wheel events in opposite directions, but the
+ // direction matches what other programs on the OS do.
+ //
+ // If we wanted to expose the physical device motion in the
+ // event we could use [NSEvent isDirectionInvertedFromDevice]
+ // to know if "natural scrolling" is enabled.
+ //
+ // TODO: On a trackpad, a scroll can be a drawn-out affair with a
+ // distinct beginning and end. Should the intermediate events be
+ // DirNone?
+ //
+ // TODO: handle horizontal scrolling
+ button := mouse.ButtonWheelUp
+ if dy < 0 {
+ dy = -dy
+ button = mouse.ButtonWheelDown
+ }
+ e := mouse.Event{
+ X: x,
+ Y: y,
+ Button: button,
+ Direction: mouse.DirStep,
+ Modifiers: cocoaMods(flags),
+ }
+ for delta := int(dy); delta != 0; delta-- {
+ sendWindowEvent(id, e)
+ }
+ return
+ }
+ sendWindowEvent(id, mouse.Event{
+ X: x,
+ Y: y,
+ Button: cmButton,
+ Direction: cocoaMouseDir(ty),
+ Modifiers: cocoaMods(flags),
+ })
+}
+
+//export keyEvent
+func keyEvent(id uintptr, runeVal rune, dir uint8, code uint16, flags uint32) {
+ sendWindowEvent(id, key.Event{
+ Rune: cocoaRune(runeVal),
+ Direction: key.Direction(dir),
+ Code: cocoaKeyCode(code),
+ Modifiers: cocoaMods(flags),
+ })
+}
+
+//export flagEvent
+func flagEvent(id uintptr, flags uint32) {
+ for _, mod := range mods {
+ if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags {
+ keyEvent(id, -1, C.NSKeyDown, mod.code, flags)
+ }
+ if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags {
+ keyEvent(id, -1, C.NSKeyUp, mod.code, flags)
+ }
+ }
+ lastFlags = flags
+}
+
+var lastFlags uint32
+
+func sendLifecycle(id uintptr, setter func(*lifecycler.State, bool), val bool) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+ setter(&w.lifecycler, val)
+ w.lifecycler.SendEvent(w, w.glctx)
+}
+
+func sendLifecycleAll(dead bool) {
+ windows := []*windowImpl{}
+
+ theScreen.mu.Lock()
+ for _, w := range theScreen.windows {
+ windows = append(windows, w)
+ }
+ theScreen.mu.Unlock()
+
+ for _, w := range windows {
+ w.lifecycler.SetFocused(false)
+ w.lifecycler.SetVisible(false)
+ if dead {
+ w.lifecycler.SetDead(true)
+ }
+ w.lifecycler.SendEvent(w, w.glctx)
+ }
+}
+
+//export lifecycleDeadAll
+func lifecycleDeadAll() { sendLifecycleAll(true) }
+
+//export lifecycleHideAll
+func lifecycleHideAll() { sendLifecycleAll(false) }
+
+//export lifecycleVisible
+func lifecycleVisible(id uintptr, val bool) {
+ sendLifecycle(id, (*lifecycler.State).SetVisible, val)
+}
+
+//export lifecycleFocused
+func lifecycleFocused(id uintptr, val bool) {
+ sendLifecycle(id, (*lifecycler.State).SetFocused, val)
+}
+
+// cocoaRune marks the Carbon/Cocoa private-range unicode rune representing
+// a non-unicode key event to -1, used for Rune in the key package.
+//
+// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
+func cocoaRune(r rune) rune {
+ if '\uE000' <= r && r <= '\uF8FF' {
+ return -1
+ }
+ return r
+}
+
+// cocoaKeyCode converts a Carbon/Cocoa virtual key code number
+// into the standard keycodes used by the key package.
+//
+// To get a sense of the key map, see the diagram on
+//
+// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes
+func cocoaKeyCode(vkcode uint16) key.Code {
+ switch vkcode {
+ case C.kVK_ANSI_A:
+ return key.CodeA
+ case C.kVK_ANSI_B:
+ return key.CodeB
+ case C.kVK_ANSI_C:
+ return key.CodeC
+ case C.kVK_ANSI_D:
+ return key.CodeD
+ case C.kVK_ANSI_E:
+ return key.CodeE
+ case C.kVK_ANSI_F:
+ return key.CodeF
+ case C.kVK_ANSI_G:
+ return key.CodeG
+ case C.kVK_ANSI_H:
+ return key.CodeH
+ case C.kVK_ANSI_I:
+ return key.CodeI
+ case C.kVK_ANSI_J:
+ return key.CodeJ
+ case C.kVK_ANSI_K:
+ return key.CodeK
+ case C.kVK_ANSI_L:
+ return key.CodeL
+ case C.kVK_ANSI_M:
+ return key.CodeM
+ case C.kVK_ANSI_N:
+ return key.CodeN
+ case C.kVK_ANSI_O:
+ return key.CodeO
+ case C.kVK_ANSI_P:
+ return key.CodeP
+ case C.kVK_ANSI_Q:
+ return key.CodeQ
+ case C.kVK_ANSI_R:
+ return key.CodeR
+ case C.kVK_ANSI_S:
+ return key.CodeS
+ case C.kVK_ANSI_T:
+ return key.CodeT
+ case C.kVK_ANSI_U:
+ return key.CodeU
+ case C.kVK_ANSI_V:
+ return key.CodeV
+ case C.kVK_ANSI_W:
+ return key.CodeW
+ case C.kVK_ANSI_X:
+ return key.CodeX
+ case C.kVK_ANSI_Y:
+ return key.CodeY
+ case C.kVK_ANSI_Z:
+ return key.CodeZ
+ case C.kVK_ANSI_1:
+ return key.Code1
+ case C.kVK_ANSI_2:
+ return key.Code2
+ case C.kVK_ANSI_3:
+ return key.Code3
+ case C.kVK_ANSI_4:
+ return key.Code4
+ case C.kVK_ANSI_5:
+ return key.Code5
+ case C.kVK_ANSI_6:
+ return key.Code6
+ case C.kVK_ANSI_7:
+ return key.Code7
+ case C.kVK_ANSI_8:
+ return key.Code8
+ case C.kVK_ANSI_9:
+ return key.Code9
+ case C.kVK_ANSI_0:
+ return key.Code0
+ // TODO: move the rest of these codes to constants in key.go
+ // if we are happy with them.
+ case C.kVK_Return:
+ return key.CodeReturnEnter
+ case C.kVK_Escape:
+ return key.CodeEscape
+ case C.kVK_Delete:
+ return key.CodeDeleteBackspace
+ case C.kVK_Tab:
+ return key.CodeTab
+ case C.kVK_Space:
+ return key.CodeSpacebar
+ case C.kVK_ANSI_Minus:
+ return key.CodeHyphenMinus
+ case C.kVK_ANSI_Equal:
+ return key.CodeEqualSign
+ case C.kVK_ANSI_LeftBracket:
+ return key.CodeLeftSquareBracket
+ case C.kVK_ANSI_RightBracket:
+ return key.CodeRightSquareBracket
+ case C.kVK_ANSI_Backslash:
+ return key.CodeBackslash
+ // 50: Keyboard Non-US "#" and ~
+ case C.kVK_ANSI_Semicolon:
+ return key.CodeSemicolon
+ case C.kVK_ANSI_Quote:
+ return key.CodeApostrophe
+ case C.kVK_ANSI_Grave:
+ return key.CodeGraveAccent
+ case C.kVK_ANSI_Comma:
+ return key.CodeComma
+ case C.kVK_ANSI_Period:
+ return key.CodeFullStop
+ case C.kVK_ANSI_Slash:
+ return key.CodeSlash
+ case C.kVK_CapsLock:
+ return key.CodeCapsLock
+ case C.kVK_F1:
+ return key.CodeF1
+ case C.kVK_F2:
+ return key.CodeF2
+ case C.kVK_F3:
+ return key.CodeF3
+ case C.kVK_F4:
+ return key.CodeF4
+ case C.kVK_F5:
+ return key.CodeF5
+ case C.kVK_F6:
+ return key.CodeF6
+ case C.kVK_F7:
+ return key.CodeF7
+ case C.kVK_F8:
+ return key.CodeF8
+ case C.kVK_F9:
+ return key.CodeF9
+ case C.kVK_F10:
+ return key.CodeF10
+ case C.kVK_F11:
+ return key.CodeF11
+ case C.kVK_F12:
+ return key.CodeF12
+ // 70: PrintScreen
+ // 71: Scroll Lock
+ // 72: Pause
+ // 73: Insert
+ case C.kVK_Home:
+ return key.CodeHome
+ case C.kVK_PageUp:
+ return key.CodePageUp
+ case C.kVK_ForwardDelete:
+ return key.CodeDeleteForward
+ case C.kVK_End:
+ return key.CodeEnd
+ case C.kVK_PageDown:
+ return key.CodePageDown
+ case C.kVK_RightArrow:
+ return key.CodeRightArrow
+ case C.kVK_LeftArrow:
+ return key.CodeLeftArrow
+ case C.kVK_DownArrow:
+ return key.CodeDownArrow
+ case C.kVK_UpArrow:
+ return key.CodeUpArrow
+ case C.kVK_ANSI_KeypadClear:
+ return key.CodeKeypadNumLock
+ case C.kVK_ANSI_KeypadDivide:
+ return key.CodeKeypadSlash
+ case C.kVK_ANSI_KeypadMultiply:
+ return key.CodeKeypadAsterisk
+ case C.kVK_ANSI_KeypadMinus:
+ return key.CodeKeypadHyphenMinus
+ case C.kVK_ANSI_KeypadPlus:
+ return key.CodeKeypadPlusSign
+ case C.kVK_ANSI_KeypadEnter:
+ return key.CodeKeypadEnter
+ case C.kVK_ANSI_Keypad1:
+ return key.CodeKeypad1
+ case C.kVK_ANSI_Keypad2:
+ return key.CodeKeypad2
+ case C.kVK_ANSI_Keypad3:
+ return key.CodeKeypad3
+ case C.kVK_ANSI_Keypad4:
+ return key.CodeKeypad4
+ case C.kVK_ANSI_Keypad5:
+ return key.CodeKeypad5
+ case C.kVK_ANSI_Keypad6:
+ return key.CodeKeypad6
+ case C.kVK_ANSI_Keypad7:
+ return key.CodeKeypad7
+ case C.kVK_ANSI_Keypad8:
+ return key.CodeKeypad8
+ case C.kVK_ANSI_Keypad9:
+ return key.CodeKeypad9
+ case C.kVK_ANSI_Keypad0:
+ return key.CodeKeypad0
+ case C.kVK_ANSI_KeypadDecimal:
+ return key.CodeKeypadFullStop
+ case C.kVK_ANSI_KeypadEquals:
+ return key.CodeKeypadEqualSign
+ case C.kVK_F13:
+ return key.CodeF13
+ case C.kVK_F14:
+ return key.CodeF14
+ case C.kVK_F15:
+ return key.CodeF15
+ case C.kVK_F16:
+ return key.CodeF16
+ case C.kVK_F17:
+ return key.CodeF17
+ case C.kVK_F18:
+ return key.CodeF18
+ case C.kVK_F19:
+ return key.CodeF19
+ case C.kVK_F20:
+ return key.CodeF20
+ // 116: Keyboard Execute
+ case C.kVK_Help:
+ return key.CodeHelp
+ // 118: Keyboard Menu
+ // 119: Keyboard Select
+ // 120: Keyboard Stop
+ // 121: Keyboard Again
+ // 122: Keyboard Undo
+ // 123: Keyboard Cut
+ // 124: Keyboard Copy
+ // 125: Keyboard Paste
+ // 126: Keyboard Find
+ case C.kVK_Mute:
+ return key.CodeMute
+ case C.kVK_VolumeUp:
+ return key.CodeVolumeUp
+ case C.kVK_VolumeDown:
+ return key.CodeVolumeDown
+ // 130: Keyboard Locking Caps Lock
+ // 131: Keyboard Locking Num Lock
+ // 132: Keyboard Locking Scroll Lock
+ // 133: Keyboard Comma
+ // 134: Keyboard Equal Sign
+ // ...: Bunch of stuff
+ case C.kVK_Control:
+ return key.CodeLeftControl
+ case C.kVK_Shift:
+ return key.CodeLeftShift
+ case C.kVK_Option:
+ return key.CodeLeftAlt
+ case C.kVK_Command:
+ return key.CodeLeftGUI
+ case C.kVK_RightControl:
+ return key.CodeRightControl
+ case C.kVK_RightShift:
+ return key.CodeRightShift
+ case C.kVK_RightOption:
+ return key.CodeRightAlt
+ // TODO key.CodeRightGUI
+ default:
+ return key.CodeUnknown
+ }
+}
+
+func surfaceCreate() error {
+ return errors.New("gldriver: surface creation not implemented on darwin")
+}
diff --git a/shiny/driver/gldriver/cocoa.m b/shiny/driver/gldriver/cocoa.m
new file mode 100644
index 0000000..1ae3397
--- /dev/null
+++ b/shiny/driver/gldriver/cocoa.m
@@ -0,0 +1,332 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+// +build !ios
+
+#include "_cgo_export.h"
+#include <pthread.h>
+#include <stdio.h>
+
+#import <Cocoa/Cocoa.h>
+#import <Foundation/Foundation.h>
+#import <OpenGL/gl3.h>
+
+// The variables did not exist on older OS X releases,
+// we use the old variables deprecated on macOS to define them.
+#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200
+enum
+{
+ NSEventTypeScrollWheel = NSScrollWheel,
+ NSEventTypeKeyDown = NSKeyDown
+};
+enum
+{
+ NSWindowStyleMaskTitled = NSTitledWindowMask,
+ NSWindowStyleMaskResizable = NSResizableWindowMask,
+ NSWindowStyleMaskMiniaturizable = NSMiniaturizableWindowMask,
+ NSWindowStyleMaskClosable = NSClosableWindowMask
+};
+#endif
+
+void makeCurrentContext(uintptr_t context) {
+ NSOpenGLContext* ctx = (NSOpenGLContext*)context;
+ [ctx makeCurrentContext];
+}
+
+void flushContext(uintptr_t context) {
+ NSOpenGLContext* ctx = (NSOpenGLContext*)context;
+ [ctx flushBuffer];
+}
+
+uint64 threadID() {
+ uint64 id;
+ if (pthread_threadid_np(pthread_self(), &id)) {
+ abort();
+ }
+ return id;
+}
+
+@interface ScreenGLView : NSOpenGLView<NSWindowDelegate>
+{
+}
+@end
+
+@implementation ScreenGLView
+- (void)prepareOpenGL {
+ [super prepareOpenGL];
+
+ [self setWantsBestResolutionOpenGLSurface:YES];
+ GLint swapInt = 1;
+ NSOpenGLContext *ctx = [self openGLContext];
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ [ctx setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
+#pragma clang diagnostic pop
+
+ // Using attribute arrays in OpenGL 3.3 requires the use of a VBA.
+ // But VBAs don't exist in ES 2. So we bind a default one.
+ GLuint vba;
+ glGenVertexArrays(1, &vba);
+ glBindVertexArray(vba);
+
+ preparedOpenGL((GoUintptr)self, (GoUintptr)ctx, (GoUintptr)vba);
+}
+
+- (void)callSetGeom {
+ // Calculate screen PPI.
+ //
+ // Note that the backingScaleFactor converts from logical
+ // pixels to actual pixels, but both of these units vary
+ // independently from real world size. E.g.
+ //
+ // 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15
+ // 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06
+ // 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51
+ // 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03
+ NSScreen *screen = self.window.screen;
+ double screenPixW = [screen frame].size.width * [screen backingScaleFactor];
+
+ CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue];
+ CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters
+ float ppi = 25.4 * screenPixW / screenSizeMM.width;
+ float pixelsPerPt = ppi/72.0;
+
+ // The width and height reported to the geom package are the
+ // bounds of the OpenGL view. Several steps are necessary.
+ // First, [self bounds] gives us the number of logical pixels
+ // in the view. Multiplying this by the backingScaleFactor
+ // gives us the number of actual pixels.
+ NSRect r = [self bounds];
+ int w = r.size.width * [screen backingScaleFactor];
+ int h = r.size.height * [screen backingScaleFactor];
+
+ setGeom((GoUintptr)self, pixelsPerPt, w, h);
+}
+
+- (void)reshape {
+ [super reshape];
+ [self callSetGeom];
+}
+
+- (void)drawRect:(NSRect)theRect {
+ // Called during resize. Do an extra draw if we are visible.
+ // This gets rid of flicker when resizing.
+ drawgl((GoUintptr)self);
+}
+
+- (void)mouseEventNS:(NSEvent *)theEvent {
+ NSPoint p = [theEvent locationInWindow];
+ double h = self.frame.size.height;
+
+ // Both h and p are measured in Cocoa pixels, which are a fraction of
+ // physical pixels, so we multiply by backingScaleFactor.
+ double scale = [self.window.screen backingScaleFactor];
+
+ double x = p.x * scale;
+ double y = (h - p.y) * scale - 1; // flip origin from bottom-left to top-left.
+
+ double dx, dy;
+ if (theEvent.type == NSEventTypeScrollWheel) {
+ dx = theEvent.scrollingDeltaX;
+ dy = theEvent.scrollingDeltaY;
+ }
+
+ mouseEvent((GoUintptr)self, x, y, dx, dy, theEvent.type, theEvent.buttonNumber, theEvent.modifierFlags);
+}
+
+- (void)mouseMoved:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)mouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)mouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)mouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)rightMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)rightMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)otherMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)otherMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+- (void)scrollWheel:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
+
+// raw modifier key presses
+- (void)flagsChanged:(NSEvent *)theEvent {
+ flagEvent((GoUintptr)self, theEvent.modifierFlags);
+}
+
+// overrides special handling of escape and tab
+- (BOOL)performKeyEquivalent:(NSEvent *)theEvent {
+ [self key:theEvent];
+ return YES;
+}
+
+- (void)keyDown:(NSEvent *)theEvent { [self key:theEvent]; }
+- (void)keyUp:(NSEvent *)theEvent { [self key:theEvent]; }
+
+- (void)key:(NSEvent *)theEvent {
+ NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0];
+
+ uint8_t buf[4] = {0, 0, 0, 0};
+ if (![theEvent.characters getBytes:buf
+ maxLength:4
+ usedLength:nil
+ encoding:NSUTF32LittleEndianStringEncoding
+ options:NSStringEncodingConversionAllowLossy
+ range:range
+ remainingRange:nil]) {
+ NSLog(@"failed to read key event %@", theEvent);
+ return;
+ }
+
+ uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24;
+
+ uint8_t direction;
+ if ([theEvent isARepeat]) {
+ direction = 0;
+ } else if (theEvent.type == NSEventTypeKeyDown) {
+ direction = 1;
+ } else {
+ direction = 2;
+ }
+ keyEvent((GoUintptr)self, (int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags);
+}
+
+- (void)windowDidChangeScreenProfile:(NSNotification *)notification {
+ [self callSetGeom];
+}
+
+// TODO: catch windowDidMiniaturize?
+
+- (void)windowDidExpose:(NSNotification *)notification {
+ lifecycleVisible((GoUintptr)self, true);
+}
+
+- (void)windowDidBecomeKey:(NSNotification *)notification {
+ lifecycleFocused((GoUintptr)self, true);
+}
+
+- (void)windowDidResignKey:(NSNotification *)notification {
+ lifecycleFocused((GoUintptr)self, false);
+ if ([NSApp isHidden]) {
+ lifecycleVisible((GoUintptr)self, false);
+ }
+}
+
+- (void)windowWillClose:(NSNotification *)notification {
+ // TODO: is this right? Closing a window via the top-left red button
+ // seems to return early without ever calling windowClosing.
+ if (self.window.nextResponder == NULL) {
+ return; // already called close
+ }
+
+ windowClosing((GoUintptr)self);
+ [self.window.nextResponder release];
+ self.window.nextResponder = NULL;
+}
+@end
+
+@interface AppDelegate : NSObject<NSApplicationDelegate>
+{
+}
+@end
+
+@implementation AppDelegate
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+ driverStarted();
+ [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateAllWindows];
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+ lifecycleDeadAll();
+}
+
+- (void)applicationWillHide:(NSNotification *)aNotification {
+ lifecycleHideAll();
+}
+@end
+
+uintptr_t doNewWindow(int width, int height, char* title) {
+ NSScreen *screen = [NSScreen mainScreen];
+ double w = (double)width / [screen backingScaleFactor];
+ double h = (double)height / [screen backingScaleFactor];
+ __block ScreenGLView* view = NULL;
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ id menuBar = [NSMenu new];
+ id menuItem = [NSMenuItem new];
+ [menuBar addItem:menuItem];
+ [NSApp setMainMenu:menuBar];
+
+ id menu = [NSMenu new];
+ NSString* name = [[NSString alloc] initWithUTF8String:title];
+
+ id hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
+ action:@selector(hide:) keyEquivalent:@"h"];
+ [menu addItem:hideMenuItem];
+
+ id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
+ action:@selector(terminate:) keyEquivalent:@"q"];
+ [menu addItem:quitMenuItem];
+ [menuItem setSubmenu:menu];
+
+ NSRect rect = NSMakeRect(0, 0, w, h);
+
+ NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
+ styleMask:NSWindowStyleMaskTitled
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ window.styleMask |= NSWindowStyleMaskResizable;
+ window.styleMask |= NSWindowStyleMaskMiniaturizable;
+ window.styleMask |= NSWindowStyleMaskClosable;
+ window.title = name;
+ window.displaysWhenScreenProfileChanges = YES;
+ [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
+ [window setAcceptsMouseMovedEvents:YES];
+
+ NSOpenGLPixelFormatAttribute attr[] = {
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFADepthSize, 16,
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAllowOfflineRenderers,
+ 0
+ };
+ id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
+ view = [[ScreenGLView alloc] initWithFrame:rect pixelFormat:pixFormat];
+ [window setContentView:view];
+ [window setDelegate:view];
+ [window makeFirstResponder:view];
+ });
+
+ return (uintptr_t)view;
+}
+
+void doShowWindow(uintptr_t viewID) {
+ ScreenGLView* view = (ScreenGLView*)viewID;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [view.window makeKeyAndOrderFront:view.window];
+ });
+}
+
+void doCloseWindow(uintptr_t viewID) {
+ ScreenGLView* view = (ScreenGLView*)viewID;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [view.window performClose:view];
+ });
+}
+
+void startDriver() {
+ [NSAutoreleasePool new];
+ [NSApplication sharedApplication];
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ AppDelegate* delegate = [[AppDelegate alloc] init];
+ [NSApp setDelegate:delegate];
+ [NSApp run];
+}
+
+void stopDriver() {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp terminate:nil];
+ });
+}
diff --git a/shiny/driver/gldriver/context.go b/shiny/driver/gldriver/context.go
new file mode 100644
index 0000000..488a388
--- /dev/null
+++ b/shiny/driver/gldriver/context.go
@@ -0,0 +1,37 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !android
+
+package gldriver
+
+import (
+ "runtime"
+
+ "golang.org/x/mobile/gl"
+)
+
+// NewContext creates an OpenGL ES context with a dedicated processing thread.
+func NewContext() (gl.Context, error) {
+ glctx, worker := gl.NewContext()
+
+ errCh := make(chan error)
+ workAvailable := worker.WorkAvailable()
+ go func() {
+ runtime.LockOSThread()
+ err := surfaceCreate()
+ errCh <- err
+ if err != nil {
+ return
+ }
+
+ for range workAvailable {
+ worker.DoWork()
+ }
+ }()
+ if err := <-errCh; err != nil {
+ return nil, err
+ }
+ return glctx, nil
+}
diff --git a/shiny/driver/gldriver/egl.go b/shiny/driver/gldriver/egl.go
new file mode 100755
index 0000000..6f5d3d7
--- /dev/null
+++ b/shiny/driver/gldriver/egl.go
@@ -0,0 +1,106 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gldriver
+
+// These constants match the values found in the EGL 1.4 headers,
+// egl.h, eglext.h, and eglplatform.h.
+const (
+ _EGL_DONT_CARE = -1
+
+ _EGL_NO_SURFACE = 0
+ _EGL_NO_CONTEXT = 0
+ _EGL_NO_DISPLAY = 0
+
+ _EGL_OPENGL_ES2_BIT = 0x04 // EGL_RENDERABLE_TYPE mask
+ _EGL_WINDOW_BIT = 0x04 // EGL_SURFACE_TYPE mask
+
+ _EGL_OPENGL_ES_API = 0x30A0
+ _EGL_RENDERABLE_TYPE = 0x3040
+ _EGL_SURFACE_TYPE = 0x3033
+ _EGL_BUFFER_SIZE = 0x3020
+ _EGL_ALPHA_SIZE = 0x3021
+ _EGL_BLUE_SIZE = 0x3022
+ _EGL_GREEN_SIZE = 0x3023
+ _EGL_RED_SIZE = 0x3024
+ _EGL_DEPTH_SIZE = 0x3025
+ _EGL_STENCIL_SIZE = 0x3026
+ _EGL_SAMPLE_BUFFERS = 0x3032
+ _EGL_CONFIG_CAVEAT = 0x3027
+ _EGL_NONE = 0x3038
+
+ _EGL_CONTEXT_CLIENT_VERSION = 0x3098
+)
+
+// ANGLE specific options found in eglext.h
+const (
+ _EGL_PLATFORM_ANGLE_ANGLE = 0x3202
+ _EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205
+ _EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206
+
+ _EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207
+ _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208
+ _EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209
+ _EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A
+ _EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE = 0x320B
+
+ _EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D
+ _EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E
+)
+
+const (
+ _EGL_SUCCESS = 0x3000
+ _EGL_NOT_INITIALIZED = 0x3001
+ _EGL_BAD_ACCESS = 0x3002
+ _EGL_BAD_ALLOC = 0x3003
+ _EGL_BAD_ATTRIBUTE = 0x3004
+ _EGL_BAD_CONFIG = 0x3005
+ _EGL_BAD_CONTEXT = 0x3006
+ _EGL_BAD_CURRENT_SURFACE = 0x3007
+ _EGL_BAD_DISPLAY = 0x3008
+ _EGL_BAD_MATCH = 0x3009
+ _EGL_BAD_NATIVE_PIXMAP = 0x300A
+ _EGL_BAD_NATIVE_WINDOW = 0x300B
+ _EGL_BAD_PARAMETER = 0x300C
+ _EGL_BAD_SURFACE = 0x300D
+ _EGL_CONTEXT_LOST = 0x300E
+)
+
+func eglErrString(errno uintptr) string {
+ switch errno {
+ case _EGL_SUCCESS:
+ return "EGL_SUCCESS"
+ case _EGL_NOT_INITIALIZED:
+ return "EGL_NOT_INITIALIZED"
+ case _EGL_BAD_ACCESS:
+ return "EGL_BAD_ACCESS"
+ case _EGL_BAD_ALLOC:
+ return "EGL_BAD_ALLOC"
+ case _EGL_BAD_ATTRIBUTE:
+ return "EGL_BAD_ATTRIBUTE"
+ case _EGL_BAD_CONFIG:
+ return "EGL_BAD_CONFIG"
+ case _EGL_BAD_CONTEXT:
+ return "EGL_BAD_CONTEXT"
+ case _EGL_BAD_CURRENT_SURFACE:
+ return "EGL_BAD_CURRENT_SURFACE"
+ case _EGL_BAD_DISPLAY:
+ return "EGL_BAD_DISPLAY"
+ case _EGL_BAD_MATCH:
+ return "EGL_BAD_MATCH"
+ case _EGL_BAD_NATIVE_PIXMAP:
+ return "EGL_BAD_NATIVE_PIXMAP"
+ case _EGL_BAD_NATIVE_WINDOW:
+ return "EGL_BAD_NATIVE_WINDOW"
+ case _EGL_BAD_PARAMETER:
+ return "EGL_BAD_PARAMETER"
+ case _EGL_BAD_SURFACE:
+ return "EGL_BAD_SURFACE"
+ case _EGL_CONTEXT_LOST:
+ return "EGL_CONTEXT_LOST"
+ }
+ return "EGL: unknown error"
+}
diff --git a/shiny/driver/gldriver/gldriver.go b/shiny/driver/gldriver/gldriver.go
new file mode 100644
index 0000000..37eb467
--- /dev/null
+++ b/shiny/driver/gldriver/gldriver.go
@@ -0,0 +1,133 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package gldriver provides an OpenGL driver for accessing a screen.
+package gldriver // import "golang.org/x/exp/shiny/driver/gldriver"
+
+import (
+ "encoding/binary"
+ "fmt"
+ "math"
+
+ "golang.org/x/exp/shiny/driver/internal/errscreen"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/gl"
+)
+
+// Main is called by the program's main function to run the graphical
+// application.
+//
+// It calls f on the Screen, possibly in a separate goroutine, as some OS-
+// specific libraries require being on 'the main thread'. It returns when f
+// returns.
+func Main(f func(screen.Screen)) {
+ if err := main(f); err != nil {
+ f(errscreen.Stub(err))
+ }
+}
+
+func mul(a, b f64.Aff3) f64.Aff3 {
+ return f64.Aff3{
+ a[0]*b[0] + a[1]*b[3],
+ a[0]*b[1] + a[1]*b[4],
+ a[0]*b[2] + a[1]*b[5] + a[2],
+
+ a[3]*b[0] + a[4]*b[3],
+ a[3]*b[1] + a[4]*b[4],
+ a[3]*b[2] + a[4]*b[5] + a[5],
+ }
+}
+
+// writeAff3 must only be called while holding windowImpl.glctxMu.
+func writeAff3(glctx gl.Context, u gl.Uniform, a f64.Aff3) {
+ var m [9]float32
+ m[0*3+0] = float32(a[0*3+0])
+ m[0*3+1] = float32(a[1*3+0])
+ m[0*3+2] = 0
+ m[1*3+0] = float32(a[0*3+1])
+ m[1*3+1] = float32(a[1*3+1])
+ m[1*3+2] = 0
+ m[2*3+0] = float32(a[0*3+2])
+ m[2*3+1] = float32(a[1*3+2])
+ m[2*3+2] = 1
+ glctx.UniformMatrix3fv(u, m[:])
+}
+
+// f32Bytes returns the byte representation of float32 values in the given byte
+// order. byteOrder must be either binary.BigEndian or binary.LittleEndian.
+func f32Bytes(byteOrder binary.ByteOrder, values ...float32) []byte {
+ le := false
+ switch byteOrder {
+ case binary.BigEndian:
+ case binary.LittleEndian:
+ le = true
+ default:
+ panic(fmt.Sprintf("invalid byte order %v", byteOrder))
+ }
+
+ b := make([]byte, 4*len(values))
+ for i, v := range values {
+ u := math.Float32bits(v)
+ if le {
+ b[4*i+0] = byte(u >> 0)
+ b[4*i+1] = byte(u >> 8)
+ b[4*i+2] = byte(u >> 16)
+ b[4*i+3] = byte(u >> 24)
+ } else {
+ b[4*i+0] = byte(u >> 24)
+ b[4*i+1] = byte(u >> 16)
+ b[4*i+2] = byte(u >> 8)
+ b[4*i+3] = byte(u >> 0)
+ }
+ }
+ return b
+}
+
+// compileProgram must only be called while holding windowImpl.glctxMu.
+func compileProgram(glctx gl.Context, vSrc, fSrc string) (gl.Program, error) {
+ program := glctx.CreateProgram()
+ if program.Value == 0 {
+ return gl.Program{}, fmt.Errorf("gldriver: no programs available")
+ }
+
+ vertexShader, err := compileShader(glctx, gl.VERTEX_SHADER, vSrc)
+ if err != nil {
+ return gl.Program{}, err
+ }
+ fragmentShader, err := compileShader(glctx, gl.FRAGMENT_SHADER, fSrc)
+ if err != nil {
+ glctx.DeleteShader(vertexShader)
+ return gl.Program{}, err
+ }
+
+ glctx.AttachShader(program, vertexShader)
+ glctx.AttachShader(program, fragmentShader)
+ glctx.LinkProgram(program)
+
+ // Flag shaders for deletion when program is unlinked.
+ glctx.DeleteShader(vertexShader)
+ glctx.DeleteShader(fragmentShader)
+
+ if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 {
+ defer glctx.DeleteProgram(program)
+ return gl.Program{}, fmt.Errorf("gldriver: program compile: %s", glctx.GetProgramInfoLog(program))
+ }
+ return program, nil
+}
+
+// compileShader must only be called while holding windowImpl.glctxMu.
+func compileShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) {
+ shader := glctx.CreateShader(shaderType)
+ if shader.Value == 0 {
+ return gl.Shader{}, fmt.Errorf("gldriver: could not create shader (type %v)", shaderType)
+ }
+ glctx.ShaderSource(shader, src)
+ glctx.CompileShader(shader)
+ if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
+ defer glctx.DeleteShader(shader)
+ return gl.Shader{}, fmt.Errorf("gldriver: shader compile: %s", glctx.GetShaderInfoLog(shader))
+ }
+ return shader, nil
+}
diff --git a/shiny/driver/gldriver/other.go b/shiny/driver/gldriver/other.go
new file mode 100644
index 0000000..f762750
--- /dev/null
+++ b/shiny/driver/gldriver/other.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (!darwin || ios || !cgo) && (!linux || android || !cgo) && (!openbsd || !cgo) && !windows
+
+package gldriver
+
+import (
+ "fmt"
+ "runtime"
+
+ "golang.org/x/exp/shiny/screen"
+)
+
+const useLifecycler = true
+const handleSizeEventsAtChannelReceive = true
+
+var errUnsupported = fmt.Errorf("gldriver: unsupported GOOS/GOARCH %s/%s or cgo not enabled", runtime.GOOS, runtime.GOARCH)
+
+func newWindow(opts *screen.NewWindowOptions) (uintptr, error) { return 0, errUnsupported }
+
+func initWindow(id *windowImpl) {}
+func showWindow(id *windowImpl) {}
+func closeWindow(id uintptr) {}
+func drawLoop(w *windowImpl) {}
+
+func surfaceCreate() error { return errUnsupported }
+func main(f func(screen.Screen)) error { return errUnsupported }
diff --git a/shiny/driver/gldriver/screen.go b/shiny/driver/gldriver/screen.go
new file mode 100644
index 0000000..f6f18f1
--- /dev/null
+++ b/shiny/driver/gldriver/screen.go
@@ -0,0 +1,149 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gldriver
+
+import (
+ "fmt"
+ "image"
+ "sync"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/gl"
+)
+
+var theScreen = &screenImpl{
+ windows: make(map[uintptr]*windowImpl),
+}
+
+type screenImpl struct {
+ texture struct {
+ program gl.Program
+ pos gl.Attrib
+ mvp gl.Uniform
+ uvp gl.Uniform
+ inUV gl.Attrib
+ sample gl.Uniform
+ quad gl.Buffer
+ }
+ fill struct {
+ program gl.Program
+ pos gl.Attrib
+ mvp gl.Uniform
+ color gl.Uniform
+ quad gl.Buffer
+ }
+
+ mu sync.Mutex
+ windows map[uintptr]*windowImpl
+}
+
+func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
+ m := image.NewRGBA(image.Rectangle{Max: size})
+ return &bufferImpl{
+ buf: m.Pix,
+ rgba: *m,
+ size: size,
+ }, nil
+}
+
+func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
+ // TODO: can we compile these programs eagerly instead of lazily?
+
+ // Find a GL context for this texture.
+ // TODO: this might be correct. Some GL objects can be shared
+ // across contexts. But this needs a review of the spec to make
+ // sure it's correct, and some testing would be nice.
+ var w *windowImpl
+
+ s.mu.Lock()
+ for _, window := range s.windows {
+ w = window
+ break
+ }
+ s.mu.Unlock()
+
+ if w == nil {
+ return nil, fmt.Errorf("gldriver: no window available")
+ }
+
+ w.glctxMu.Lock()
+ defer w.glctxMu.Unlock()
+ glctx := w.glctx
+ if glctx == nil {
+ return nil, fmt.Errorf("gldriver: no GL context available")
+ }
+
+ if !glctx.IsProgram(s.texture.program) {
+ p, err := compileProgram(glctx, textureVertexSrc, textureFragmentSrc)
+ if err != nil {
+ return nil, err
+ }
+ s.texture.program = p
+ s.texture.pos = glctx.GetAttribLocation(p, "pos")
+ s.texture.mvp = glctx.GetUniformLocation(p, "mvp")
+ s.texture.uvp = glctx.GetUniformLocation(p, "uvp")
+ s.texture.inUV = glctx.GetAttribLocation(p, "inUV")
+ s.texture.sample = glctx.GetUniformLocation(p, "sample")
+ s.texture.quad = glctx.CreateBuffer()
+
+ glctx.BindBuffer(gl.ARRAY_BUFFER, s.texture.quad)
+ glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
+ }
+
+ t := &textureImpl{
+ w: w,
+ id: glctx.CreateTexture(),
+ size: size,
+ }
+
+ glctx.BindTexture(gl.TEXTURE_2D, t.id)
+ glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.X, size.Y, gl.RGBA, gl.UNSIGNED_BYTE, nil)
+ glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
+ glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
+ glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+ glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+
+ return t, nil
+}
+
+func optsSize(opts *screen.NewWindowOptions) (width, height int) {
+ width, height = 1024, 768
+ if opts != nil {
+ if opts.Width > 0 {
+ width = opts.Width
+ }
+ if opts.Height > 0 {
+ height = opts.Height
+ }
+ }
+ return width, height
+}
+
+func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
+ id, err := newWindow(opts)
+ if err != nil {
+ return nil, err
+ }
+ w := &windowImpl{
+ s: s,
+ id: id,
+ publish: make(chan struct{}),
+ publishDone: make(chan screen.PublishResult),
+ drawDone: make(chan struct{}),
+ }
+ initWindow(w)
+
+ s.mu.Lock()
+ s.windows[id] = w
+ s.mu.Unlock()
+
+ if useLifecycler {
+ w.lifecycler.SendEvent(w, nil)
+ }
+
+ showWindow(w)
+
+ return w, nil
+}
diff --git a/shiny/driver/gldriver/texture.go b/shiny/driver/gldriver/texture.go
new file mode 100644
index 0000000..a691ed8
--- /dev/null
+++ b/shiny/driver/gldriver/texture.go
@@ -0,0 +1,160 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gldriver
+
+import (
+ "encoding/binary"
+ "image"
+ "image/color"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/gl"
+)
+
+type textureImpl struct {
+ w *windowImpl
+ id gl.Texture
+ fb gl.Framebuffer
+ size image.Point
+}
+
+func (t *textureImpl) Size() image.Point { return t.size }
+func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} }
+
+func (t *textureImpl) Release() {
+ t.w.glctxMu.Lock()
+ defer t.w.glctxMu.Unlock()
+
+ if t.fb.Value != 0 {
+ t.w.glctx.DeleteFramebuffer(t.fb)
+ t.fb = gl.Framebuffer{}
+ }
+ t.w.glctx.DeleteTexture(t.id)
+ t.id = gl.Texture{}
+}
+
+func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ buf := src.(*bufferImpl)
+ buf.preUpload()
+
+ // src2dst is added to convert from the src coordinate space to the dst
+ // coordinate space. It is subtracted to convert the other way.
+ src2dst := dp.Sub(sr.Min)
+
+ // Clip to the source.
+ sr = sr.Intersect(buf.Bounds())
+
+ // Clip to the destination.
+ dr := sr.Add(src2dst)
+ dr = dr.Intersect(t.Bounds())
+ if dr.Empty() {
+ return
+ }
+
+ // Bring dr.Min in dst-space back to src-space to get the pixel buffer offset.
+ pix := buf.rgba.Pix[buf.rgba.PixOffset(dr.Min.X-src2dst.X, dr.Min.Y-src2dst.Y):]
+
+ t.w.glctxMu.Lock()
+ defer t.w.glctxMu.Unlock()
+
+ t.w.glctx.BindTexture(gl.TEXTURE_2D, t.id)
+
+ width := dr.Dx()
+ if width*4 == buf.rgba.Stride {
+ t.w.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, dr.Min.X, dr.Min.Y, width, dr.Dy(), gl.RGBA, gl.UNSIGNED_BYTE, pix)
+ return
+ }
+ // TODO: can we use GL_UNPACK_ROW_LENGTH with glPixelStorei for stride in
+ // ES 3.0, instead of uploading the pixels row-by-row?
+ for y, p := dr.Min.Y, 0; y < dr.Max.Y; y++ {
+ t.w.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, dr.Min.X, y, width, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix[p:])
+ p += buf.rgba.Stride
+ }
+}
+
+func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ minX := float64(dr.Min.X)
+ minY := float64(dr.Min.Y)
+ maxX := float64(dr.Max.X)
+ maxY := float64(dr.Max.Y)
+ mvp := calcMVP(
+ t.size.X, t.size.Y,
+ minX, minY,
+ maxX, minY,
+ minX, maxY,
+ )
+
+ glctx := t.w.glctx
+
+ t.w.glctxMu.Lock()
+ defer t.w.glctxMu.Unlock()
+
+ create := t.fb.Value == 0
+ if create {
+ t.fb = glctx.CreateFramebuffer()
+ }
+ glctx.BindFramebuffer(gl.FRAMEBUFFER, t.fb)
+ if create {
+ glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.id, 0)
+ }
+
+ glctx.Viewport(0, 0, t.size.X, t.size.Y)
+ doFill(t.w.s, t.w.glctx, mvp, src, op)
+
+ // We can't restore the GL state (i.e. bind the back buffer, also known as
+ // gl.Framebuffer{Value: 0}) right away, since we don't necessarily know
+ // the right viewport size yet. It is valid to call textureImpl.Fill before
+ // we've gotten our first size.Event. We bind it lazily instead.
+ t.w.backBufferBound = false
+}
+
+var quadCoords = f32Bytes(binary.LittleEndian,
+ 0, 0, // top left
+ 1, 0, // top right
+ 0, 1, // bottom left
+ 1, 1, // bottom right
+)
+
+const textureVertexSrc = `#version 100
+uniform mat3 mvp;
+uniform mat3 uvp;
+attribute vec3 pos;
+attribute vec2 inUV;
+varying vec2 uv;
+void main() {
+ vec3 p = pos;
+ p.z = 1.0;
+ gl_Position = vec4(mvp * p, 1);
+ uv = (uvp * vec3(inUV, 1)).xy;
+}
+`
+
+const textureFragmentSrc = `#version 100
+precision mediump float;
+varying vec2 uv;
+uniform sampler2D sample;
+void main() {
+ gl_FragColor = texture2D(sample, uv);
+}
+`
+
+const fillVertexSrc = `#version 100
+uniform mat3 mvp;
+attribute vec3 pos;
+void main() {
+ vec3 p = pos;
+ p.z = 1.0;
+ gl_Position = vec4(mvp * p, 1);
+}
+`
+
+const fillFragmentSrc = `#version 100
+precision mediump float;
+uniform vec4 color;
+void main() {
+ gl_FragColor = color;
+}
+`
diff --git a/shiny/driver/gldriver/win32.go b/shiny/driver/gldriver/win32.go
new file mode 100755
index 0000000..c94549e
--- /dev/null
+++ b/shiny/driver/gldriver/win32.go
@@ -0,0 +1,357 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package gldriver
+
+import (
+ "errors"
+ "fmt"
+ "runtime"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/internal/win32"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+ "golang.org/x/mobile/gl"
+)
+
+// TODO: change this to true, after manual testing on Win32.
+const useLifecycler = false
+
+// TODO: change this to true, after manual testing on Win32.
+const handleSizeEventsAtChannelReceive = false
+
+func main(f func(screen.Screen)) error {
+ return win32.Main(func() { f(theScreen) })
+}
+
+var (
+ eglGetPlatformDisplayEXT = gl.LibEGL.NewProc("eglGetPlatformDisplayEXT")
+ eglInitialize = gl.LibEGL.NewProc("eglInitialize")
+ eglChooseConfig = gl.LibEGL.NewProc("eglChooseConfig")
+ eglGetError = gl.LibEGL.NewProc("eglGetError")
+ eglBindAPI = gl.LibEGL.NewProc("eglBindAPI")
+ eglCreateWindowSurface = gl.LibEGL.NewProc("eglCreateWindowSurface")
+ eglCreateContext = gl.LibEGL.NewProc("eglCreateContext")
+ eglMakeCurrent = gl.LibEGL.NewProc("eglMakeCurrent")
+ eglSwapInterval = gl.LibEGL.NewProc("eglSwapInterval")
+ eglDestroySurface = gl.LibEGL.NewProc("eglDestroySurface")
+ eglSwapBuffers = gl.LibEGL.NewProc("eglSwapBuffers")
+)
+
+type eglConfig uintptr // void*
+
+type eglInt int32
+
+var rgb888 = [...]eglInt{
+ _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT,
+ _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT,
+ _EGL_BLUE_SIZE, 8,
+ _EGL_GREEN_SIZE, 8,
+ _EGL_RED_SIZE, 8,
+ _EGL_DEPTH_SIZE, 16,
+ _EGL_STENCIL_SIZE, 8,
+ _EGL_NONE,
+}
+
+type ctxWin32 struct {
+ ctx uintptr
+ display uintptr // EGLDisplay
+ surface uintptr // EGLSurface
+}
+
+func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
+ w, err := win32.NewWindow(opts)
+ if err != nil {
+ return 0, err
+ }
+ return uintptr(w), nil
+}
+
+func initWindow(w *windowImpl) {
+ w.glctx, w.worker = gl.NewContext()
+}
+
+func showWindow(w *windowImpl) {
+ // Show makes an initial call to sizeEvent (via win32.SizeEvent), where
+ // we setup the EGL surface and GL context.
+ win32.Show(syscall.Handle(w.id))
+}
+
+func closeWindow(id uintptr) {} // TODO
+
+func drawLoop(w *windowImpl) {
+ runtime.LockOSThread()
+
+ display := w.ctx.(ctxWin32).display
+ surface := w.ctx.(ctxWin32).surface
+ ctx := w.ctx.(ctxWin32).ctx
+
+ if ret, _, _ := eglMakeCurrent.Call(display, surface, surface, ctx); ret == 0 {
+ panic(fmt.Sprintf("eglMakeCurrent failed: %v", eglErr()))
+ }
+
+ // TODO(crawshaw): exit this goroutine on Release.
+ workAvailable := w.worker.WorkAvailable()
+ for {
+ select {
+ case <-workAvailable:
+ w.worker.DoWork()
+ case <-w.publish:
+ loop:
+ for {
+ select {
+ case <-workAvailable:
+ w.worker.DoWork()
+ default:
+ break loop
+ }
+ }
+ if ret, _, _ := eglSwapBuffers.Call(display, surface); ret == 0 {
+ panic(fmt.Sprintf("eglSwapBuffers failed: %v", eglErr()))
+ }
+ w.publishDone <- screen.PublishResult{}
+ }
+ }
+}
+
+func init() {
+ win32.SizeEvent = sizeEvent
+ win32.PaintEvent = paintEvent
+ win32.MouseEvent = mouseEvent
+ win32.KeyEvent = keyEvent
+ win32.LifecycleEvent = lifecycleEvent
+}
+
+func lifecycleEvent(hwnd syscall.Handle, to lifecycle.Stage) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[uintptr(hwnd)]
+ theScreen.mu.Unlock()
+
+ if w.lifecycleStage == to {
+ return
+ }
+ w.Send(lifecycle.Event{
+ From: w.lifecycleStage,
+ To: to,
+ DrawContext: w.glctx,
+ })
+ w.lifecycleStage = to
+}
+
+func mouseEvent(hwnd syscall.Handle, e mouse.Event) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[uintptr(hwnd)]
+ theScreen.mu.Unlock()
+
+ w.Send(e)
+}
+
+func keyEvent(hwnd syscall.Handle, e key.Event) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[uintptr(hwnd)]
+ theScreen.mu.Unlock()
+
+ w.Send(e)
+}
+
+func paintEvent(hwnd syscall.Handle, e paint.Event) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[uintptr(hwnd)]
+ theScreen.mu.Unlock()
+
+ if w.ctx == nil {
+ // Sometimes a paint event comes in before initial
+ // window size is set. Ignore it.
+ return
+ }
+
+ // TODO: the paint.Event should have External: true.
+ w.Send(paint.Event{})
+}
+
+func sizeEvent(hwnd syscall.Handle, e size.Event) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[uintptr(hwnd)]
+ theScreen.mu.Unlock()
+
+ if w.ctx == nil {
+ // This is the initial size event on window creation.
+ // Create an EGL surface and spin up a GL context.
+ if err := createEGLSurface(hwnd, w); err != nil {
+ panic(err)
+ }
+ go drawLoop(w)
+ }
+
+ if !handleSizeEventsAtChannelReceive {
+ w.szMu.Lock()
+ w.sz = e
+ w.szMu.Unlock()
+ }
+
+ w.Send(e)
+
+ if handleSizeEventsAtChannelReceive {
+ return
+ }
+
+ // Screen is dirty, generate a paint event.
+ //
+ // The sizeEvent function is called on the goroutine responsible for
+ // calling the GL worker.DoWork. When compiling with -tags gldebug,
+ // these GL calls are blocking (so we can read the error message), so
+ // to make progress they need to happen on another goroutine.
+ go func() {
+ // TODO: this call to Viewport is not right, but is very hard to
+ // do correctly with our async events channel model. We want
+ // the call to Viewport to be made the instant before the
+ // paint.Event is received.
+ w.glctxMu.Lock()
+ w.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
+ w.glctx.ClearColor(0, 0, 0, 1)
+ w.glctx.Clear(gl.COLOR_BUFFER_BIT)
+ w.glctxMu.Unlock()
+
+ w.Send(paint.Event{})
+ }()
+}
+
+func eglErr() error {
+ if ret, _, _ := eglGetError.Call(); ret != _EGL_SUCCESS {
+ return errors.New(eglErrString(ret))
+ }
+ return nil
+}
+
+func createEGLSurface(hwnd syscall.Handle, w *windowImpl) error {
+ var displayAttribPlatforms = [][]eglInt{
+ // Default
+ []eglInt{
+ _EGL_PLATFORM_ANGLE_TYPE_ANGLE,
+ _EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_NONE,
+ },
+ // Direct3D 11
+ []eglInt{
+ _EGL_PLATFORM_ANGLE_TYPE_ANGLE,
+ _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_NONE,
+ },
+ // Direct3D 9
+ []eglInt{
+ _EGL_PLATFORM_ANGLE_TYPE_ANGLE,
+ _EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_NONE,
+ },
+ // Direct3D 11 with WARP
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/gg615082.aspx
+ []eglInt{
+ _EGL_PLATFORM_ANGLE_TYPE_ANGLE,
+ _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
+ _EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
+ _EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE,
+ _EGL_NONE,
+ },
+ }
+
+ dc, err := win32.GetDC(hwnd)
+ if err != nil {
+ return fmt.Errorf("win32.GetDC failed: %v", err)
+ }
+
+ var display uintptr = _EGL_NO_DISPLAY
+ for i, displayAttrib := range displayAttribPlatforms {
+ lastTry := i == len(displayAttribPlatforms)-1
+
+ display, _, _ = eglGetPlatformDisplayEXT.Call(
+ _EGL_PLATFORM_ANGLE_ANGLE,
+ uintptr(dc),
+ uintptr(unsafe.Pointer(&displayAttrib[0])),
+ )
+
+ if display == _EGL_NO_DISPLAY {
+ if !lastTry {
+ continue
+ }
+ return fmt.Errorf("eglGetPlatformDisplayEXT failed: %v", eglErr())
+ }
+
+ if ret, _, _ := eglInitialize.Call(display, 0, 0); ret == 0 {
+ if !lastTry {
+ continue
+ }
+ return fmt.Errorf("eglInitialize failed: %v", eglErr())
+ }
+ }
+
+ eglBindAPI.Call(_EGL_OPENGL_ES_API)
+ if err := eglErr(); err != nil {
+ return err
+ }
+
+ var numConfigs eglInt
+ var config eglConfig
+ ret, _, _ := eglChooseConfig.Call(
+ display,
+ uintptr(unsafe.Pointer(&rgb888[0])),
+ uintptr(unsafe.Pointer(&config)),
+ 1,
+ uintptr(unsafe.Pointer(&numConfigs)),
+ )
+ if ret == 0 {
+ return fmt.Errorf("eglChooseConfig failed: %v", eglErr())
+ }
+ if numConfigs <= 0 {
+ return errors.New("eglChooseConfig found no valid config")
+ }
+
+ surface, _, _ := eglCreateWindowSurface.Call(display, uintptr(config), uintptr(hwnd), 0, 0)
+ if surface == _EGL_NO_SURFACE {
+ return fmt.Errorf("eglCreateWindowSurface failed: %v", eglErr())
+ }
+
+ contextAttribs := [...]eglInt{
+ _EGL_CONTEXT_CLIENT_VERSION, 2,
+ _EGL_NONE,
+ }
+ context, _, _ := eglCreateContext.Call(
+ display,
+ uintptr(config),
+ _EGL_NO_CONTEXT,
+ uintptr(unsafe.Pointer(&contextAttribs[0])),
+ )
+ if context == _EGL_NO_CONTEXT {
+ return fmt.Errorf("eglCreateContext failed: %v", eglErr())
+ }
+
+ eglSwapInterval.Call(display, 1)
+
+ w.ctx = ctxWin32{
+ ctx: context,
+ display: display,
+ surface: surface,
+ }
+
+ return nil
+}
+
+func surfaceCreate() error {
+ return errors.New("gldriver: surface creation not implemented on windows")
+}
diff --git a/shiny/driver/gldriver/window.go b/shiny/driver/gldriver/window.go
new file mode 100644
index 0000000..82baf2c
--- /dev/null
+++ b/shiny/driver/gldriver/window.go
@@ -0,0 +1,389 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gldriver
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "sync"
+
+ "golang.org/x/exp/shiny/driver/internal/drawer"
+ "golang.org/x/exp/shiny/driver/internal/event"
+ "golang.org/x/exp/shiny/driver/internal/lifecycler"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/size"
+ "golang.org/x/mobile/gl"
+)
+
+type windowImpl struct {
+ s *screenImpl
+
+ // id is an OS-specific data structure for the window.
+ // - Cocoa: ScreenGLView*
+ // - X11: Window
+ // - Windows: win32.HWND
+ id uintptr
+
+ // ctx is a C data structure for the GL context.
+ // - Cocoa: uintptr holding a NSOpenGLContext*.
+ // - X11: uintptr holding an EGLSurface.
+ // - Windows: ctxWin32
+ ctx interface{}
+
+ lifecycler lifecycler.State
+ // TODO: Delete the field below (and the useLifecycler constant), and use
+ // the field above for cocoa and win32.
+ lifecycleStage lifecycle.Stage // current stage
+
+ event.Deque
+ publish chan struct{}
+ publishDone chan screen.PublishResult
+ drawDone chan struct{}
+
+ // glctxMu is a mutex that enforces the atomicity of methods like
+ // Texture.Upload or Window.Draw that are conceptually one operation
+ // but are implemented by multiple OpenGL calls. OpenGL is a stateful
+ // API, so interleaving OpenGL calls from separate higher-level
+ // operations causes inconsistencies.
+ glctxMu sync.Mutex
+ glctx gl.Context
+ worker gl.Worker
+ // backBufferBound is whether the default Framebuffer, with ID 0, also
+ // known as the back buffer or the window's Framebuffer, is bound and its
+ // viewport is known to equal the window size. It can become false when we
+ // bind to a texture's Framebuffer or when the window size changes.
+ backBufferBound bool
+
+ // szMu protects only sz. If you need to hold both glctxMu and szMu, the
+ // lock ordering is to lock glctxMu first (and unlock it last).
+ szMu sync.Mutex
+ sz size.Event
+}
+
+// NextEvent implements the screen.EventDeque interface.
+func (w *windowImpl) NextEvent() interface{} {
+ e := w.Deque.NextEvent()
+ if handleSizeEventsAtChannelReceive {
+ if sz, ok := e.(size.Event); ok {
+ w.glctxMu.Lock()
+ w.backBufferBound = false
+ w.szMu.Lock()
+ w.sz = sz
+ w.szMu.Unlock()
+ w.glctxMu.Unlock()
+ }
+ }
+ return e
+}
+
+func (w *windowImpl) Release() {
+ // There are two ways a window can be closed: the Operating System or
+ // Desktop Environment can initiate (e.g. in response to a user clicking a
+ // red button), or the Go app can programatically close the window (by
+ // calling Window.Release).
+ //
+ // When the OS closes a window:
+ // - Cocoa: Obj-C's windowWillClose calls Go's windowClosing.
+ // - X11: the X11 server sends a WM_DELETE_WINDOW message.
+ // - Windows: TODO: implement and document this.
+ //
+ // This should send a lifecycle event (To: StageDead) to the Go app's event
+ // loop, which should respond by calling Window.Release (this method).
+ // Window.Release is where system resources are actually cleaned up.
+ //
+ // When Window.Release is called, the closeWindow call below:
+ // - Cocoa: calls Obj-C's performClose, which emulates the red button
+ // being clicked. (TODO: document how this actually cleans up
+ // resources??)
+ // - X11: calls C's XDestroyWindow.
+ // - Windows: TODO: implement and document this.
+ //
+ // On Cocoa, if these two approaches race, experiments suggest that the
+ // race is won by performClose (which is called serially on the main
+ // thread). Even if that isn't true, the windowWillClose handler is
+ // idempotent.
+
+ theScreen.mu.Lock()
+ delete(theScreen.windows, w.id)
+ theScreen.mu.Unlock()
+
+ closeWindow(w.id)
+}
+
+func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ originalSRMin := sr.Min
+ sr = sr.Intersect(src.Bounds())
+ if sr.Empty() {
+ return
+ }
+ dp = dp.Add(sr.Min.Sub(originalSRMin))
+ // TODO: keep a texture around for this purpose?
+ t, err := w.s.NewTexture(sr.Size())
+ if err != nil {
+ panic(err)
+ }
+ t.Upload(image.Point{}, src, sr)
+ w.Draw(f64.Aff3{
+ 1, 0, float64(dp.X),
+ 0, 1, float64(dp.Y),
+ }, t, t.Bounds(), draw.Src, nil)
+ t.Release()
+}
+
+func useOp(glctx gl.Context, op draw.Op) {
+ if op == draw.Over {
+ glctx.Enable(gl.BLEND)
+ glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
+ } else {
+ glctx.Disable(gl.BLEND)
+ }
+}
+
+func (w *windowImpl) bindBackBuffer() {
+ w.szMu.Lock()
+ sz := w.sz
+ w.szMu.Unlock()
+
+ w.backBufferBound = true
+ w.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
+ w.glctx.Viewport(0, 0, sz.WidthPx, sz.HeightPx)
+}
+
+func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) {
+ w.glctxMu.Lock()
+ defer w.glctxMu.Unlock()
+
+ if !w.backBufferBound {
+ w.bindBackBuffer()
+ }
+
+ doFill(w.s, w.glctx, mvp, src, op)
+}
+
+func doFill(s *screenImpl, glctx gl.Context, mvp f64.Aff3, src color.Color, op draw.Op) {
+ useOp(glctx, op)
+ if !glctx.IsProgram(s.fill.program) {
+ p, err := compileProgram(glctx, fillVertexSrc, fillFragmentSrc)
+ if err != nil {
+ // TODO: initialize this somewhere else we can better handle the error.
+ panic(err.Error())
+ }
+ s.fill.program = p
+ s.fill.pos = glctx.GetAttribLocation(p, "pos")
+ s.fill.mvp = glctx.GetUniformLocation(p, "mvp")
+ s.fill.color = glctx.GetUniformLocation(p, "color")
+ s.fill.quad = glctx.CreateBuffer()
+
+ glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
+ glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW)
+ }
+ glctx.UseProgram(s.fill.program)
+
+ writeAff3(glctx, s.fill.mvp, mvp)
+
+ r, g, b, a := src.RGBA()
+ glctx.Uniform4f(
+ s.fill.color,
+ float32(r)/65535,
+ float32(g)/65535,
+ float32(b)/65535,
+ float32(a)/65535,
+ )
+
+ glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad)
+ glctx.EnableVertexAttribArray(s.fill.pos)
+ glctx.VertexAttribPointer(s.fill.pos, 2, gl.FLOAT, false, 0, 0)
+
+ glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
+
+ glctx.DisableVertexAttribArray(s.fill.pos)
+}
+
+func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ minX := float64(dr.Min.X)
+ minY := float64(dr.Min.Y)
+ maxX := float64(dr.Max.X)
+ maxY := float64(dr.Max.Y)
+ w.fill(w.mvp(
+ minX, minY,
+ maxX, minY,
+ minX, maxY,
+ ), src, op)
+}
+
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ minX := float64(sr.Min.X)
+ minY := float64(sr.Min.Y)
+ maxX := float64(sr.Max.X)
+ maxY := float64(sr.Max.Y)
+ w.fill(w.mvp(
+ src2dst[0]*minX+src2dst[1]*minY+src2dst[2],
+ src2dst[3]*minX+src2dst[4]*minY+src2dst[5],
+ src2dst[0]*maxX+src2dst[1]*minY+src2dst[2],
+ src2dst[3]*maxX+src2dst[4]*minY+src2dst[5],
+ src2dst[0]*minX+src2dst[1]*maxY+src2dst[2],
+ src2dst[3]*minX+src2dst[4]*maxY+src2dst[5],
+ ), src, op)
+}
+
+func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ t := src.(*textureImpl)
+ sr = sr.Intersect(t.Bounds())
+ if sr.Empty() {
+ return
+ }
+
+ w.glctxMu.Lock()
+ defer w.glctxMu.Unlock()
+
+ if !w.backBufferBound {
+ w.bindBackBuffer()
+ }
+
+ useOp(w.glctx, op)
+ w.glctx.UseProgram(w.s.texture.program)
+
+ // Start with src-space left, top, right and bottom.
+ srcL := float64(sr.Min.X)
+ srcT := float64(sr.Min.Y)
+ srcR := float64(sr.Max.X)
+ srcB := float64(sr.Max.Y)
+ // Transform to dst-space via the src2dst matrix, then to a MVP matrix.
+ writeAff3(w.glctx, w.s.texture.mvp, w.mvp(
+ src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2],
+ src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5],
+ src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2],
+ src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5],
+ src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2],
+ src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5],
+ ))
+
+ // OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1),
+ // unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1).
+ //
+ // We are drawing a rectangle PQRS, defined by two of its
+ // corners, onto the entire texture. The two quads may actually
+ // be equal, but in the general case, PQRS can be smaller.
+ //
+ // (0,0) +---------------+ (1,0)
+ // | P +-----+ Q |
+ // | | | |
+ // | S +-----+ R |
+ // (0,1) +---------------+ (1,1)
+ //
+ // The PQRS quad is always axis-aligned. First of all, convert
+ // from pixel space to texture space.
+ tw := float64(t.size.X)
+ th := float64(t.size.Y)
+ px := float64(sr.Min.X-0) / tw
+ py := float64(sr.Min.Y-0) / th
+ qx := float64(sr.Max.X-0) / tw
+ sy := float64(sr.Max.Y-0) / th
+ // Due to axis alignment, qy = py and sx = px.
+ //
+ // The simultaneous equations are:
+ // 0 + 0 + a02 = px
+ // 0 + 0 + a12 = py
+ // a00 + 0 + a02 = qx
+ // a10 + 0 + a12 = qy = py
+ // 0 + a01 + a02 = sx = px
+ // 0 + a11 + a12 = sy
+ writeAff3(w.glctx, w.s.texture.uvp, f64.Aff3{
+ qx - px, 0, px,
+ 0, sy - py, py,
+ })
+
+ w.glctx.ActiveTexture(gl.TEXTURE0)
+ w.glctx.BindTexture(gl.TEXTURE_2D, t.id)
+ w.glctx.Uniform1i(w.s.texture.sample, 0)
+
+ w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad)
+ w.glctx.EnableVertexAttribArray(w.s.texture.pos)
+ w.glctx.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0)
+
+ w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad)
+ w.glctx.EnableVertexAttribArray(w.s.texture.inUV)
+ w.glctx.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0)
+
+ w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
+
+ w.glctx.DisableVertexAttribArray(w.s.texture.pos)
+ w.glctx.DisableVertexAttribArray(w.s.texture.inUV)
+}
+
+func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Copy(w, dp, src, sr, op, opts)
+}
+
+func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Scale(w, dr, src, sr, op, opts)
+}
+
+func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
+ w.szMu.Lock()
+ sz := w.sz
+ w.szMu.Unlock()
+
+ return calcMVP(sz.WidthPx, sz.HeightPx, tlx, tly, trx, try, blx, bly)
+}
+
+// calcMVP returns the Model View Projection matrix that maps the quadCoords
+// unit square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader
+// space corresponds to the quad QP in pixel space, where QP is defined by
+// three of its four corners - the arguments to this function. The three
+// corners are nominally the top-left, top-right and bottom-left, but there is
+// no constraint that e.g. tlx < trx.
+//
+// In pixel space, the window ranges from (0, 0) to (widthPx, heightPx). The
+// Y-axis points downwards.
+//
+// In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which
+// is a 2-unit by 2-unit square. The Y-axis points upwards.
+func calcMVP(widthPx, heightPx int, tlx, tly, trx, try, blx, bly float64) f64.Aff3 {
+ // Convert from pixel coords to vertex shader coords.
+ invHalfWidth := +2 / float64(widthPx)
+ invHalfHeight := -2 / float64(heightPx)
+ tlx = tlx*invHalfWidth - 1
+ tly = tly*invHalfHeight + 1
+ trx = trx*invHalfWidth - 1
+ try = try*invHalfHeight + 1
+ blx = blx*invHalfWidth - 1
+ bly = bly*invHalfHeight + 1
+
+ // The resultant affine matrix:
+ // - maps (0, 0) to (tlx, tly).
+ // - maps (1, 0) to (trx, try).
+ // - maps (0, 1) to (blx, bly).
+ return f64.Aff3{
+ trx - tlx, blx - tlx, tlx,
+ try - tly, bly - tly, tly,
+ }
+}
+
+func (w *windowImpl) Publish() screen.PublishResult {
+ // gl.Flush is a lightweight (on modern GL drivers) blocking call
+ // that ensures all GL functions pending in the gl package have
+ // been passed onto the GL driver before the app package attempts
+ // to swap the screen buffer.
+ //
+ // This enforces that the final receive (for this paint cycle) on
+ // gl.WorkAvailable happens before the send on publish.
+ w.glctxMu.Lock()
+ w.glctx.Flush()
+ w.glctxMu.Unlock()
+
+ w.publish <- struct{}{}
+ res := <-w.publishDone
+
+ select {
+ case w.drawDone <- struct{}{}:
+ default:
+ }
+
+ return res
+}
diff --git a/shiny/driver/gldriver/x11.c b/shiny/driver/gldriver/x11.c
new file mode 100644
index 0000000..e7fb289
--- /dev/null
+++ b/shiny/driver/gldriver/x11.c
@@ -0,0 +1,330 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux,!android openbsd
+
+#include "_cgo_export.h"
+#include <EGL/egl.h>
+#include <X11/Xlib.h> // for Atom, Colormap, Display, Window
+#include <X11/Xutil.h> // for XVisualInfo
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Atom net_wm_name;
+Atom utf8_string;
+Atom wm_delete_window;
+Atom wm_protocols;
+Atom wm_take_focus;
+
+EGLConfig e_config;
+EGLContext e_ctx;
+EGLDisplay e_dpy;
+Colormap x_colormap;
+Display *x_dpy;
+XVisualInfo *x_visual_info;
+Window x_root;
+
+// TODO: share code with eglErrString
+char *
+eglGetErrorStr() {
+ switch (eglGetError()) {
+ case EGL_SUCCESS:
+ return "EGL_SUCCESS";
+ case EGL_NOT_INITIALIZED:
+ return "EGL_NOT_INITIALIZED";
+ case EGL_BAD_ACCESS:
+ return "EGL_BAD_ACCESS";
+ case EGL_BAD_ALLOC:
+ return "EGL_BAD_ALLOC";
+ case EGL_BAD_ATTRIBUTE:
+ return "EGL_BAD_ATTRIBUTE";
+ case EGL_BAD_CONFIG:
+ return "EGL_BAD_CONFIG";
+ case EGL_BAD_CONTEXT:
+ return "EGL_BAD_CONTEXT";
+ case EGL_BAD_CURRENT_SURFACE:
+ return "EGL_BAD_CURRENT_SURFACE";
+ case EGL_BAD_DISPLAY:
+ return "EGL_BAD_DISPLAY";
+ case EGL_BAD_MATCH:
+ return "EGL_BAD_MATCH";
+ case EGL_BAD_NATIVE_PIXMAP:
+ return "EGL_BAD_NATIVE_PIXMAP";
+ case EGL_BAD_NATIVE_WINDOW:
+ return "EGL_BAD_NATIVE_WINDOW";
+ case EGL_BAD_PARAMETER:
+ return "EGL_BAD_PARAMETER";
+ case EGL_BAD_SURFACE:
+ return "EGL_BAD_SURFACE";
+ case EGL_CONTEXT_LOST:
+ return "EGL_CONTEXT_LOST";
+ }
+ return "unknown EGL error";
+}
+
+void
+startDriver() {
+ x_dpy = XOpenDisplay(NULL);
+ if (!x_dpy) {
+ fprintf(stderr, "XOpenDisplay failed\n");
+ exit(1);
+ }
+ e_dpy = eglGetDisplay(x_dpy);
+ if (!e_dpy) {
+ fprintf(stderr, "eglGetDisplay failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+ EGLint e_major, e_minor;
+ if (!eglInitialize(e_dpy, &e_major, &e_minor)) {
+ fprintf(stderr, "eglInitialize failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+ if (!eglBindAPI(EGL_OPENGL_ES_API)) {
+ fprintf(stderr, "eglBindAPI failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+
+ static const EGLint attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_BLUE_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_RED_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_CONFIG_CAVEAT, EGL_NONE,
+ EGL_NONE
+ };
+ EGLint num_configs;
+ if (!eglChooseConfig(e_dpy, attribs, &e_config, 1, &num_configs)) {
+ fprintf(stderr, "eglChooseConfig failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+ EGLint vid;
+ if (!eglGetConfigAttrib(e_dpy, e_config, EGL_NATIVE_VISUAL_ID, &vid)) {
+ fprintf(stderr, "eglGetConfigAttrib failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+
+ XVisualInfo visTemplate;
+ visTemplate.visualid = vid;
+ int num_visuals;
+ x_visual_info = XGetVisualInfo(x_dpy, VisualIDMask, &visTemplate, &num_visuals);
+ if (!x_visual_info) {
+ fprintf(stderr, "XGetVisualInfo failed\n");
+ exit(1);
+ }
+
+ x_root = RootWindow(x_dpy, DefaultScreen(x_dpy));
+ x_colormap = XCreateColormap(x_dpy, x_root, x_visual_info->visual, AllocNone);
+ if (!x_colormap) {
+ fprintf(stderr, "XCreateColormap failed\n");
+ exit(1);
+ }
+
+ static const EGLint ctx_attribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 3,
+ EGL_NONE
+ };
+ e_ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs);
+ if (!e_ctx) {
+ fprintf(stderr, "eglCreateContext failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+
+ net_wm_name = XInternAtom(x_dpy, "_NET_WM_NAME", False);
+ utf8_string = XInternAtom(x_dpy, "UTF8_STRING", False);
+ wm_delete_window = XInternAtom(x_dpy, "WM_DELETE_WINDOW", False);
+ wm_protocols = XInternAtom(x_dpy, "WM_PROTOCOLS", False);
+ wm_take_focus = XInternAtom(x_dpy, "WM_TAKE_FOCUS", False);
+
+ const int key_lo = 8;
+ const int key_hi = 255;
+ int keysyms_per_keycode;
+ KeySym *keysyms = XGetKeyboardMapping(x_dpy, key_lo, key_hi-key_lo+1, &keysyms_per_keycode);
+ if (keysyms_per_keycode < 2) {
+ fprintf(stderr, "XGetKeyboardMapping returned too few keysyms per keycode: %d\n", keysyms_per_keycode);
+ exit(1);
+ }
+ int k;
+ for (k = key_lo; k <= key_hi; k++) {
+ onKeysym(k,
+ keysyms[(k-key_lo)*keysyms_per_keycode + 0],
+ keysyms[(k-key_lo)*keysyms_per_keycode + 1]);
+ }
+ //TODO: use GetModifierMapping to figure out which modifier is the numlock modifier.
+}
+
+void
+processEvents() {
+ while (XPending(x_dpy)) {
+ XEvent ev;
+ XNextEvent(x_dpy, &ev);
+ switch (ev.type) {
+ case KeyPress:
+ case KeyRelease:
+ onKey(ev.xkey.window, ev.xkey.state, ev.xkey.keycode, ev.type == KeyPress ? 1 : 2);
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ onMouse(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y, ev.xbutton.state, ev.xbutton.button,
+ ev.type == ButtonPress ? 1 : 2);
+ break;
+ case MotionNotify:
+ onMouse(ev.xmotion.window, ev.xmotion.x, ev.xmotion.y, ev.xmotion.state, 0, 0);
+ break;
+ case FocusIn:
+ case FocusOut:
+ onFocus(ev.xmotion.window, ev.type == FocusIn);
+ break;
+ case Expose:
+ // A non-zero Count means that there are more expose events coming. For
+ // example, a non-rectangular exposure (e.g. from a partially overlapped
+ // window) will result in multiple expose events whose dirty rectangles
+ // combine to define the dirty region. Go's paint events do not provide
+ // dirty regions, so we only pass on the final X11 expose event.
+ if (ev.xexpose.count == 0) {
+ onExpose(ev.xexpose.window);
+ }
+ break;
+ case ConfigureNotify:
+ onConfigure(ev.xconfigure.window, ev.xconfigure.x, ev.xconfigure.y,
+ ev.xconfigure.width, ev.xconfigure.height,
+ DisplayWidth(x_dpy, DefaultScreen(x_dpy)),
+ DisplayWidthMM(x_dpy, DefaultScreen(x_dpy)));
+ break;
+ case ClientMessage:
+ if ((ev.xclient.message_type != wm_protocols) || (ev.xclient.format != 32)) {
+ break;
+ }
+ Atom a = ev.xclient.data.l[0];
+ if (a == wm_delete_window) {
+ onDeleteWindow(ev.xclient.window);
+ } else if (a == wm_take_focus) {
+ XSetInputFocus(x_dpy, ev.xclient.window, RevertToParent, ev.xclient.data.l[1]);
+ }
+ break;
+ }
+ }
+}
+
+void
+makeCurrent(uintptr_t surface) {
+ EGLSurface surf = (EGLSurface)(surface);
+ if (!eglMakeCurrent(e_dpy, surf, surf, e_ctx)) {
+ fprintf(stderr, "eglMakeCurrent failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+}
+
+void
+swapBuffers(uintptr_t surface) {
+ EGLSurface surf = (EGLSurface)(surface);
+ if (!eglSwapBuffers(e_dpy, surf)) {
+ fprintf(stderr, "eglSwapBuffers failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+}
+
+void
+doCloseWindow(uintptr_t id) {
+ Window win = (Window)(id);
+ XDestroyWindow(x_dpy, win);
+}
+
+uintptr_t
+doNewWindow(int width, int height, char* title, int title_len) {
+ XSetWindowAttributes attr;
+ attr.colormap = x_colormap;
+ attr.event_mask =
+ KeyPressMask |
+ KeyReleaseMask |
+ ButtonPressMask |
+ ButtonReleaseMask |
+ PointerMotionMask |
+ ExposureMask |
+ StructureNotifyMask |
+ FocusChangeMask;
+
+ Window win = XCreateWindow(
+ x_dpy, x_root, 0, 0, width, height, 0, x_visual_info->depth, InputOutput,
+ x_visual_info->visual, CWColormap | CWEventMask, &attr);
+
+ XSizeHints sizehints;
+ sizehints.width = width;
+ sizehints.height = height;
+ sizehints.flags = USSize;
+ XSetNormalHints(x_dpy, win, &sizehints);
+
+ Atom atoms[2];
+ atoms[0] = wm_delete_window;
+ atoms[1] = wm_take_focus;
+ XSetWMProtocols(x_dpy, win, atoms, 2);
+
+ XSetStandardProperties(x_dpy, win, "", "App", None, (char **)NULL, 0, &sizehints);
+ XChangeProperty(x_dpy, win, net_wm_name, utf8_string, 8, PropModeReplace, title, title_len);
+
+ return win;
+}
+
+uintptr_t
+doShowWindow(uintptr_t id) {
+ Window win = (Window)(id);
+ XMapWindow(x_dpy, win);
+ EGLSurface surf = eglCreateWindowSurface(e_dpy, e_config, win, NULL);
+ if (!surf) {
+ fprintf(stderr, "eglCreateWindowSurface failed: %s\n", eglGetErrorStr());
+ exit(1);
+ }
+ return (uintptr_t)(surf);
+}
+
+uintptr_t
+surfaceCreate() {
+ static const EGLint ctx_attribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 3,
+ EGL_NONE
+ };
+ EGLContext ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs);
+ if (!ctx) {
+ fprintf(stderr, "surface eglCreateContext failed: %s\n", eglGetErrorStr());
+ return 0;
+ }
+
+ static const EGLint cfg_attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_BLUE_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_RED_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_CONFIG_CAVEAT, EGL_NONE,
+ EGL_NONE
+ };
+ EGLConfig cfg;
+ EGLint num_configs;
+ if (!eglChooseConfig(e_dpy, cfg_attribs, &cfg, 1, &num_configs)) {
+ fprintf(stderr, "gldriver: surface eglChooseConfig failed: %s\n", eglGetErrorStr());
+ return 0;
+ }
+
+ // TODO: use the size of the monitor as a bound for texture size.
+ static const EGLint attribs[] = {
+ EGL_WIDTH, 4096,
+ EGL_HEIGHT, 3072,
+ EGL_NONE
+ };
+ EGLSurface surface = eglCreatePbufferSurface(e_dpy, cfg, attribs);
+ if (!surface) {
+ fprintf(stderr, "gldriver: surface eglCreatePbufferSurface failed: %s\n", eglGetErrorStr());
+ return 0;
+ }
+
+ if (!eglMakeCurrent(e_dpy, surface, surface, ctx)) {
+ fprintf(stderr, "gldriver: surface eglMakeCurrent failed: %s\n", eglGetErrorStr());
+ return 0;
+ }
+
+ return (uintptr_t)surface;
+}
diff --git a/shiny/driver/gldriver/x11.go b/shiny/driver/gldriver/x11.go
new file mode 100644
index 0000000..0ccdd59
--- /dev/null
+++ b/shiny/driver/gldriver/x11.go
@@ -0,0 +1,318 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (linux && !android) || openbsd
+
+package gldriver
+
+/*
+#cgo linux LDFLAGS: -lEGL -lGLESv2 -lX11
+#cgo openbsd LDFLAGS: -L/usr/X11R6/lib/ -lEGL -lGLESv2 -lX11
+
+#cgo openbsd CFLAGS: -I/usr/X11R6/include/
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+char *eglGetErrorStr();
+void startDriver();
+void processEvents();
+void makeCurrent(uintptr_t ctx);
+void swapBuffers(uintptr_t ctx);
+void doCloseWindow(uintptr_t id);
+uintptr_t doNewWindow(int width, int height, char* title, int title_len);
+uintptr_t doShowWindow(uintptr_t id);
+uintptr_t surfaceCreate();
+*/
+import "C"
+import (
+ "errors"
+ "runtime"
+ "time"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/internal/x11key"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+ "golang.org/x/mobile/geom"
+ "golang.org/x/mobile/gl"
+)
+
+const useLifecycler = true
+
+const handleSizeEventsAtChannelReceive = true
+
+var theKeysyms x11key.KeysymTable
+
+func init() {
+ // It might not be necessary, but it probably doesn't hurt to try to make
+ // 'the main thread' be 'the X11 / OpenGL thread'.
+ runtime.LockOSThread()
+}
+
+func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
+ width, height := optsSize(opts)
+
+ title := opts.GetTitle()
+ ctitle := C.CString(title)
+ defer C.free(unsafe.Pointer(ctitle))
+
+ retc := make(chan uintptr)
+ uic <- uiClosure{
+ f: func() uintptr {
+ return uintptr(C.doNewWindow(C.int(width), C.int(height), ctitle, C.int(len(title))))
+ },
+ retc: retc,
+ }
+ return <-retc, nil
+}
+
+func initWindow(w *windowImpl) {
+ w.glctx, w.worker = glctx, worker
+}
+
+func showWindow(w *windowImpl) {
+ retc := make(chan uintptr)
+ uic <- uiClosure{
+ f: func() uintptr {
+ return uintptr(C.doShowWindow(C.uintptr_t(w.id)))
+ },
+ retc: retc,
+ }
+ w.ctx = <-retc
+ go drawLoop(w)
+}
+
+func closeWindow(id uintptr) {
+ uic <- uiClosure{
+ f: func() uintptr {
+ C.doCloseWindow(C.uintptr_t(id))
+ return 0
+ },
+ }
+}
+
+func drawLoop(w *windowImpl) {
+ glcontextc <- w.ctx.(uintptr)
+ go func() {
+ for range w.publish {
+ publishc <- w
+ }
+ }()
+}
+
+var (
+ glcontextc = make(chan uintptr)
+ publishc = make(chan *windowImpl)
+ uic = make(chan uiClosure)
+
+ // TODO: don't assume that there is only one window, and hence only
+ // one (global) GL context.
+ //
+ // TODO: should we be able to make a shiny.Texture before having a
+ // shiny.Window's GL context? Should something like gl.IsProgram be a
+ // method instead of a function, and have each shiny.Window have its own
+ // gl.Context?
+ glctx gl.Context
+ worker gl.Worker
+)
+
+// uiClosure is a closure to be run on C's UI thread.
+type uiClosure struct {
+ f func() uintptr
+ retc chan uintptr
+}
+
+func main(f func(screen.Screen)) error {
+ if gl.Version() == "GL_ES_2_0" {
+ return errors.New("gldriver: ES 3 required on X11")
+ }
+ C.startDriver()
+ glctx, worker = gl.NewContext()
+
+ closec := make(chan struct{})
+ go func() {
+ f(theScreen)
+ close(closec)
+ }()
+
+ // heartbeat is a channel that, at regular intervals, directs the select
+ // below to also consider X11 events, not just Go events (channel
+ // communications).
+ //
+ // TODO: select instead of poll. Note that knowing whether to call
+ // C.processEvents needs to select on a file descriptor, and the other
+ // cases below select on Go channels.
+ heartbeat := time.NewTicker(time.Second / 60)
+ workAvailable := worker.WorkAvailable()
+
+ for {
+ select {
+ case <-closec:
+ return nil
+ case ctx := <-glcontextc:
+ // TODO: do we need to synchronize with seeing a size event for
+ // this window's context before or after calling makeCurrent?
+ // Otherwise, are we racing with the gl.Viewport call? I've
+ // occasionally seen a stale viewport, if the window manager sets
+ // the window width and height to something other than that
+ // requested by XCreateWindow, but it's not easily reproducible.
+ C.makeCurrent(C.uintptr_t(ctx))
+ case w := <-publishc:
+ C.swapBuffers(C.uintptr_t(w.ctx.(uintptr)))
+ w.publishDone <- screen.PublishResult{}
+ case req := <-uic:
+ ret := req.f()
+ if req.retc != nil {
+ req.retc <- ret
+ }
+ case <-heartbeat.C:
+ C.processEvents()
+ case <-workAvailable:
+ worker.DoWork()
+ }
+ }
+}
+
+//export onExpose
+func onExpose(id uintptr) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+
+ w.Send(paint.Event{External: true})
+}
+
+//export onKeysym
+func onKeysym(k, unshifted, shifted uint32) {
+ theKeysyms.Table[k][0] = unshifted
+ theKeysyms.Table[k][1] = shifted
+}
+
+//export onKey
+func onKey(id uintptr, state uint16, detail, dir uint8) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+
+ r, c := theKeysyms.Lookup(detail, state)
+ w.Send(key.Event{
+ Rune: r,
+ Code: c,
+ Modifiers: x11key.KeyModifiers(state),
+ Direction: key.Direction(dir),
+ })
+}
+
+//export onMouse
+func onMouse(id uintptr, x, y int32, state uint16, button, dir uint8) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+
+ // TODO: should a mouse.Event have a separate MouseModifiers field, for
+ // which buttons are pressed during a mouse move?
+ btn := mouse.Button(button)
+ switch btn {
+ case 4:
+ btn = mouse.ButtonWheelUp
+ case 5:
+ btn = mouse.ButtonWheelDown
+ case 6:
+ btn = mouse.ButtonWheelLeft
+ case 7:
+ btn = mouse.ButtonWheelRight
+ }
+ if btn.IsWheel() {
+ if dir != uint8(mouse.DirPress) {
+ return
+ }
+ dir = uint8(mouse.DirStep)
+ }
+ w.Send(mouse.Event{
+ X: float32(x),
+ Y: float32(y),
+ Button: btn,
+ Modifiers: x11key.KeyModifiers(state),
+ Direction: mouse.Direction(dir),
+ })
+}
+
+//export onFocus
+func onFocus(id uintptr, focused bool) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+
+ w.lifecycler.SetFocused(focused)
+ w.lifecycler.SendEvent(w, w.glctx)
+}
+
+//export onConfigure
+func onConfigure(id uintptr, x, y, width, height, displayWidth, displayWidthMM int32) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+
+ w.lifecycler.SetVisible(x+width > 0 && y+height > 0)
+ w.lifecycler.SendEvent(w, w.glctx)
+
+ const (
+ mmPerInch = 25.4
+ ptPerInch = 72
+ )
+ pixelsPerMM := float32(displayWidth) / float32(displayWidthMM)
+ w.Send(size.Event{
+ WidthPx: int(width),
+ HeightPx: int(height),
+ WidthPt: geom.Pt(width),
+ HeightPt: geom.Pt(height),
+ PixelsPerPt: pixelsPerMM * mmPerInch / ptPerInch,
+ })
+}
+
+//export onDeleteWindow
+func onDeleteWindow(id uintptr) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[id]
+ theScreen.mu.Unlock()
+
+ if w == nil {
+ return
+ }
+
+ w.lifecycler.SetDead(true)
+ w.lifecycler.SendEvent(w, w.glctx)
+}
+
+func surfaceCreate() error {
+ if C.surfaceCreate() == 0 {
+ return errors.New("gldriver: surface creation failed")
+ }
+ return nil
+}
diff --git a/shiny/driver/internal/drawer/drawer.go b/shiny/driver/internal/drawer/drawer.go
new file mode 100644
index 0000000..230a174
--- /dev/null
+++ b/shiny/driver/internal/drawer/drawer.go
@@ -0,0 +1,34 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package drawer provides functions that help implement screen.Drawer methods.
+package drawer // import "golang.org/x/exp/shiny/driver/internal/drawer"
+
+import (
+ "image"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+)
+
+// Copy implements the Copy method of the screen.Drawer interface by calling
+// the Draw method of that same interface.
+func Copy(dst screen.Drawer, dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ dst.Draw(f64.Aff3{
+ 1, 0, float64(dp.X - sr.Min.X),
+ 0, 1, float64(dp.Y - sr.Min.Y),
+ }, src, sr, op, opts)
+}
+
+// Scale implements the Scale method of the screen.Drawer interface by calling
+// the Draw method of that same interface.
+func Scale(dst screen.Drawer, dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ rx := float64(dr.Dx()) / float64(sr.Dx())
+ ry := float64(dr.Dy()) / float64(sr.Dy())
+ dst.Draw(f64.Aff3{
+ rx, 0, float64(dr.Min.X) - rx*float64(sr.Min.X),
+ 0, ry, float64(dr.Min.Y) - ry*float64(sr.Min.Y),
+ }, src, sr, op, opts)
+}
diff --git a/shiny/driver/internal/errscreen/errscreen.go b/shiny/driver/internal/errscreen/errscreen.go
new file mode 100644
index 0000000..97f9773
--- /dev/null
+++ b/shiny/driver/internal/errscreen/errscreen.go
@@ -0,0 +1,25 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package errscreen provides a stub Screen implementation.
+package errscreen // import "golang.org/x/exp/shiny/driver/internal/errscreen"
+
+import (
+ "image"
+
+ "golang.org/x/exp/shiny/screen"
+)
+
+// Stub returns a Screen whose methods all return the given error.
+func Stub(err error) screen.Screen {
+ return stub{err}
+}
+
+type stub struct {
+ err error
+}
+
+func (s stub) NewBuffer(size image.Point) (screen.Buffer, error) { return nil, s.err }
+func (s stub) NewTexture(size image.Point) (screen.Texture, error) { return nil, s.err }
+func (s stub) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) { return nil, s.err }
diff --git a/shiny/driver/internal/event/event.go b/shiny/driver/internal/event/event.go
new file mode 100644
index 0000000..686a123
--- /dev/null
+++ b/shiny/driver/internal/event/event.go
@@ -0,0 +1,68 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package event provides an infinitely buffered double-ended queue of events.
+package event // import "golang.org/x/exp/shiny/driver/internal/event"
+
+import (
+ "sync"
+)
+
+// Deque is an infinitely buffered double-ended queue of events. The zero value
+// is usable, but a Deque value must not be copied.
+type Deque struct {
+ mu sync.Mutex
+ cond sync.Cond // cond.L is lazily initialized to &Deque.mu.
+ back []interface{} // FIFO.
+ front []interface{} // LIFO.
+}
+
+func (q *Deque) lockAndInit() {
+ q.mu.Lock()
+ if q.cond.L == nil {
+ q.cond.L = &q.mu
+ }
+}
+
+// NextEvent implements the screen.EventDeque interface.
+func (q *Deque) NextEvent() interface{} {
+ q.lockAndInit()
+ defer q.mu.Unlock()
+
+ for {
+ if n := len(q.front); n > 0 {
+ e := q.front[n-1]
+ q.front[n-1] = nil
+ q.front = q.front[:n-1]
+ return e
+ }
+
+ if n := len(q.back); n > 0 {
+ e := q.back[0]
+ q.back[0] = nil
+ q.back = q.back[1:]
+ return e
+ }
+
+ q.cond.Wait()
+ }
+}
+
+// Send implements the screen.EventDeque interface.
+func (q *Deque) Send(event interface{}) {
+ q.lockAndInit()
+ defer q.mu.Unlock()
+
+ q.back = append(q.back, event)
+ q.cond.Signal()
+}
+
+// SendFirst implements the screen.EventDeque interface.
+func (q *Deque) SendFirst(event interface{}) {
+ q.lockAndInit()
+ defer q.mu.Unlock()
+
+ q.front = append(q.front, event)
+ q.cond.Signal()
+}
diff --git a/shiny/driver/internal/lifecycler/lifecycler.go b/shiny/driver/internal/lifecycler/lifecycler.go
new file mode 100644
index 0000000..53d3543
--- /dev/null
+++ b/shiny/driver/internal/lifecycler/lifecycler.go
@@ -0,0 +1,80 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package lifecycler tracks a window's lifecycle state.
+//
+// It eliminates sending redundant lifecycle events, ones where the From and To
+// stages are equal. For example, moving a window from one part of the screen
+// to another should not send multiple events from StageVisible to
+// StageVisible, even though the underlying window system's message might only
+// hold the new position, and not whether the window was previously visible.
+package lifecycler // import "golang.org/x/exp/shiny/driver/internal/lifecycler"
+
+import (
+ "sync"
+
+ "golang.org/x/mobile/event/lifecycle"
+)
+
+// State is a window's lifecycle state.
+type State struct {
+ mu sync.Mutex
+ stage lifecycle.Stage
+ dead bool
+ focused bool
+ visible bool
+}
+
+func (s *State) SetDead(b bool) {
+ s.mu.Lock()
+ s.dead = b
+ s.mu.Unlock()
+}
+
+func (s *State) SetFocused(b bool) {
+ s.mu.Lock()
+ s.focused = b
+ s.mu.Unlock()
+}
+
+func (s *State) SetVisible(b bool) {
+ s.mu.Lock()
+ s.visible = b
+ s.mu.Unlock()
+}
+
+func (s *State) SendEvent(r Sender, drawContext interface{}) {
+ s.mu.Lock()
+ from, to := s.stage, lifecycle.StageAlive
+ // The order of these if's is important. For example, once a window becomes
+ // StageDead, it should never change stage again.
+ //
+ // Similarly, focused trumps visible. It's hard to imagine a situation
+ // where a window is focused and not visible on screen, but in that
+ // unlikely case, StageFocused seems the most appropriate stage.
+ if s.dead {
+ to = lifecycle.StageDead
+ } else if s.focused {
+ to = lifecycle.StageFocused
+ } else if s.visible {
+ to = lifecycle.StageVisible
+ }
+ s.stage = to
+ s.mu.Unlock()
+
+ if from != to {
+ r.Send(lifecycle.Event{
+ From: from,
+ To: to,
+
+ // TODO: does shiny use this at all?
+ DrawContext: drawContext,
+ })
+ }
+}
+
+// Sender is who to send the lifecycle event to.
+type Sender interface {
+ Send(event interface{})
+}
diff --git a/shiny/driver/internal/swizzle/swizzle_amd64.go b/shiny/driver/internal/swizzle/swizzle_amd64.go
new file mode 100644
index 0000000..55693ff
--- /dev/null
+++ b/shiny/driver/internal/swizzle/swizzle_amd64.go
@@ -0,0 +1,17 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package swizzle
+
+// haveSSSE3 returns whether the CPU supports SSSE3 instructions (i.e. PSHUFB).
+//
+// Note that this is SSSE3, not SSE3.
+func haveSSSE3() bool
+
+var useBGRA16 = haveSSSE3()
+
+const useBGRA4 = true
+
+func bgra16(p []byte)
+func bgra4(p []byte)
diff --git a/shiny/driver/internal/swizzle/swizzle_amd64.s b/shiny/driver/internal/swizzle/swizzle_amd64.s
new file mode 100644
index 0000000..1c65280
--- /dev/null
+++ b/shiny/driver/internal/swizzle/swizzle_amd64.s
@@ -0,0 +1,76 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "textflag.h"
+
+// func haveSSSE3() bool
+TEXT Β·haveSSSE3(SB),NOSPLIT,$0
+ MOVQ $1, AX
+ CPUID
+ SHRQ $9, CX
+ ANDQ $1, CX
+ MOVB CX, ret+0(FP)
+ RET
+
+// func bgra16(p []byte)
+TEXT Β·bgra16(SB),NOSPLIT,$0-24
+ MOVQ p+0(FP), SI
+ MOVQ len+8(FP), DI
+
+ // Sanity check that len is a multiple of 16.
+ MOVQ DI, AX
+ ANDQ $15, AX
+ JNZ done
+
+ // Make the shuffle control mask (16-byte register X0) look like this,
+ // where the low order byte comes first:
+ //
+ // 02 01 00 03 06 05 04 07 0a 09 08 0b 0e 0d 0c 0f
+ //
+ // Load the bottom 8 bytes into X0, the top into X1, then interleave them
+ // into X0.
+ MOVQ $0x0704050603000102, AX
+ MOVQ AX, X0
+ MOVQ $0x0f0c0d0e0b08090a, AX
+ MOVQ AX, X1
+ PUNPCKLQDQ X1, X0
+
+ ADDQ SI, DI
+loop:
+ CMPQ SI, DI
+ JEQ done
+
+ MOVOU (SI), X1
+ PSHUFB X0, X1
+ MOVOU X1, (SI)
+
+ ADDQ $16, SI
+ JMP loop
+done:
+ RET
+
+// func bgra4(p []byte)
+TEXT Β·bgra4(SB),NOSPLIT,$0-24
+ MOVQ p+0(FP), SI
+ MOVQ len+8(FP), DI
+
+ // Sanity check that len is a multiple of 4.
+ MOVQ DI, AX
+ ANDQ $3, AX
+ JNZ done
+
+ ADDQ SI, DI
+loop:
+ CMPQ SI, DI
+ JEQ done
+
+ MOVB 0(SI), AX
+ MOVB 2(SI), BX
+ MOVB BX, 0(SI)
+ MOVB AX, 2(SI)
+
+ ADDQ $4, SI
+ JMP loop
+done:
+ RET
diff --git a/shiny/driver/internal/swizzle/swizzle_common.go b/shiny/driver/internal/swizzle/swizzle_common.go
new file mode 100644
index 0000000..07aebba
--- /dev/null
+++ b/shiny/driver/internal/swizzle/swizzle_common.go
@@ -0,0 +1,31 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package swizzle provides functions for converting between RGBA pixel
+// formats.
+package swizzle // import "golang.org/x/exp/shiny/driver/internal/swizzle"
+
+// BGRA converts a pixel buffer between Go's RGBA and other systems' BGRA byte
+// orders.
+//
+// It panics if the input slice length is not a multiple of 4.
+func BGRA(p []byte) {
+ if len(p)%4 != 0 {
+ panic("input slice length is not a multiple of 4")
+ }
+
+ // Use asm code for 16- or 4-byte chunks, if supported.
+ if useBGRA16 {
+ n := len(p) &^ (16 - 1)
+ bgra16(p[:n])
+ p = p[n:]
+ } else if useBGRA4 {
+ bgra4(p)
+ return
+ }
+
+ for i := 0; i < len(p); i += 4 {
+ p[i+0], p[i+2] = p[i+2], p[i+0]
+ }
+}
diff --git a/shiny/driver/internal/swizzle/swizzle_other.go b/shiny/driver/internal/swizzle/swizzle_other.go
new file mode 100644
index 0000000..4bf7751
--- /dev/null
+++ b/shiny/driver/internal/swizzle/swizzle_other.go
@@ -0,0 +1,15 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !amd64
+
+package swizzle
+
+const (
+ useBGRA16 = false
+ useBGRA4 = false
+)
+
+func bgra16(p []byte) { panic("unreachable") }
+func bgra4(p []byte) { panic("unreachable") }
diff --git a/shiny/driver/internal/swizzle/swizzle_test.go b/shiny/driver/internal/swizzle/swizzle_test.go
new file mode 100644
index 0000000..83a6d0c
--- /dev/null
+++ b/shiny/driver/internal/swizzle/swizzle_test.go
@@ -0,0 +1,86 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package swizzle
+
+import (
+ "bytes"
+ "math/rand"
+ "testing"
+)
+
+func TestBGRAShortInput(t *testing.T) {
+ const s = "012.456.89A.CDE.GHI.KLM.O"
+ testCases := []string{
+ 0: "012.456.89A.CDE.GHI.KLM.O",
+ 1: "210.456.89A.CDE.GHI.KLM.O",
+ 2: "210.654.89A.CDE.GHI.KLM.O",
+ 3: "210.654.A98.CDE.GHI.KLM.O",
+ 4: "210.654.A98.EDC.GHI.KLM.O",
+ 5: "210.654.A98.EDC.IHG.KLM.O",
+ 6: "210.654.A98.EDC.IHG.MLK.O",
+ }
+ for i, want := range testCases {
+ b := []byte(s)
+ BGRA(b[:4*i])
+ got := string(b)
+ if got != want {
+ t.Errorf("i=%d: got %q, want %q", i, got, want)
+ }
+ changed := got != s
+ wantChanged := i != 0
+ if changed != wantChanged {
+ t.Errorf("i=%d: changed=%t, want %t", i, changed, wantChanged)
+ }
+ }
+}
+
+func TestBGRARandomInput(t *testing.T) {
+ r := rand.New(rand.NewSource(1))
+ fastBuf := make([]byte, 1024)
+ slowBuf := make([]byte, 1024)
+ for i := range fastBuf {
+ fastBuf[i] = uint8(r.Intn(256))
+ }
+ copy(slowBuf, fastBuf)
+
+ for i := 0; i < 100000; i++ {
+ o := r.Intn(len(fastBuf))
+ n := r.Intn(len(fastBuf)-o) &^ 0x03
+ BGRA(fastBuf[o : o+n])
+ pureGoBGRA(slowBuf[o : o+n])
+ if bytes.Equal(fastBuf, slowBuf) {
+ continue
+ }
+ for j := range fastBuf {
+ x := fastBuf[j]
+ y := slowBuf[j]
+ if x != y {
+ t.Fatalf("iter %d: swizzling [%d:%d+%d]: bytes differ at offset %d (aka %d+%d): %#02x vs %#02x",
+ i, o, o, n, j, o, j-o, x, y)
+ }
+ }
+ }
+}
+
+func pureGoBGRA(p []byte) {
+ if len(p)%4 != 0 {
+ return
+ }
+ for i := 0; i < len(p); i += 4 {
+ p[i+0], p[i+2] = p[i+2], p[i+0]
+ }
+}
+
+func benchmarkBGRA(b *testing.B, f func([]byte)) {
+ const w, h = 1920, 1080 // 1080p RGBA.
+ buf := make([]byte, 4*w*h)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ f(buf)
+ }
+}
+
+func BenchmarkBGRA(b *testing.B) { benchmarkBGRA(b, BGRA) }
+func BenchmarkPureGoBGRA(b *testing.B) { benchmarkBGRA(b, pureGoBGRA) }
diff --git a/shiny/driver/internal/win32/key.go b/shiny/driver/internal/win32/key.go
new file mode 100644
index 0000000..da75d9a
--- /dev/null
+++ b/shiny/driver/internal/win32/key.go
@@ -0,0 +1,350 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package win32
+
+import (
+ "fmt"
+ "syscall"
+ "unicode/utf16"
+
+ "golang.org/x/mobile/event/key"
+)
+
+// convVirtualKeyCode converts a Win32 virtual key code number
+// into the standard keycodes used by the key package.
+func convVirtualKeyCode(vKey uint32) key.Code {
+ switch vKey {
+ case 0x01: // VK_LBUTTON left mouse button
+ case 0x02: // VK_RBUTTON right mouse button
+ case 0x03: // VK_CANCEL control-break processing
+ case 0x04: // VK_MBUTTON middle mouse button
+ case 0x05: // VK_XBUTTON1 X1 mouse button
+ case 0x06: // VK_XBUTTON2 X2 mouse button
+ case 0x08: // VK_BACK
+ return key.CodeDeleteBackspace
+ case 0x09: // VK_TAB
+ return key.CodeTab
+ case 0x0C: // VK_CLEAR
+ case 0x0D: // VK_RETURN
+ return key.CodeReturnEnter
+ case 0x10: // VK_SHIFT
+ return key.CodeLeftShift
+ case 0x11: // VK_CONTROL
+ return key.CodeLeftControl
+ case 0x12: // VK_MENU
+ return key.CodeLeftAlt
+ case 0x13: // VK_PAUSE
+ case 0x14: // VK_CAPITAL
+ return key.CodeCapsLock
+ case 0x15: // VK_KANA, VK_HANGUEL, VK_HANGUL
+ case 0x17: // VK_JUNJA
+ case 0x18: // VK_FINA, L
+ case 0x19: // VK_HANJA, VK_KANJI
+ case 0x1B: // VK_ESCAPE
+ return key.CodeEscape
+ case 0x1C: // VK_CONVERT
+ case 0x1D: // VK_NONCONVERT
+ case 0x1E: // VK_ACCEPT
+ case 0x1F: // VK_MODECHANGE
+ case 0x20: // VK_SPACE
+ return key.CodeSpacebar
+ case 0x21: // VK_PRIOR
+ return key.CodePageUp
+ case 0x22: // VK_NEXT
+ return key.CodePageDown
+ case 0x23: // VK_END
+ return key.CodeEnd
+ case 0x24: // VK_HOME
+ return key.CodeHome
+ case 0x25: // VK_LEFT
+ return key.CodeLeftArrow
+ case 0x26: // VK_UP
+ return key.CodeUpArrow
+ case 0x27: // VK_RIGHT
+ return key.CodeRightArrow
+ case 0x28: // VK_DOWN
+ return key.CodeDownArrow
+ case 0x29: // VK_SELECT
+ case 0x2A: // VK_PRINT
+ case 0x2B: // VK_EXECUTE
+ case 0x2C: // VK_SNAPSHOT
+ case 0x2D: // VK_INSERT
+ case 0x2E: // VK_DELETE
+ return key.CodeDeleteForward
+ case 0x2F: // VK_HELP
+ return key.CodeHelp
+ case 0x30:
+ return key.Code0
+ case 0x31:
+ return key.Code1
+ case 0x32:
+ return key.Code2
+ case 0x33:
+ return key.Code3
+ case 0x34:
+ return key.Code4
+ case 0x35:
+ return key.Code5
+ case 0x36:
+ return key.Code6
+ case 0x37:
+ return key.Code7
+ case 0x38:
+ return key.Code8
+ case 0x39:
+ return key.Code9
+ case 0x41:
+ return key.CodeA
+ case 0x42:
+ return key.CodeB
+ case 0x43:
+ return key.CodeC
+ case 0x44:
+ return key.CodeD
+ case 0x45:
+ return key.CodeE
+ case 0x46:
+ return key.CodeF
+ case 0x47:
+ return key.CodeG
+ case 0x48:
+ return key.CodeH
+ case 0x49:
+ return key.CodeI
+ case 0x4A:
+ return key.CodeJ
+ case 0x4B:
+ return key.CodeK
+ case 0x4C:
+ return key.CodeL
+ case 0x4D:
+ return key.CodeM
+ case 0x4E:
+ return key.CodeN
+ case 0x4F:
+ return key.CodeO
+ case 0x50:
+ return key.CodeP
+ case 0x51:
+ return key.CodeQ
+ case 0x52:
+ return key.CodeR
+ case 0x53:
+ return key.CodeS
+ case 0x54:
+ return key.CodeT
+ case 0x55:
+ return key.CodeU
+ case 0x56:
+ return key.CodeV
+ case 0x57:
+ return key.CodeW
+ case 0x58:
+ return key.CodeX
+ case 0x59:
+ return key.CodeY
+ case 0x5A:
+ return key.CodeZ
+ case 0x5B: // VK_LWIN
+ return key.CodeLeftGUI
+ case 0x5C: // VK_RWIN
+ return key.CodeRightGUI
+ case 0x5D: // VK_APPS
+ case 0x5F: // VK_SLEEP
+ case 0x60: // VK_NUMPAD0
+ return key.CodeKeypad0
+ case 0x61: // VK_NUMPAD1
+ return key.CodeKeypad1
+ case 0x62: // VK_NUMPAD2
+ return key.CodeKeypad2
+ case 0x63: // VK_NUMPAD3
+ return key.CodeKeypad3
+ case 0x64: // VK_NUMPAD4
+ return key.CodeKeypad4
+ case 0x65: // VK_NUMPAD5
+ return key.CodeKeypad5
+ case 0x66: // VK_NUMPAD6
+ return key.CodeKeypad6
+ case 0x67: // VK_NUMPAD7
+ return key.CodeKeypad7
+ case 0x68: // VK_NUMPAD8
+ return key.CodeKeypad8
+ case 0x69: // VK_NUMPAD9
+ return key.CodeKeypad9
+ case 0x6A: // VK_MULTIPLY
+ return key.CodeKeypadAsterisk
+ case 0x6B: // VK_ADD
+ return key.CodeKeypadPlusSign
+ case 0x6C: // VK_SEPARATOR
+ case 0x6D: // VK_SUBTRACT
+ return key.CodeKeypadHyphenMinus
+ case 0x6E: // VK_DECIMAL
+ return key.CodeFullStop
+ case 0x6F: // VK_DIVIDE
+ return key.CodeKeypadSlash
+ case 0x70: // VK_F1
+ return key.CodeF1
+ case 0x71: // VK_F2
+ return key.CodeF2
+ case 0x72: // VK_F3
+ return key.CodeF3
+ case 0x73: // VK_F4
+ return key.CodeF4
+ case 0x74: // VK_F5
+ return key.CodeF5
+ case 0x75: // VK_F6
+ return key.CodeF6
+ case 0x76: // VK_F7
+ return key.CodeF7
+ case 0x77: // VK_F8
+ return key.CodeF8
+ case 0x78: // VK_F9
+ return key.CodeF9
+ case 0x79: // VK_F10
+ return key.CodeF10
+ case 0x7A: // VK_F11
+ return key.CodeF11
+ case 0x7B: // VK_F12
+ return key.CodeF12
+ case 0x7C: // VK_F13
+ return key.CodeF13
+ case 0x7D: // VK_F14
+ return key.CodeF14
+ case 0x7E: // VK_F15
+ return key.CodeF15
+ case 0x7F: // VK_F16
+ return key.CodeF16
+ case 0x80: // VK_F17
+ return key.CodeF17
+ case 0x81: // VK_F18
+ return key.CodeF18
+ case 0x82: // VK_F19
+ return key.CodeF19
+ case 0x83: // VK_F20
+ return key.CodeF20
+ case 0x84: // VK_F21
+ return key.CodeF21
+ case 0x85: // VK_F22
+ return key.CodeF22
+ case 0x86: // VK_F23
+ return key.CodeF23
+ case 0x87: // VK_F24
+ return key.CodeF24
+ case 0x90: // VK_NUMLOCK
+ return key.CodeKeypadNumLock
+ case 0x91: // VK_SCROLL
+ case 0xA0: // VK_LSHIFT
+ return key.CodeLeftShift
+ case 0xA1: // VK_RSHIFT
+ return key.CodeRightShift
+ case 0xA2: // VK_LCONTROL
+ return key.CodeLeftControl
+ case 0xA3: // VK_RCONTROL
+ return key.CodeRightControl
+ case 0xA4: // VK_LMENU
+ case 0xA5: // VK_RMENU
+ case 0xA6: // VK_BROWSER_BACK
+ case 0xA7: // VK_BROWSER_FORWARD
+ case 0xA8: // VK_BROWSER_REFRESH
+ case 0xA9: // VK_BROWSER_STOP
+ case 0xAA: // VK_BROWSER_SEARCH
+ case 0xAB: // VK_BROWSER_FAVORITES
+ case 0xAC: // VK_BROWSER_HOME
+ case 0xAD: // VK_VOLUME_MUTE
+ return key.CodeMute
+ case 0xAE: // VK_VOLUME_DOWN
+ return key.CodeVolumeDown
+ case 0xAF: // VK_VOLUME_UP
+ return key.CodeVolumeUp
+ case 0xB0: // VK_MEDIA_NEXT_TRACK
+ case 0xB1: // VK_MEDIA_PREV_TRACK
+ case 0xB2: // VK_MEDIA_STOP
+ case 0xB3: // VK_MEDIA_PLAY_PAUSE
+ case 0xB4: // VK_LAUNCH_MAIL
+ case 0xB5: // VK_LAUNCH_MEDIA_SELECT
+ case 0xB6: // VK_LAUNCH_APP1
+ case 0xB7: // VK_LAUNCH_APP2
+ case 0xBA: // VK_OEM_1 ';:'
+ return key.CodeSemicolon
+ case 0xBB: // VK_OEM_PLUS '+'
+ return key.CodeEqualSign
+ case 0xBC: // VK_OEM_COMMA ','
+ return key.CodeComma
+ case 0xBD: // VK_OEM_MINUS '-'
+ return key.CodeHyphenMinus
+ case 0xBE: // VK_OEM_PERIOD '.'
+ return key.CodeFullStop
+ case 0xBF: // VK_OEM_2 '/?'
+ return key.CodeSlash
+ case 0xC0: // VK_OEM_3 '`~'
+ return key.CodeGraveAccent
+ case 0xDB: // VK_OEM_4 '[{'
+ return key.CodeLeftSquareBracket
+ case 0xDC: // VK_OEM_5 '\|'
+ return key.CodeBackslash
+ case 0xDD: // VK_OEM_6 ']}'
+ return key.CodeRightSquareBracket
+ case 0xDE: // VK_OEM_7 'single-quote/double-quote'
+ return key.CodeApostrophe
+ case 0xDF: // VK_OEM_8
+ return key.CodeUnknown
+ case 0xE2: // VK_OEM_102
+ case 0xE5: // VK_PROCESSKEY
+ case 0xE7: // VK_PACKET
+ case 0xF6: // VK_ATTN
+ case 0xF7: // VK_CRSEL
+ case 0xF8: // VK_EXSEL
+ case 0xF9: // VK_EREOF
+ case 0xFA: // VK_PLAY
+ case 0xFB: // VK_ZOOM
+ case 0xFC: // VK_NONAME
+ case 0xFD: // VK_PA1
+ case 0xFE: // VK_OEM_CLEAR
+ }
+ return key.CodeUnknown
+}
+
+func readRune(vKey uint32, scanCode uint8) rune {
+ var (
+ keystate [256]byte
+ buf [4]uint16
+ )
+ if err := _GetKeyboardState(&keystate[0]); err != nil {
+ panic(fmt.Sprintf("win32: %v", err))
+ }
+ // TODO: cache GetKeyboardLayout result, update on WM_INPUTLANGCHANGE
+ layout := _GetKeyboardLayout(0)
+ ret := _ToUnicodeEx(vKey, uint32(scanCode), &keystate[0], &buf[0], int32(len(buf)), 0, layout)
+ if ret < 1 {
+ return -1
+ }
+ return utf16.Decode(buf[:ret])[0]
+}
+
+func sendKeyEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ e := key.Event{
+ Rune: readRune(uint32(wParam), uint8(lParam>>16)),
+ Code: convVirtualKeyCode(uint32(wParam)),
+ Modifiers: keyModifiers(),
+ }
+ switch uMsg {
+ case _WM_KEYDOWN, _WM_SYSKEYDOWN:
+ const prevMask = 1 << 30
+ if repeat := lParam&prevMask == prevMask; repeat {
+ e.Direction = key.DirNone
+ } else {
+ e.Direction = key.DirPress
+ }
+ case _WM_KEYUP, _WM_SYSKEYUP:
+ e.Direction = key.DirRelease
+ default:
+ panic(fmt.Sprintf("win32: unexpected key message: %d", uMsg))
+ }
+
+ KeyEvent(hwnd, e)
+ return 0
+}
diff --git a/shiny/driver/internal/win32/syscall.go b/shiny/driver/internal/win32/syscall.go
new file mode 100644
index 0000000..43366e9
--- /dev/null
+++ b/shiny/driver/internal/win32/syscall.go
@@ -0,0 +1,7 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
+
+package win32
diff --git a/shiny/driver/internal/win32/syscall_windows.go b/shiny/driver/internal/win32/syscall_windows.go
new file mode 100644
index 0000000..d71536d
--- /dev/null
+++ b/shiny/driver/internal/win32/syscall_windows.go
@@ -0,0 +1,186 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package win32
+
+import (
+ "syscall"
+)
+
+type _COLORREF uint32
+
+func _RGB(r, g, b byte) _COLORREF {
+ return _COLORREF(r) | _COLORREF(g)<<8 | _COLORREF(b)<<16
+}
+
+type _POINT struct {
+ X int32
+ Y int32
+}
+
+type _RECT struct {
+ Left int32
+ Top int32
+ Right int32
+ Bottom int32
+}
+
+type _MSG struct {
+ HWND syscall.Handle
+ Message uint32
+ Wparam uintptr
+ Lparam uintptr
+ Time uint32
+ Pt _POINT
+}
+
+type _WNDCLASS struct {
+ Style uint32
+ LpfnWndProc uintptr
+ CbClsExtra int32
+ CbWndExtra int32
+ HInstance syscall.Handle
+ HIcon syscall.Handle
+ HCursor syscall.Handle
+ HbrBackground syscall.Handle
+ LpszMenuName *uint16
+ LpszClassName *uint16
+}
+
+type _WINDOWPOS struct {
+ HWND syscall.Handle
+ HWNDInsertAfter syscall.Handle
+ X int32
+ Y int32
+ Cx int32
+ Cy int32
+ Flags uint32
+}
+
+const (
+ _WM_SETFOCUS = 7
+ _WM_KILLFOCUS = 8
+ _WM_PAINT = 15
+ _WM_CLOSE = 16
+ _WM_WINDOWPOSCHANGED = 71
+ _WM_KEYDOWN = 256
+ _WM_KEYUP = 257
+ _WM_SYSKEYDOWN = 260
+ _WM_SYSKEYUP = 261
+ _WM_MOUSEMOVE = 512
+ _WM_MOUSEWHEEL = 522
+ _WM_LBUTTONDOWN = 513
+ _WM_LBUTTONUP = 514
+ _WM_RBUTTONDOWN = 516
+ _WM_RBUTTONUP = 517
+ _WM_MBUTTONDOWN = 519
+ _WM_MBUTTONUP = 520
+ _WM_USER = 0x0400
+)
+
+const (
+ _WS_OVERLAPPED = 0x00000000
+ _WS_CAPTION = 0x00C00000
+ _WS_SYSMENU = 0x00080000
+ _WS_THICKFRAME = 0x00040000
+ _WS_MINIMIZEBOX = 0x00020000
+ _WS_MAXIMIZEBOX = 0x00010000
+ _WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME | _WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
+)
+
+const (
+ _VK_SHIFT = 16
+ _VK_CONTROL = 17
+ _VK_MENU = 18
+ _VK_LWIN = 0x5B
+ _VK_RWIN = 0x5C
+)
+
+const (
+ _MK_LBUTTON = 0x0001
+ _MK_MBUTTON = 0x0010
+ _MK_RBUTTON = 0x0002
+)
+
+const (
+ _COLOR_BTNFACE = 15
+)
+
+const (
+ _IDI_APPLICATION = 32512
+ _IDC_ARROW = 32512
+)
+
+const (
+ _CW_USEDEFAULT = 0x80000000 - 0x100000000
+
+ _SW_SHOWDEFAULT = 10
+
+ _HWND_MESSAGE = syscall.Handle(^uintptr(2)) // -3
+
+ _SWP_NOSIZE = 0x0001
+)
+
+const (
+ _BI_RGB = 0
+ _DIB_RGB_COLORS = 0
+
+ _AC_SRC_OVER = 0x00
+ _AC_SRC_ALPHA = 0x01
+
+ _SRCCOPY = 0x00cc0020
+
+ _WHEEL_DELTA = 120
+)
+
+func _GET_X_LPARAM(lp uintptr) int32 {
+ return int32(_LOWORD(lp))
+}
+
+func _GET_Y_LPARAM(lp uintptr) int32 {
+ return int32(_HIWORD(lp))
+}
+
+func _GET_WHEEL_DELTA_WPARAM(lp uintptr) int16 {
+ return int16(_HIWORD(lp))
+}
+
+func _LOWORD(l uintptr) uint16 {
+ return uint16(uint32(l))
+}
+
+func _HIWORD(l uintptr) uint16 {
+ return uint16(uint32(l >> 16))
+}
+
+// notes to self
+// UINT = uint32
+// callbacks = uintptr
+// strings = *uint16
+
+//sys GetDC(hwnd syscall.Handle) (dc syscall.Handle, err error) = user32.GetDC
+//sys ReleaseDC(hwnd syscall.Handle, dc syscall.Handle) (err error) = user32.ReleaseDC
+//sys sendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.SendMessageW
+
+//sys _CreateWindowEx(exstyle uint32, className *uint16, windowText *uint16, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) = user32.CreateWindowExW
+//sys _DefWindowProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.DefWindowProcW
+//sys _DestroyWindow(hwnd syscall.Handle) (err error) = user32.DestroyWindow
+//sys _DispatchMessage(msg *_MSG) (ret int32) = user32.DispatchMessageW
+//sys _GetClientRect(hwnd syscall.Handle, rect *_RECT) (err error) = user32.GetClientRect
+//sys _GetWindowRect(hwnd syscall.Handle, rect *_RECT) (err error) = user32.GetWindowRect
+//sys _GetKeyboardLayout(threadID uint32) (locale syscall.Handle) = user32.GetKeyboardLayout
+//sys _GetKeyboardState(lpKeyState *byte) (err error) = user32.GetKeyboardState
+//sys _GetKeyState(virtkey int32) (keystatus int16) = user32.GetKeyState
+//sys _GetMessage(msg *_MSG, hwnd syscall.Handle, msgfiltermin uint32, msgfiltermax uint32) (ret int32, err error) [failretval==-1] = user32.GetMessageW
+//sys _LoadCursor(hInstance syscall.Handle, cursorName uintptr) (cursor syscall.Handle, err error) = user32.LoadCursorW
+//sys _LoadIcon(hInstance syscall.Handle, iconName uintptr) (icon syscall.Handle, err error) = user32.LoadIconW
+//sys _MoveWindow(hwnd syscall.Handle, x int32, y int32, w int32, h int32, repaint bool) (err error) = user32.MoveWindow
+//sys _PostMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult bool) = user32.PostMessageW
+//sys _PostQuitMessage(exitCode int32) = user32.PostQuitMessage
+//sys _RegisterClass(wc *_WNDCLASS) (atom uint16, err error) = user32.RegisterClassW
+//sys _ShowWindow(hwnd syscall.Handle, cmdshow int32) (wasvisible bool) = user32.ShowWindow
+//sys _ScreenToClient(hwnd syscall.Handle, lpPoint *_POINT) (ok bool) = user32.ScreenToClient
+//sys _ToUnicodeEx(wVirtKey uint32, wScanCode uint32, lpKeyState *byte, pwszBuff *uint16, cchBuff int32, wFlags uint32, dwhkl syscall.Handle) (ret int32) = user32.ToUnicodeEx
+//sys _TranslateMessage(msg *_MSG) (done bool) = user32.TranslateMessage
+//sys _UnregisterClass(lpClassName *uint16, hInstance syscall.Handle) (done bool) = user32.UnregisterClassW
diff --git a/shiny/driver/internal/win32/win32.go b/shiny/driver/internal/win32/win32.go
new file mode 100644
index 0000000..c020ce6
--- /dev/null
+++ b/shiny/driver/internal/win32/win32.go
@@ -0,0 +1,519 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+// Package win32 implements a partial shiny screen driver using the Win32 API.
+// It provides window, lifecycle, key, and mouse management, but no drawing.
+// That is left to windriver (using GDI) or gldriver (using DirectX via ANGLE).
+package win32 // import "golang.org/x/exp/shiny/driver/internal/win32"
+
+import (
+ "fmt"
+ "runtime"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+ "golang.org/x/mobile/geom"
+)
+
+// screenHWND is the handle to the "Screen window".
+// The Screen window encapsulates all screen.Screen operations
+// in an actual Windows window so they all run on the main thread.
+// Since any messages sent to a window will be executed on the
+// main thread, we can safely use the messages below.
+var screenHWND syscall.Handle
+
+const (
+ msgCreateWindow = _WM_USER + iota
+ msgMainCallback
+ msgShow
+ msgQuit
+ msgLast
+)
+
+// userWM is used to generate private (WM_USER and above) window message IDs
+// for use by screenWindowWndProc and windowWndProc.
+type userWM struct {
+ sync.Mutex
+ id uint32
+}
+
+func (m *userWM) next() uint32 {
+ m.Lock()
+ if m.id == 0 {
+ m.id = msgLast
+ }
+ r := m.id
+ m.id++
+ m.Unlock()
+ return r
+}
+
+var currentUserWM userWM
+
+func newWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
+ // TODO(brainman): convert windowClass to *uint16 once (in initWindowClass)
+ wcname, err := syscall.UTF16PtrFromString(windowClass)
+ if err != nil {
+ return 0, err
+ }
+ title, err := syscall.UTF16PtrFromString(opts.GetTitle())
+ if err != nil {
+ return 0, err
+ }
+ hwnd, err := _CreateWindowEx(0,
+ wcname, title,
+ _WS_OVERLAPPEDWINDOW,
+ _CW_USEDEFAULT, _CW_USEDEFAULT,
+ _CW_USEDEFAULT, _CW_USEDEFAULT,
+ 0, 0, hThisInstance, 0)
+ if err != nil {
+ return 0, err
+ }
+ // TODO(andlabs): use proper nCmdShow
+ // TODO(andlabs): call UpdateWindow()
+
+ return hwnd, nil
+}
+
+// ResizeClientRect makes hwnd client rectangle opts.Width by opts.Height in size.
+func ResizeClientRect(hwnd syscall.Handle, opts *screen.NewWindowOptions) error {
+ if opts == nil || opts.Width <= 0 || opts.Height <= 0 {
+ return nil
+ }
+ var cr, wr _RECT
+ err := _GetClientRect(hwnd, &cr)
+ if err != nil {
+ return err
+ }
+ err = _GetWindowRect(hwnd, &wr)
+ if err != nil {
+ return err
+ }
+ w := (wr.Right - wr.Left) - (cr.Right - int32(opts.Width))
+ h := (wr.Bottom - wr.Top) - (cr.Bottom - int32(opts.Height))
+ return _MoveWindow(hwnd, wr.Left, wr.Top, w, h, false)
+}
+
+// Show shows a newly created window.
+// It sends the appropriate lifecycle events, makes the window appear
+// on the screen, and sends an initial size event.
+//
+// This is a separate step from NewWindow to give the driver a chance
+// to setup its internal state for a window before events start being
+// delivered.
+func Show(hwnd syscall.Handle) {
+ SendMessage(hwnd, msgShow, 0, 0)
+}
+
+func Release(hwnd syscall.Handle) {
+ SendMessage(hwnd, _WM_CLOSE, 0, 0)
+}
+
+func sendFocus(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ switch uMsg {
+ case _WM_SETFOCUS:
+ LifecycleEvent(hwnd, lifecycle.StageFocused)
+ case _WM_KILLFOCUS:
+ LifecycleEvent(hwnd, lifecycle.StageVisible)
+ default:
+ panic(fmt.Sprintf("unexpected focus message: %d", uMsg))
+ }
+ return _DefWindowProc(hwnd, uMsg, wParam, lParam)
+}
+
+func sendShow(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ LifecycleEvent(hwnd, lifecycle.StageVisible)
+ _ShowWindow(hwnd, _SW_SHOWDEFAULT)
+ sendSize(hwnd)
+ return 0
+}
+
+func sendSizeEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ wp := (*_WINDOWPOS)(unsafe.Pointer(lParam))
+ if wp.Flags&_SWP_NOSIZE != 0 {
+ return 0
+ }
+ sendSize(hwnd)
+ return 0
+}
+
+func sendSize(hwnd syscall.Handle) {
+ var r _RECT
+ if err := _GetClientRect(hwnd, &r); err != nil {
+ panic(err) // TODO(andlabs)
+ }
+
+ width := int(r.Right - r.Left)
+ height := int(r.Bottom - r.Top)
+
+ // TODO(andlabs): don't assume that PixelsPerPt == 1
+ SizeEvent(hwnd, size.Event{
+ WidthPx: width,
+ HeightPx: height,
+ WidthPt: geom.Pt(width),
+ HeightPt: geom.Pt(height),
+ PixelsPerPt: 1,
+ })
+}
+
+func sendClose(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ // TODO(ktye): DefWindowProc calls DestroyWindow by default.
+ // To intercept destruction of the window, return 0 and call
+ // DestroyWindow when appropriate.
+ LifecycleEvent(hwnd, lifecycle.StageDead)
+ return _DefWindowProc(hwnd, uMsg, wParam, lParam)
+}
+
+func sendMouseEvent(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ e := mouse.Event{
+ X: float32(_GET_X_LPARAM(lParam)),
+ Y: float32(_GET_Y_LPARAM(lParam)),
+ Modifiers: keyModifiers(),
+ }
+
+ switch uMsg {
+ case _WM_MOUSEMOVE:
+ e.Direction = mouse.DirNone
+ case _WM_LBUTTONDOWN, _WM_MBUTTONDOWN, _WM_RBUTTONDOWN:
+ e.Direction = mouse.DirPress
+ case _WM_LBUTTONUP, _WM_MBUTTONUP, _WM_RBUTTONUP:
+ e.Direction = mouse.DirRelease
+ case _WM_MOUSEWHEEL:
+ // TODO: On a trackpad, a scroll can be a drawn-out affair with a
+ // distinct beginning and end. Should the intermediate events be
+ // DirNone?
+ e.Direction = mouse.DirStep
+
+ // Convert from screen to window coordinates.
+ p := _POINT{
+ int32(e.X),
+ int32(e.Y),
+ }
+ _ScreenToClient(hwnd, &p)
+ e.X = float32(p.X)
+ e.Y = float32(p.Y)
+ default:
+ panic("sendMouseEvent() called on non-mouse message")
+ }
+
+ switch uMsg {
+ case _WM_MOUSEMOVE:
+ // No-op.
+ case _WM_LBUTTONDOWN, _WM_LBUTTONUP:
+ e.Button = mouse.ButtonLeft
+ case _WM_MBUTTONDOWN, _WM_MBUTTONUP:
+ e.Button = mouse.ButtonMiddle
+ case _WM_RBUTTONDOWN, _WM_RBUTTONUP:
+ e.Button = mouse.ButtonRight
+ case _WM_MOUSEWHEEL:
+ // TODO: handle horizontal scrolling
+ delta := _GET_WHEEL_DELTA_WPARAM(wParam) / _WHEEL_DELTA
+ switch {
+ case delta > 0:
+ e.Button = mouse.ButtonWheelUp
+ case delta < 0:
+ e.Button = mouse.ButtonWheelDown
+ delta = -delta
+ default:
+ return
+ }
+ for delta > 0 {
+ MouseEvent(hwnd, e)
+ delta--
+ }
+ return
+ }
+
+ MouseEvent(hwnd, e)
+
+ return 0
+}
+
+// Precondition: this is called in immediate response to the message that triggered the event (so not after w.Send).
+func keyModifiers() (m key.Modifiers) {
+ down := func(x int32) bool {
+ // GetKeyState gets the key state at the time of the message, so this is what we want.
+ return _GetKeyState(x)&0x80 != 0
+ }
+
+ if down(_VK_CONTROL) {
+ m |= key.ModControl
+ }
+ if down(_VK_MENU) {
+ m |= key.ModAlt
+ }
+ if down(_VK_SHIFT) {
+ m |= key.ModShift
+ }
+ if down(_VK_LWIN) || down(_VK_RWIN) {
+ m |= key.ModMeta
+ }
+ return m
+}
+
+var (
+ MouseEvent func(hwnd syscall.Handle, e mouse.Event)
+ PaintEvent func(hwnd syscall.Handle, e paint.Event)
+ SizeEvent func(hwnd syscall.Handle, e size.Event)
+ KeyEvent func(hwnd syscall.Handle, e key.Event)
+ LifecycleEvent func(hwnd syscall.Handle, e lifecycle.Stage)
+
+ // TODO: use the golang.org/x/exp/shiny/driver/internal/lifecycler package
+ // instead of or together with the LifecycleEvent callback?
+)
+
+func sendPaint(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr) {
+ PaintEvent(hwnd, paint.Event{})
+ return _DefWindowProc(hwnd, uMsg, wParam, lParam)
+}
+
+var screenMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){}
+
+func AddScreenMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
+ uMsg := currentUserWM.next()
+ screenMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
+ fn(hwnd, uMsg, wParam, lParam)
+ return 0
+ }
+ return uMsg
+}
+
+func screenWindowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
+ switch uMsg {
+ case msgCreateWindow:
+ p := (*newWindowParams)(unsafe.Pointer(lParam))
+ p.w, p.err = newWindow(p.opts)
+ case msgMainCallback:
+ go func() {
+ mainCallback()
+ SendScreenMessage(msgQuit, 0, 0)
+ }()
+ case msgQuit:
+ _PostQuitMessage(0)
+ }
+ fn := screenMsgs[uMsg]
+ if fn != nil {
+ return fn(hwnd, uMsg, wParam, lParam)
+ }
+ return _DefWindowProc(hwnd, uMsg, wParam, lParam)
+}
+
+//go:uintptrescapes
+
+func SendScreenMessage(uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
+ return SendMessage(screenHWND, uMsg, wParam, lParam)
+}
+
+var windowMsgs = map[uint32]func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) (lResult uintptr){
+ _WM_SETFOCUS: sendFocus,
+ _WM_KILLFOCUS: sendFocus,
+ _WM_PAINT: sendPaint,
+ msgShow: sendShow,
+ _WM_WINDOWPOSCHANGED: sendSizeEvent,
+ _WM_CLOSE: sendClose,
+
+ _WM_LBUTTONDOWN: sendMouseEvent,
+ _WM_LBUTTONUP: sendMouseEvent,
+ _WM_MBUTTONDOWN: sendMouseEvent,
+ _WM_MBUTTONUP: sendMouseEvent,
+ _WM_RBUTTONDOWN: sendMouseEvent,
+ _WM_RBUTTONUP: sendMouseEvent,
+ _WM_MOUSEMOVE: sendMouseEvent,
+ _WM_MOUSEWHEEL: sendMouseEvent,
+
+ _WM_KEYDOWN: sendKeyEvent,
+ _WM_KEYUP: sendKeyEvent,
+ _WM_SYSKEYDOWN: sendKeyEvent,
+ _WM_SYSKEYUP: sendKeyEvent,
+}
+
+func AddWindowMsg(fn func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr)) uint32 {
+ uMsg := currentUserWM.next()
+ windowMsgs[uMsg] = func(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) uintptr {
+ fn(hwnd, uMsg, wParam, lParam)
+ return 0
+ }
+ return uMsg
+}
+
+func windowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
+ fn := windowMsgs[uMsg]
+ if fn != nil {
+ return fn(hwnd, uMsg, wParam, lParam)
+ }
+ return _DefWindowProc(hwnd, uMsg, wParam, lParam)
+}
+
+type newWindowParams struct {
+ opts *screen.NewWindowOptions
+ w syscall.Handle
+ err error
+}
+
+func NewWindow(opts *screen.NewWindowOptions) (syscall.Handle, error) {
+ var p newWindowParams
+ p.opts = opts
+ SendScreenMessage(msgCreateWindow, 0, uintptr(unsafe.Pointer(&p)))
+ return p.w, p.err
+}
+
+const windowClass = "shiny_Window"
+const screenWindowClass = "shiny_ScreenWindow"
+
+func initWindowClass() (err error) {
+ wcname, err := syscall.UTF16PtrFromString(windowClass)
+ if err != nil {
+ return err
+ }
+ _, err = _RegisterClass(&_WNDCLASS{
+ LpszClassName: wcname,
+ LpfnWndProc: syscall.NewCallback(windowWndProc),
+ HIcon: hDefaultIcon,
+ HCursor: hDefaultCursor,
+ HInstance: hThisInstance,
+ // TODO(andlabs): change this to something else? NULL? the hollow brush?
+ HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
+ })
+ return err
+}
+
+func closeWindowClass() (err error) {
+ wcname, err := syscall.UTF16PtrFromString(windowClass)
+ if err != nil {
+ return err
+ }
+ _UnregisterClass(wcname, hThisInstance)
+
+ return nil
+}
+
+func initScreenWindow() (err error) {
+ swc, err := syscall.UTF16PtrFromString(screenWindowClass)
+ if err != nil {
+ return err
+ }
+ emptyString, err := syscall.UTF16PtrFromString("")
+ if err != nil {
+ return err
+ }
+ wc := _WNDCLASS{
+ LpszClassName: swc,
+ LpfnWndProc: syscall.NewCallback(screenWindowWndProc),
+ HIcon: hDefaultIcon,
+ HCursor: hDefaultCursor,
+ HInstance: hThisInstance,
+ HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
+ }
+ _, err = _RegisterClass(&wc)
+ if err != nil {
+ return err
+ }
+ screenHWND, err = _CreateWindowEx(0,
+ swc, emptyString,
+ _WS_OVERLAPPEDWINDOW,
+ _CW_USEDEFAULT, _CW_USEDEFAULT,
+ _CW_USEDEFAULT, _CW_USEDEFAULT,
+ _HWND_MESSAGE, 0, hThisInstance, 0)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func closeScreenWindow() (err error) {
+ // first destroy window
+ _DestroyWindow(screenHWND)
+
+ // then unregister class
+ swc, err := syscall.UTF16PtrFromString(screenWindowClass)
+ if err != nil {
+ return err
+ }
+ _UnregisterClass(swc, hThisInstance)
+
+ return nil
+}
+
+var (
+ hDefaultIcon syscall.Handle
+ hDefaultCursor syscall.Handle
+ hThisInstance syscall.Handle
+)
+
+func initCommon() (err error) {
+ hDefaultIcon, err = _LoadIcon(0, _IDI_APPLICATION)
+ if err != nil {
+ return err
+ }
+ hDefaultCursor, err = _LoadCursor(0, _IDC_ARROW)
+ if err != nil {
+ return err
+ }
+ // TODO(andlabs) hThisInstance
+ return nil
+}
+
+//go:uintptrescapes
+
+func SendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
+ return sendMessage(hwnd, uMsg, wParam, lParam)
+}
+
+var mainCallback func()
+
+func Main(f func()) (retErr error) {
+ // It does not matter which OS thread we are on.
+ // All that matters is that we confine all UI operations
+ // to the thread that created the respective window.
+ runtime.LockOSThread()
+
+ if err := initCommon(); err != nil {
+ return err
+ }
+
+ if err := initScreenWindow(); err != nil {
+ return err
+ }
+ defer func() {
+ // TODO(andlabs): log an error if this fails?
+ closeScreenWindow()
+ }()
+
+ if err := initWindowClass(); err != nil {
+ return err
+ }
+ defer func() {
+ // TODO(andlabs): log an error if this fails?
+ closeWindowClass()
+ }()
+
+ // Prime the pump.
+ mainCallback = f
+ _PostMessage(screenHWND, msgMainCallback, 0, 0)
+
+ // Main message pump.
+ var m _MSG
+ for {
+ done, err := _GetMessage(&m, 0, 0, 0)
+ if err != nil {
+ return fmt.Errorf("win32 GetMessage failed: %v", err)
+ }
+ if done == 0 { // WM_QUIT
+ break
+ }
+ _TranslateMessage(&m)
+ _DispatchMessage(&m)
+ }
+
+ return nil
+}
diff --git a/shiny/driver/internal/win32/zsyscall_windows.go b/shiny/driver/internal/win32/zsyscall_windows.go
new file mode 100644
index 0000000..26999f6
--- /dev/null
+++ b/shiny/driver/internal/win32/zsyscall_windows.go
@@ -0,0 +1,293 @@
+// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
+
+package win32
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var _ unsafe.Pointer
+
+// Do the interface allocations only once for common
+// Errno values.
+const (
+ errnoERROR_IO_PENDING = 997
+)
+
+var (
+ errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
+)
+
+// errnoErr returns common boxed Errno values, to prevent
+// allocations at runtime.
+func errnoErr(e syscall.Errno) error {
+ switch e {
+ case 0:
+ return nil
+ case errnoERROR_IO_PENDING:
+ return errERROR_IO_PENDING
+ }
+ // TODO: add more here, after collecting data on the common
+ // error values see on Windows. (perhaps when running
+ // all.bat?)
+ return e
+}
+
+var (
+ moduser32 = windows.NewLazySystemDLL("user32.dll")
+
+ procGetDC = moduser32.NewProc("GetDC")
+ procReleaseDC = moduser32.NewProc("ReleaseDC")
+ procSendMessageW = moduser32.NewProc("SendMessageW")
+ procCreateWindowExW = moduser32.NewProc("CreateWindowExW")
+ procDefWindowProcW = moduser32.NewProc("DefWindowProcW")
+ procDestroyWindow = moduser32.NewProc("DestroyWindow")
+ procDispatchMessageW = moduser32.NewProc("DispatchMessageW")
+ procGetClientRect = moduser32.NewProc("GetClientRect")
+ procGetWindowRect = moduser32.NewProc("GetWindowRect")
+ procGetKeyboardLayout = moduser32.NewProc("GetKeyboardLayout")
+ procGetKeyboardState = moduser32.NewProc("GetKeyboardState")
+ procGetKeyState = moduser32.NewProc("GetKeyState")
+ procGetMessageW = moduser32.NewProc("GetMessageW")
+ procLoadCursorW = moduser32.NewProc("LoadCursorW")
+ procLoadIconW = moduser32.NewProc("LoadIconW")
+ procMoveWindow = moduser32.NewProc("MoveWindow")
+ procPostMessageW = moduser32.NewProc("PostMessageW")
+ procPostQuitMessage = moduser32.NewProc("PostQuitMessage")
+ procRegisterClassW = moduser32.NewProc("RegisterClassW")
+ procShowWindow = moduser32.NewProc("ShowWindow")
+ procScreenToClient = moduser32.NewProc("ScreenToClient")
+ procToUnicodeEx = moduser32.NewProc("ToUnicodeEx")
+ procTranslateMessage = moduser32.NewProc("TranslateMessage")
+ procUnregisterClassW = moduser32.NewProc("UnregisterClassW")
+)
+
+func GetDC(hwnd syscall.Handle) (dc syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procGetDC.Addr(), 1, uintptr(hwnd), 0, 0)
+ dc = syscall.Handle(r0)
+ if dc == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func ReleaseDC(hwnd syscall.Handle, dc syscall.Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procReleaseDC.Addr(), 2, uintptr(hwnd), uintptr(dc), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func sendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
+ r0, _, _ := syscall.Syscall6(procSendMessageW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
+ lResult = uintptr(r0)
+ return
+}
+
+func _CreateWindowEx(exstyle uint32, className *uint16, windowText *uint16, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall12(procCreateWindowExW.Addr(), 12, uintptr(exstyle), uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowText)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(hInstance), uintptr(lpParam))
+ hwnd = syscall.Handle(r0)
+ if hwnd == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _DefWindowProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
+ r0, _, _ := syscall.Syscall6(procDefWindowProcW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
+ lResult = uintptr(r0)
+ return
+}
+
+func _DestroyWindow(hwnd syscall.Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procDestroyWindow.Addr(), 1, uintptr(hwnd), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _DispatchMessage(msg *_MSG) (ret int32) {
+ r0, _, _ := syscall.Syscall(procDispatchMessageW.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
+ ret = int32(r0)
+ return
+}
+
+func _GetClientRect(hwnd syscall.Handle, rect *_RECT) (err error) {
+ r1, _, e1 := syscall.Syscall(procGetClientRect.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(rect)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _GetWindowRect(hwnd syscall.Handle, rect *_RECT) (err error) {
+ r1, _, e1 := syscall.Syscall(procGetWindowRect.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(rect)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _GetKeyboardLayout(threadID uint32) (locale syscall.Handle) {
+ r0, _, _ := syscall.Syscall(procGetKeyboardLayout.Addr(), 1, uintptr(threadID), 0, 0)
+ locale = syscall.Handle(r0)
+ return
+}
+
+func _GetKeyboardState(lpKeyState *byte) (err error) {
+ r1, _, e1 := syscall.Syscall(procGetKeyboardState.Addr(), 1, uintptr(unsafe.Pointer(lpKeyState)), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _GetKeyState(virtkey int32) (keystatus int16) {
+ r0, _, _ := syscall.Syscall(procGetKeyState.Addr(), 1, uintptr(virtkey), 0, 0)
+ keystatus = int16(r0)
+ return
+}
+
+func _GetMessage(msg *_MSG, hwnd syscall.Handle, msgfiltermin uint32, msgfiltermax uint32) (ret int32, err error) {
+ r0, _, e1 := syscall.Syscall6(procGetMessageW.Addr(), 4, uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgfiltermin), uintptr(msgfiltermax), 0, 0)
+ ret = int32(r0)
+ if ret == -1 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _LoadCursor(hInstance syscall.Handle, cursorName uintptr) (cursor syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procLoadCursorW.Addr(), 2, uintptr(hInstance), uintptr(cursorName), 0)
+ cursor = syscall.Handle(r0)
+ if cursor == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _LoadIcon(hInstance syscall.Handle, iconName uintptr) (icon syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procLoadIconW.Addr(), 2, uintptr(hInstance), uintptr(iconName), 0)
+ icon = syscall.Handle(r0)
+ if icon == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _MoveWindow(hwnd syscall.Handle, x int32, y int32, w int32, h int32, repaint bool) (err error) {
+ var _p0 uint32
+ if repaint {
+ _p0 = 1
+ } else {
+ _p0 = 0
+ }
+ r1, _, e1 := syscall.Syscall6(procMoveWindow.Addr(), 6, uintptr(hwnd), uintptr(x), uintptr(y), uintptr(w), uintptr(h), uintptr(_p0))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _PostMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult bool) {
+ r0, _, _ := syscall.Syscall6(procPostMessageW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
+ lResult = r0 != 0
+ return
+}
+
+func _PostQuitMessage(exitCode int32) {
+ syscall.Syscall(procPostQuitMessage.Addr(), 1, uintptr(exitCode), 0, 0)
+ return
+}
+
+func _RegisterClass(wc *_WNDCLASS) (atom uint16, err error) {
+ r0, _, e1 := syscall.Syscall(procRegisterClassW.Addr(), 1, uintptr(unsafe.Pointer(wc)), 0, 0)
+ atom = uint16(r0)
+ if atom == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _ShowWindow(hwnd syscall.Handle, cmdshow int32) (wasvisible bool) {
+ r0, _, _ := syscall.Syscall(procShowWindow.Addr(), 2, uintptr(hwnd), uintptr(cmdshow), 0)
+ wasvisible = r0 != 0
+ return
+}
+
+func _ScreenToClient(hwnd syscall.Handle, lpPoint *_POINT) (ok bool) {
+ r0, _, _ := syscall.Syscall(procScreenToClient.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(lpPoint)), 0)
+ ok = r0 != 0
+ return
+}
+
+func _ToUnicodeEx(wVirtKey uint32, wScanCode uint32, lpKeyState *byte, pwszBuff *uint16, cchBuff int32, wFlags uint32, dwhkl syscall.Handle) (ret int32) {
+ r0, _, _ := syscall.Syscall9(procToUnicodeEx.Addr(), 7, uintptr(wVirtKey), uintptr(wScanCode), uintptr(unsafe.Pointer(lpKeyState)), uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(wFlags), uintptr(dwhkl), 0, 0)
+ ret = int32(r0)
+ return
+}
+
+func _TranslateMessage(msg *_MSG) (done bool) {
+ r0, _, _ := syscall.Syscall(procTranslateMessage.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
+ done = r0 != 0
+ return
+}
+
+func _UnregisterClass(lpClassName *uint16, hInstance syscall.Handle) (done bool) {
+ r0, _, _ := syscall.Syscall(procUnregisterClassW.Addr(), 2, uintptr(unsafe.Pointer(lpClassName)), uintptr(hInstance), 0)
+ done = r0 != 0
+ return
+}
diff --git a/shiny/driver/internal/x11key/gen.go b/shiny/driver/internal/x11key/gen.go
new file mode 100644
index 0000000..72201aa
--- /dev/null
+++ b/shiny/driver/internal/x11key/gen.go
@@ -0,0 +1,73 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+// This program generates the table keysymCodePoints from /usr/include/X11/keysymdef.h
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "go/format"
+ "log"
+ "os"
+ "regexp"
+ "strings"
+)
+
+func main() {
+ fh, err := os.Open("/usr/include/X11/keysymdef.h")
+ if err != nil {
+ log.Fatalf("opening keysymdef.h: %v", err)
+ }
+
+ defer fh.Close()
+
+ seen := make(map[string]struct{})
+
+ buf := &bytes.Buffer{}
+
+ fmt.Fprintf(buf, `// generated by go generate; DO NOT EDIT.
+
+package x11key
+
+// keysymCodePoints maps xproto.Keysym values to their corresponding unicode code point.
+var keysymCodePoints = map[rune]rune{
+`)
+
+ re := regexp.MustCompile(`^#define (XK_[^ ]*) *0x([[:xdigit:]]+) .*U\+([[:xdigit:]]+) (.+)(?: |\))\*/$`)
+
+ s := bufio.NewScanner(fh)
+ for s.Scan() {
+ m := re.FindStringSubmatch(strings.TrimSpace(s.Text()))
+ if m == nil {
+ continue
+ }
+
+ if _, isSeen := seen[m[2]]; isSeen {
+ continue
+ }
+ seen[m[2]] = struct{}{}
+
+ fmt.Fprintf(buf, "0x%s: 0x%s, // %s:\t%s\n", m[2], m[3], m[1], m[4])
+
+ }
+ if err := s.Err(); err != nil {
+ log.Fatalf("reading keysymdef.h: %v", err)
+ }
+
+ fmt.Fprintf(buf, "}\n")
+
+ fmted, err := format.Source(buf.Bytes())
+ if err != nil {
+ log.Fatalf("formatting output: %v", err)
+ }
+
+ err = os.WriteFile("table.go", fmted, 0644)
+ if err != nil {
+ log.Fatalf("writing table.go: %v", err)
+ }
+}
diff --git a/shiny/driver/internal/x11key/table.go b/shiny/driver/internal/x11key/table.go
new file mode 100644
index 0000000..63e8344
--- /dev/null
+++ b/shiny/driver/internal/x11key/table.go
@@ -0,0 +1,1577 @@
+// generated by go generate; DO NOT EDIT.
+
+package x11key
+
+// keysymCodePoints maps xproto.Keysym values to their corresponding unicode code point.
+var keysymCodePoints = map[rune]rune{
+ 0x0020: 0x0020, // XK_space: SPACE
+ 0x0021: 0x0021, // XK_exclam: EXCLAMATION MARK
+ 0x0022: 0x0022, // XK_quotedbl: QUOTATION MARK
+ 0x0023: 0x0023, // XK_numbersign: NUMBER SIGN
+ 0x0024: 0x0024, // XK_dollar: DOLLAR SIGN
+ 0x0025: 0x0025, // XK_percent: PERCENT SIGN
+ 0x0026: 0x0026, // XK_ampersand: AMPERSAND
+ 0x0027: 0x0027, // XK_apostrophe: APOSTROPHE
+ 0x0028: 0x0028, // XK_parenleft: LEFT PARENTHESIS
+ 0x0029: 0x0029, // XK_parenright: RIGHT PARENTHESIS
+ 0x002a: 0x002A, // XK_asterisk: ASTERISK
+ 0x002b: 0x002B, // XK_plus: PLUS SIGN
+ 0x002c: 0x002C, // XK_comma: COMMA
+ 0x002d: 0x002D, // XK_minus: HYPHEN-MINUS
+ 0x002e: 0x002E, // XK_period: FULL STOP
+ 0x002f: 0x002F, // XK_slash: SOLIDUS
+ 0x0030: 0x0030, // XK_0: DIGIT ZERO
+ 0x0031: 0x0031, // XK_1: DIGIT ONE
+ 0x0032: 0x0032, // XK_2: DIGIT TWO
+ 0x0033: 0x0033, // XK_3: DIGIT THREE
+ 0x0034: 0x0034, // XK_4: DIGIT FOUR
+ 0x0035: 0x0035, // XK_5: DIGIT FIVE
+ 0x0036: 0x0036, // XK_6: DIGIT SIX
+ 0x0037: 0x0037, // XK_7: DIGIT SEVEN
+ 0x0038: 0x0038, // XK_8: DIGIT EIGHT
+ 0x0039: 0x0039, // XK_9: DIGIT NINE
+ 0x003a: 0x003A, // XK_colon: COLON
+ 0x003b: 0x003B, // XK_semicolon: SEMICOLON
+ 0x003c: 0x003C, // XK_less: LESS-THAN SIGN
+ 0x003d: 0x003D, // XK_equal: EQUALS SIGN
+ 0x003e: 0x003E, // XK_greater: GREATER-THAN SIGN
+ 0x003f: 0x003F, // XK_question: QUESTION MARK
+ 0x0040: 0x0040, // XK_at: COMMERCIAL AT
+ 0x0041: 0x0041, // XK_A: LATIN CAPITAL LETTER A
+ 0x0042: 0x0042, // XK_B: LATIN CAPITAL LETTER B
+ 0x0043: 0x0043, // XK_C: LATIN CAPITAL LETTER C
+ 0x0044: 0x0044, // XK_D: LATIN CAPITAL LETTER D
+ 0x0045: 0x0045, // XK_E: LATIN CAPITAL LETTER E
+ 0x0046: 0x0046, // XK_F: LATIN CAPITAL LETTER F
+ 0x0047: 0x0047, // XK_G: LATIN CAPITAL LETTER G
+ 0x0048: 0x0048, // XK_H: LATIN CAPITAL LETTER H
+ 0x0049: 0x0049, // XK_I: LATIN CAPITAL LETTER I
+ 0x004a: 0x004A, // XK_J: LATIN CAPITAL LETTER J
+ 0x004b: 0x004B, // XK_K: LATIN CAPITAL LETTER K
+ 0x004c: 0x004C, // XK_L: LATIN CAPITAL LETTER L
+ 0x004d: 0x004D, // XK_M: LATIN CAPITAL LETTER M
+ 0x004e: 0x004E, // XK_N: LATIN CAPITAL LETTER N
+ 0x004f: 0x004F, // XK_O: LATIN CAPITAL LETTER O
+ 0x0050: 0x0050, // XK_P: LATIN CAPITAL LETTER P
+ 0x0051: 0x0051, // XK_Q: LATIN CAPITAL LETTER Q
+ 0x0052: 0x0052, // XK_R: LATIN CAPITAL LETTER R
+ 0x0053: 0x0053, // XK_S: LATIN CAPITAL LETTER S
+ 0x0054: 0x0054, // XK_T: LATIN CAPITAL LETTER T
+ 0x0055: 0x0055, // XK_U: LATIN CAPITAL LETTER U
+ 0x0056: 0x0056, // XK_V: LATIN CAPITAL LETTER V
+ 0x0057: 0x0057, // XK_W: LATIN CAPITAL LETTER W
+ 0x0058: 0x0058, // XK_X: LATIN CAPITAL LETTER X
+ 0x0059: 0x0059, // XK_Y: LATIN CAPITAL LETTER Y
+ 0x005a: 0x005A, // XK_Z: LATIN CAPITAL LETTER Z
+ 0x005b: 0x005B, // XK_bracketleft: LEFT SQUARE BRACKET
+ 0x005c: 0x005C, // XK_backslash: REVERSE SOLIDUS
+ 0x005d: 0x005D, // XK_bracketright: RIGHT SQUARE BRACKET
+ 0x005e: 0x005E, // XK_asciicircum: CIRCUMFLEX ACCENT
+ 0x005f: 0x005F, // XK_underscore: LOW LINE
+ 0x0060: 0x0060, // XK_grave: GRAVE ACCENT
+ 0x0061: 0x0061, // XK_a: LATIN SMALL LETTER A
+ 0x0062: 0x0062, // XK_b: LATIN SMALL LETTER B
+ 0x0063: 0x0063, // XK_c: LATIN SMALL LETTER C
+ 0x0064: 0x0064, // XK_d: LATIN SMALL LETTER D
+ 0x0065: 0x0065, // XK_e: LATIN SMALL LETTER E
+ 0x0066: 0x0066, // XK_f: LATIN SMALL LETTER F
+ 0x0067: 0x0067, // XK_g: LATIN SMALL LETTER G
+ 0x0068: 0x0068, // XK_h: LATIN SMALL LETTER H
+ 0x0069: 0x0069, // XK_i: LATIN SMALL LETTER I
+ 0x006a: 0x006A, // XK_j: LATIN SMALL LETTER J
+ 0x006b: 0x006B, // XK_k: LATIN SMALL LETTER K
+ 0x006c: 0x006C, // XK_l: LATIN SMALL LETTER L
+ 0x006d: 0x006D, // XK_m: LATIN SMALL LETTER M
+ 0x006e: 0x006E, // XK_n: LATIN SMALL LETTER N
+ 0x006f: 0x006F, // XK_o: LATIN SMALL LETTER O
+ 0x0070: 0x0070, // XK_p: LATIN SMALL LETTER P
+ 0x0071: 0x0071, // XK_q: LATIN SMALL LETTER Q
+ 0x0072: 0x0072, // XK_r: LATIN SMALL LETTER R
+ 0x0073: 0x0073, // XK_s: LATIN SMALL LETTER S
+ 0x0074: 0x0074, // XK_t: LATIN SMALL LETTER T
+ 0x0075: 0x0075, // XK_u: LATIN SMALL LETTER U
+ 0x0076: 0x0076, // XK_v: LATIN SMALL LETTER V
+ 0x0077: 0x0077, // XK_w: LATIN SMALL LETTER W
+ 0x0078: 0x0078, // XK_x: LATIN SMALL LETTER X
+ 0x0079: 0x0079, // XK_y: LATIN SMALL LETTER Y
+ 0x007a: 0x007A, // XK_z: LATIN SMALL LETTER Z
+ 0x007b: 0x007B, // XK_braceleft: LEFT CURLY BRACKET
+ 0x007c: 0x007C, // XK_bar: VERTICAL LINE
+ 0x007d: 0x007D, // XK_braceright: RIGHT CURLY BRACKET
+ 0x007e: 0x007E, // XK_asciitilde: TILDE
+ 0x00a0: 0x00A0, // XK_nobreakspace: NO-BREAK SPACE
+ 0x00a1: 0x00A1, // XK_exclamdown: INVERTED EXCLAMATION MARK
+ 0x00a2: 0x00A2, // XK_cent: CENT SIGN
+ 0x00a3: 0x00A3, // XK_sterling: POUND SIGN
+ 0x00a4: 0x00A4, // XK_currency: CURRENCY SIGN
+ 0x00a5: 0x00A5, // XK_yen: YEN SIGN
+ 0x00a6: 0x00A6, // XK_brokenbar: BROKEN BAR
+ 0x00a7: 0x00A7, // XK_section: SECTION SIGN
+ 0x00a8: 0x00A8, // XK_diaeresis: DIAERESIS
+ 0x00a9: 0x00A9, // XK_copyright: COPYRIGHT SIGN
+ 0x00aa: 0x00AA, // XK_ordfeminine: FEMININE ORDINAL INDICATOR
+ 0x00ab: 0x00AB, // XK_guillemotleft: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00ac: 0x00AC, // XK_notsign: NOT SIGN
+ 0x00ad: 0x00AD, // XK_hyphen: SOFT HYPHEN
+ 0x00ae: 0x00AE, // XK_registered: REGISTERED SIGN
+ 0x00af: 0x00AF, // XK_macron: MACRON
+ 0x00b0: 0x00B0, // XK_degree: DEGREE SIGN
+ 0x00b1: 0x00B1, // XK_plusminus: PLUS-MINUS SIGN
+ 0x00b2: 0x00B2, // XK_twosuperior: SUPERSCRIPT TWO
+ 0x00b3: 0x00B3, // XK_threesuperior: SUPERSCRIPT THREE
+ 0x00b4: 0x00B4, // XK_acute: ACUTE ACCENT
+ 0x00b5: 0x00B5, // XK_mu: MICRO SIGN
+ 0x00b6: 0x00B6, // XK_paragraph: PILCROW SIGN
+ 0x00b7: 0x00B7, // XK_periodcentered: MIDDLE DOT
+ 0x00b8: 0x00B8, // XK_cedilla: CEDILLA
+ 0x00b9: 0x00B9, // XK_onesuperior: SUPERSCRIPT ONE
+ 0x00ba: 0x00BA, // XK_masculine: MASCULINE ORDINAL INDICATOR
+ 0x00bb: 0x00BB, // XK_guillemotright: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00bc: 0x00BC, // XK_onequarter: VULGAR FRACTION ONE QUARTER
+ 0x00bd: 0x00BD, // XK_onehalf: VULGAR FRACTION ONE HALF
+ 0x00be: 0x00BE, // XK_threequarters: VULGAR FRACTION THREE QUARTERS
+ 0x00bf: 0x00BF, // XK_questiondown: INVERTED QUESTION MARK
+ 0x00c0: 0x00C0, // XK_Agrave: LATIN CAPITAL LETTER A WITH GRAVE
+ 0x00c1: 0x00C1, // XK_Aacute: LATIN CAPITAL LETTER A WITH ACUTE
+ 0x00c2: 0x00C2, // XK_Acircumflex: LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ 0x00c3: 0x00C3, // XK_Atilde: LATIN CAPITAL LETTER A WITH TILDE
+ 0x00c4: 0x00C4, // XK_Adiaeresis: LATIN CAPITAL LETTER A WITH DIAERESIS
+ 0x00c5: 0x00C5, // XK_Aring: LATIN CAPITAL LETTER A WITH RING ABOVE
+ 0x00c6: 0x00C6, // XK_AE: LATIN CAPITAL LETTER AE
+ 0x00c7: 0x00C7, // XK_Ccedilla: LATIN CAPITAL LETTER C WITH CEDILLA
+ 0x00c8: 0x00C8, // XK_Egrave: LATIN CAPITAL LETTER E WITH GRAVE
+ 0x00c9: 0x00C9, // XK_Eacute: LATIN CAPITAL LETTER E WITH ACUTE
+ 0x00ca: 0x00CA, // XK_Ecircumflex: LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ 0x00cb: 0x00CB, // XK_Ediaeresis: LATIN CAPITAL LETTER E WITH DIAERESIS
+ 0x00cc: 0x00CC, // XK_Igrave: LATIN CAPITAL LETTER I WITH GRAVE
+ 0x00cd: 0x00CD, // XK_Iacute: LATIN CAPITAL LETTER I WITH ACUTE
+ 0x00ce: 0x00CE, // XK_Icircumflex: LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ 0x00cf: 0x00CF, // XK_Idiaeresis: LATIN CAPITAL LETTER I WITH DIAERESIS
+ 0x00d0: 0x00D0, // XK_ETH: LATIN CAPITAL LETTER ETH
+ 0x00d1: 0x00D1, // XK_Ntilde: LATIN CAPITAL LETTER N WITH TILDE
+ 0x00d2: 0x00D2, // XK_Ograve: LATIN CAPITAL LETTER O WITH GRAVE
+ 0x00d3: 0x00D3, // XK_Oacute: LATIN CAPITAL LETTER O WITH ACUTE
+ 0x00d4: 0x00D4, // XK_Ocircumflex: LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ 0x00d5: 0x00D5, // XK_Otilde: LATIN CAPITAL LETTER O WITH TILDE
+ 0x00d6: 0x00D6, // XK_Odiaeresis: LATIN CAPITAL LETTER O WITH DIAERESIS
+ 0x00d7: 0x00D7, // XK_multiply: MULTIPLICATION SIGN
+ 0x00d8: 0x00D8, // XK_Oslash: LATIN CAPITAL LETTER O WITH STROKE
+ 0x00d9: 0x00D9, // XK_Ugrave: LATIN CAPITAL LETTER U WITH GRAVE
+ 0x00da: 0x00DA, // XK_Uacute: LATIN CAPITAL LETTER U WITH ACUTE
+ 0x00db: 0x00DB, // XK_Ucircumflex: LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ 0x00dc: 0x00DC, // XK_Udiaeresis: LATIN CAPITAL LETTER U WITH DIAERESIS
+ 0x00dd: 0x00DD, // XK_Yacute: LATIN CAPITAL LETTER Y WITH ACUTE
+ 0x00de: 0x00DE, // XK_THORN: LATIN CAPITAL LETTER THORN
+ 0x00df: 0x00DF, // XK_ssharp: LATIN SMALL LETTER SHARP S
+ 0x00e0: 0x00E0, // XK_agrave: LATIN SMALL LETTER A WITH GRAVE
+ 0x00e1: 0x00E1, // XK_aacute: LATIN SMALL LETTER A WITH ACUTE
+ 0x00e2: 0x00E2, // XK_acircumflex: LATIN SMALL LETTER A WITH CIRCUMFLEX
+ 0x00e3: 0x00E3, // XK_atilde: LATIN SMALL LETTER A WITH TILDE
+ 0x00e4: 0x00E4, // XK_adiaeresis: LATIN SMALL LETTER A WITH DIAERESIS
+ 0x00e5: 0x00E5, // XK_aring: LATIN SMALL LETTER A WITH RING ABOVE
+ 0x00e6: 0x00E6, // XK_ae: LATIN SMALL LETTER AE
+ 0x00e7: 0x00E7, // XK_ccedilla: LATIN SMALL LETTER C WITH CEDILLA
+ 0x00e8: 0x00E8, // XK_egrave: LATIN SMALL LETTER E WITH GRAVE
+ 0x00e9: 0x00E9, // XK_eacute: LATIN SMALL LETTER E WITH ACUTE
+ 0x00ea: 0x00EA, // XK_ecircumflex: LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 0x00eb: 0x00EB, // XK_ediaeresis: LATIN SMALL LETTER E WITH DIAERESIS
+ 0x00ec: 0x00EC, // XK_igrave: LATIN SMALL LETTER I WITH GRAVE
+ 0x00ed: 0x00ED, // XK_iacute: LATIN SMALL LETTER I WITH ACUTE
+ 0x00ee: 0x00EE, // XK_icircumflex: LATIN SMALL LETTER I WITH CIRCUMFLEX
+ 0x00ef: 0x00EF, // XK_idiaeresis: LATIN SMALL LETTER I WITH DIAERESIS
+ 0x00f0: 0x00F0, // XK_eth: LATIN SMALL LETTER ETH
+ 0x00f1: 0x00F1, // XK_ntilde: LATIN SMALL LETTER N WITH TILDE
+ 0x00f2: 0x00F2, // XK_ograve: LATIN SMALL LETTER O WITH GRAVE
+ 0x00f3: 0x00F3, // XK_oacute: LATIN SMALL LETTER O WITH ACUTE
+ 0x00f4: 0x00F4, // XK_ocircumflex: LATIN SMALL LETTER O WITH CIRCUMFLEX
+ 0x00f5: 0x00F5, // XK_otilde: LATIN SMALL LETTER O WITH TILDE
+ 0x00f6: 0x00F6, // XK_odiaeresis: LATIN SMALL LETTER O WITH DIAERESIS
+ 0x00f7: 0x00F7, // XK_division: DIVISION SIGN
+ 0x00f8: 0x00F8, // XK_oslash: LATIN SMALL LETTER O WITH STROKE
+ 0x00f9: 0x00F9, // XK_ugrave: LATIN SMALL LETTER U WITH GRAVE
+ 0x00fa: 0x00FA, // XK_uacute: LATIN SMALL LETTER U WITH ACUTE
+ 0x00fb: 0x00FB, // XK_ucircumflex: LATIN SMALL LETTER U WITH CIRCUMFLEX
+ 0x00fc: 0x00FC, // XK_udiaeresis: LATIN SMALL LETTER U WITH DIAERESIS
+ 0x00fd: 0x00FD, // XK_yacute: LATIN SMALL LETTER Y WITH ACUTE
+ 0x00fe: 0x00FE, // XK_thorn: LATIN SMALL LETTER THORN
+ 0x00ff: 0x00FF, // XK_ydiaeresis: LATIN SMALL LETTER Y WITH DIAERESIS
+ 0x01a1: 0x0104, // XK_Aogonek: LATIN CAPITAL LETTER A WITH OGONEK
+ 0x01a2: 0x02D8, // XK_breve: BREVE
+ 0x01a3: 0x0141, // XK_Lstroke: LATIN CAPITAL LETTER L WITH STROKE
+ 0x01a5: 0x013D, // XK_Lcaron: LATIN CAPITAL LETTER L WITH CARON
+ 0x01a6: 0x015A, // XK_Sacute: LATIN CAPITAL LETTER S WITH ACUTE
+ 0x01a9: 0x0160, // XK_Scaron: LATIN CAPITAL LETTER S WITH CARON
+ 0x01aa: 0x015E, // XK_Scedilla: LATIN CAPITAL LETTER S WITH CEDILLA
+ 0x01ab: 0x0164, // XK_Tcaron: LATIN CAPITAL LETTER T WITH CARON
+ 0x01ac: 0x0179, // XK_Zacute: LATIN CAPITAL LETTER Z WITH ACUTE
+ 0x01ae: 0x017D, // XK_Zcaron: LATIN CAPITAL LETTER Z WITH CARON
+ 0x01af: 0x017B, // XK_Zabovedot: LATIN CAPITAL LETTER Z WITH DOT ABOVE
+ 0x01b1: 0x0105, // XK_aogonek: LATIN SMALL LETTER A WITH OGONEK
+ 0x01b2: 0x02DB, // XK_ogonek: OGONEK
+ 0x01b3: 0x0142, // XK_lstroke: LATIN SMALL LETTER L WITH STROKE
+ 0x01b5: 0x013E, // XK_lcaron: LATIN SMALL LETTER L WITH CARON
+ 0x01b6: 0x015B, // XK_sacute: LATIN SMALL LETTER S WITH ACUTE
+ 0x01b7: 0x02C7, // XK_caron: CARON
+ 0x01b9: 0x0161, // XK_scaron: LATIN SMALL LETTER S WITH CARON
+ 0x01ba: 0x015F, // XK_scedilla: LATIN SMALL LETTER S WITH CEDILLA
+ 0x01bb: 0x0165, // XK_tcaron: LATIN SMALL LETTER T WITH CARON
+ 0x01bc: 0x017A, // XK_zacute: LATIN SMALL LETTER Z WITH ACUTE
+ 0x01bd: 0x02DD, // XK_doubleacute: DOUBLE ACUTE ACCENT
+ 0x01be: 0x017E, // XK_zcaron: LATIN SMALL LETTER Z WITH CARON
+ 0x01bf: 0x017C, // XK_zabovedot: LATIN SMALL LETTER Z WITH DOT ABOVE
+ 0x01c0: 0x0154, // XK_Racute: LATIN CAPITAL LETTER R WITH ACUTE
+ 0x01c3: 0x0102, // XK_Abreve: LATIN CAPITAL LETTER A WITH BREVE
+ 0x01c5: 0x0139, // XK_Lacute: LATIN CAPITAL LETTER L WITH ACUTE
+ 0x01c6: 0x0106, // XK_Cacute: LATIN CAPITAL LETTER C WITH ACUTE
+ 0x01c8: 0x010C, // XK_Ccaron: LATIN CAPITAL LETTER C WITH CARON
+ 0x01ca: 0x0118, // XK_Eogonek: LATIN CAPITAL LETTER E WITH OGONEK
+ 0x01cc: 0x011A, // XK_Ecaron: LATIN CAPITAL LETTER E WITH CARON
+ 0x01cf: 0x010E, // XK_Dcaron: LATIN CAPITAL LETTER D WITH CARON
+ 0x01d0: 0x0110, // XK_Dstroke: LATIN CAPITAL LETTER D WITH STROKE
+ 0x01d1: 0x0143, // XK_Nacute: LATIN CAPITAL LETTER N WITH ACUTE
+ 0x01d2: 0x0147, // XK_Ncaron: LATIN CAPITAL LETTER N WITH CARON
+ 0x01d5: 0x0150, // XK_Odoubleacute: LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+ 0x01d8: 0x0158, // XK_Rcaron: LATIN CAPITAL LETTER R WITH CARON
+ 0x01d9: 0x016E, // XK_Uring: LATIN CAPITAL LETTER U WITH RING ABOVE
+ 0x01db: 0x0170, // XK_Udoubleacute: LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+ 0x01de: 0x0162, // XK_Tcedilla: LATIN CAPITAL LETTER T WITH CEDILLA
+ 0x01e0: 0x0155, // XK_racute: LATIN SMALL LETTER R WITH ACUTE
+ 0x01e3: 0x0103, // XK_abreve: LATIN SMALL LETTER A WITH BREVE
+ 0x01e5: 0x013A, // XK_lacute: LATIN SMALL LETTER L WITH ACUTE
+ 0x01e6: 0x0107, // XK_cacute: LATIN SMALL LETTER C WITH ACUTE
+ 0x01e8: 0x010D, // XK_ccaron: LATIN SMALL LETTER C WITH CARON
+ 0x01ea: 0x0119, // XK_eogonek: LATIN SMALL LETTER E WITH OGONEK
+ 0x01ec: 0x011B, // XK_ecaron: LATIN SMALL LETTER E WITH CARON
+ 0x01ef: 0x010F, // XK_dcaron: LATIN SMALL LETTER D WITH CARON
+ 0x01f0: 0x0111, // XK_dstroke: LATIN SMALL LETTER D WITH STROKE
+ 0x01f1: 0x0144, // XK_nacute: LATIN SMALL LETTER N WITH ACUTE
+ 0x01f2: 0x0148, // XK_ncaron: LATIN SMALL LETTER N WITH CARON
+ 0x01f5: 0x0151, // XK_odoubleacute: LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ 0x01f8: 0x0159, // XK_rcaron: LATIN SMALL LETTER R WITH CARON
+ 0x01f9: 0x016F, // XK_uring: LATIN SMALL LETTER U WITH RING ABOVE
+ 0x01fb: 0x0171, // XK_udoubleacute: LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ 0x01fe: 0x0163, // XK_tcedilla: LATIN SMALL LETTER T WITH CEDILLA
+ 0x01ff: 0x02D9, // XK_abovedot: DOT ABOVE
+ 0x02a1: 0x0126, // XK_Hstroke: LATIN CAPITAL LETTER H WITH STROKE
+ 0x02a6: 0x0124, // XK_Hcircumflex: LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+ 0x02a9: 0x0130, // XK_Iabovedot: LATIN CAPITAL LETTER I WITH DOT ABOVE
+ 0x02ab: 0x011E, // XK_Gbreve: LATIN CAPITAL LETTER G WITH BREVE
+ 0x02ac: 0x0134, // XK_Jcircumflex: LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+ 0x02b1: 0x0127, // XK_hstroke: LATIN SMALL LETTER H WITH STROKE
+ 0x02b6: 0x0125, // XK_hcircumflex: LATIN SMALL LETTER H WITH CIRCUMFLEX
+ 0x02b9: 0x0131, // XK_idotless: LATIN SMALL LETTER DOTLESS I
+ 0x02bb: 0x011F, // XK_gbreve: LATIN SMALL LETTER G WITH BREVE
+ 0x02bc: 0x0135, // XK_jcircumflex: LATIN SMALL LETTER J WITH CIRCUMFLEX
+ 0x02c5: 0x010A, // XK_Cabovedot: LATIN CAPITAL LETTER C WITH DOT ABOVE
+ 0x02c6: 0x0108, // XK_Ccircumflex: LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+ 0x02d5: 0x0120, // XK_Gabovedot: LATIN CAPITAL LETTER G WITH DOT ABOVE
+ 0x02d8: 0x011C, // XK_Gcircumflex: LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+ 0x02dd: 0x016C, // XK_Ubreve: LATIN CAPITAL LETTER U WITH BREVE
+ 0x02de: 0x015C, // XK_Scircumflex: LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+ 0x02e5: 0x010B, // XK_cabovedot: LATIN SMALL LETTER C WITH DOT ABOVE
+ 0x02e6: 0x0109, // XK_ccircumflex: LATIN SMALL LETTER C WITH CIRCUMFLEX
+ 0x02f5: 0x0121, // XK_gabovedot: LATIN SMALL LETTER G WITH DOT ABOVE
+ 0x02f8: 0x011D, // XK_gcircumflex: LATIN SMALL LETTER G WITH CIRCUMFLEX
+ 0x02fd: 0x016D, // XK_ubreve: LATIN SMALL LETTER U WITH BREVE
+ 0x02fe: 0x015D, // XK_scircumflex: LATIN SMALL LETTER S WITH CIRCUMFLEX
+ 0x03a2: 0x0138, // XK_kra: LATIN SMALL LETTER KRA
+ 0x03a3: 0x0156, // XK_Rcedilla: LATIN CAPITAL LETTER R WITH CEDILLA
+ 0x03a5: 0x0128, // XK_Itilde: LATIN CAPITAL LETTER I WITH TILDE
+ 0x03a6: 0x013B, // XK_Lcedilla: LATIN CAPITAL LETTER L WITH CEDILLA
+ 0x03aa: 0x0112, // XK_Emacron: LATIN CAPITAL LETTER E WITH MACRON
+ 0x03ab: 0x0122, // XK_Gcedilla: LATIN CAPITAL LETTER G WITH CEDILLA
+ 0x03ac: 0x0166, // XK_Tslash: LATIN CAPITAL LETTER T WITH STROKE
+ 0x03b3: 0x0157, // XK_rcedilla: LATIN SMALL LETTER R WITH CEDILLA
+ 0x03b5: 0x0129, // XK_itilde: LATIN SMALL LETTER I WITH TILDE
+ 0x03b6: 0x013C, // XK_lcedilla: LATIN SMALL LETTER L WITH CEDILLA
+ 0x03ba: 0x0113, // XK_emacron: LATIN SMALL LETTER E WITH MACRON
+ 0x03bb: 0x0123, // XK_gcedilla: LATIN SMALL LETTER G WITH CEDILLA
+ 0x03bc: 0x0167, // XK_tslash: LATIN SMALL LETTER T WITH STROKE
+ 0x03bd: 0x014A, // XK_ENG: LATIN CAPITAL LETTER ENG
+ 0x03bf: 0x014B, // XK_eng: LATIN SMALL LETTER ENG
+ 0x03c0: 0x0100, // XK_Amacron: LATIN CAPITAL LETTER A WITH MACRON
+ 0x03c7: 0x012E, // XK_Iogonek: LATIN CAPITAL LETTER I WITH OGONEK
+ 0x03cc: 0x0116, // XK_Eabovedot: LATIN CAPITAL LETTER E WITH DOT ABOVE
+ 0x03cf: 0x012A, // XK_Imacron: LATIN CAPITAL LETTER I WITH MACRON
+ 0x03d1: 0x0145, // XK_Ncedilla: LATIN CAPITAL LETTER N WITH CEDILLA
+ 0x03d2: 0x014C, // XK_Omacron: LATIN CAPITAL LETTER O WITH MACRON
+ 0x03d3: 0x0136, // XK_Kcedilla: LATIN CAPITAL LETTER K WITH CEDILLA
+ 0x03d9: 0x0172, // XK_Uogonek: LATIN CAPITAL LETTER U WITH OGONEK
+ 0x03dd: 0x0168, // XK_Utilde: LATIN CAPITAL LETTER U WITH TILDE
+ 0x03de: 0x016A, // XK_Umacron: LATIN CAPITAL LETTER U WITH MACRON
+ 0x03e0: 0x0101, // XK_amacron: LATIN SMALL LETTER A WITH MACRON
+ 0x03e7: 0x012F, // XK_iogonek: LATIN SMALL LETTER I WITH OGONEK
+ 0x03ec: 0x0117, // XK_eabovedot: LATIN SMALL LETTER E WITH DOT ABOVE
+ 0x03ef: 0x012B, // XK_imacron: LATIN SMALL LETTER I WITH MACRON
+ 0x03f1: 0x0146, // XK_ncedilla: LATIN SMALL LETTER N WITH CEDILLA
+ 0x03f2: 0x014D, // XK_omacron: LATIN SMALL LETTER O WITH MACRON
+ 0x03f3: 0x0137, // XK_kcedilla: LATIN SMALL LETTER K WITH CEDILLA
+ 0x03f9: 0x0173, // XK_uogonek: LATIN SMALL LETTER U WITH OGONEK
+ 0x03fd: 0x0169, // XK_utilde: LATIN SMALL LETTER U WITH TILDE
+ 0x03fe: 0x016B, // XK_umacron: LATIN SMALL LETTER U WITH MACRON
+ 0x1000174: 0x0174, // XK_Wcircumflex: LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+ 0x1000175: 0x0175, // XK_wcircumflex: LATIN SMALL LETTER W WITH CIRCUMFLEX
+ 0x1000176: 0x0176, // XK_Ycircumflex: LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+ 0x1000177: 0x0177, // XK_ycircumflex: LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ 0x1001e02: 0x1E02, // XK_Babovedot: LATIN CAPITAL LETTER B WITH DOT ABOVE
+ 0x1001e03: 0x1E03, // XK_babovedot: LATIN SMALL LETTER B WITH DOT ABOVE
+ 0x1001e0a: 0x1E0A, // XK_Dabovedot: LATIN CAPITAL LETTER D WITH DOT ABOVE
+ 0x1001e0b: 0x1E0B, // XK_dabovedot: LATIN SMALL LETTER D WITH DOT ABOVE
+ 0x1001e1e: 0x1E1E, // XK_Fabovedot: LATIN CAPITAL LETTER F WITH DOT ABOVE
+ 0x1001e1f: 0x1E1F, // XK_fabovedot: LATIN SMALL LETTER F WITH DOT ABOVE
+ 0x1001e40: 0x1E40, // XK_Mabovedot: LATIN CAPITAL LETTER M WITH DOT ABOVE
+ 0x1001e41: 0x1E41, // XK_mabovedot: LATIN SMALL LETTER M WITH DOT ABOVE
+ 0x1001e56: 0x1E56, // XK_Pabovedot: LATIN CAPITAL LETTER P WITH DOT ABOVE
+ 0x1001e57: 0x1E57, // XK_pabovedot: LATIN SMALL LETTER P WITH DOT ABOVE
+ 0x1001e60: 0x1E60, // XK_Sabovedot: LATIN CAPITAL LETTER S WITH DOT ABOVE
+ 0x1001e61: 0x1E61, // XK_sabovedot: LATIN SMALL LETTER S WITH DOT ABOVE
+ 0x1001e6a: 0x1E6A, // XK_Tabovedot: LATIN CAPITAL LETTER T WITH DOT ABOVE
+ 0x1001e6b: 0x1E6B, // XK_tabovedot: LATIN SMALL LETTER T WITH DOT ABOVE
+ 0x1001e80: 0x1E80, // XK_Wgrave: LATIN CAPITAL LETTER W WITH GRAVE
+ 0x1001e81: 0x1E81, // XK_wgrave: LATIN SMALL LETTER W WITH GRAVE
+ 0x1001e82: 0x1E82, // XK_Wacute: LATIN CAPITAL LETTER W WITH ACUTE
+ 0x1001e83: 0x1E83, // XK_wacute: LATIN SMALL LETTER W WITH ACUTE
+ 0x1001e84: 0x1E84, // XK_Wdiaeresis: LATIN CAPITAL LETTER W WITH DIAERESIS
+ 0x1001e85: 0x1E85, // XK_wdiaeresis: LATIN SMALL LETTER W WITH DIAERESIS
+ 0x1001ef2: 0x1EF2, // XK_Ygrave: LATIN CAPITAL LETTER Y WITH GRAVE
+ 0x1001ef3: 0x1EF3, // XK_ygrave: LATIN SMALL LETTER Y WITH GRAVE
+ 0x13bc: 0x0152, // XK_OE: LATIN CAPITAL LIGATURE OE
+ 0x13bd: 0x0153, // XK_oe: LATIN SMALL LIGATURE OE
+ 0x13be: 0x0178, // XK_Ydiaeresis: LATIN CAPITAL LETTER Y WITH DIAERESIS
+ 0x047e: 0x203E, // XK_overline: OVERLINE
+ 0x04a1: 0x3002, // XK_kana_fullstop: IDEOGRAPHIC FULL STOP
+ 0x04a2: 0x300C, // XK_kana_openingbracket: LEFT CORNER BRACKET
+ 0x04a3: 0x300D, // XK_kana_closingbracket: RIGHT CORNER BRACKET
+ 0x04a4: 0x3001, // XK_kana_comma: IDEOGRAPHIC COMMA
+ 0x04a5: 0x30FB, // XK_kana_conjunctive: KATAKANA MIDDLE DOT
+ 0x04a6: 0x30F2, // XK_kana_WO: KATAKANA LETTER WO
+ 0x04a7: 0x30A1, // XK_kana_a: KATAKANA LETTER SMALL A
+ 0x04a8: 0x30A3, // XK_kana_i: KATAKANA LETTER SMALL I
+ 0x04a9: 0x30A5, // XK_kana_u: KATAKANA LETTER SMALL U
+ 0x04aa: 0x30A7, // XK_kana_e: KATAKANA LETTER SMALL E
+ 0x04ab: 0x30A9, // XK_kana_o: KATAKANA LETTER SMALL O
+ 0x04ac: 0x30E3, // XK_kana_ya: KATAKANA LETTER SMALL YA
+ 0x04ad: 0x30E5, // XK_kana_yu: KATAKANA LETTER SMALL YU
+ 0x04ae: 0x30E7, // XK_kana_yo: KATAKANA LETTER SMALL YO
+ 0x04af: 0x30C3, // XK_kana_tsu: KATAKANA LETTER SMALL TU
+ 0x04b0: 0x30FC, // XK_prolongedsound: KATAKANA-HIRAGANA PROLONGED SOUND MARK
+ 0x04b1: 0x30A2, // XK_kana_A: KATAKANA LETTER A
+ 0x04b2: 0x30A4, // XK_kana_I: KATAKANA LETTER I
+ 0x04b3: 0x30A6, // XK_kana_U: KATAKANA LETTER U
+ 0x04b4: 0x30A8, // XK_kana_E: KATAKANA LETTER E
+ 0x04b5: 0x30AA, // XK_kana_O: KATAKANA LETTER O
+ 0x04b6: 0x30AB, // XK_kana_KA: KATAKANA LETTER KA
+ 0x04b7: 0x30AD, // XK_kana_KI: KATAKANA LETTER KI
+ 0x04b8: 0x30AF, // XK_kana_KU: KATAKANA LETTER KU
+ 0x04b9: 0x30B1, // XK_kana_KE: KATAKANA LETTER KE
+ 0x04ba: 0x30B3, // XK_kana_KO: KATAKANA LETTER KO
+ 0x04bb: 0x30B5, // XK_kana_SA: KATAKANA LETTER SA
+ 0x04bc: 0x30B7, // XK_kana_SHI: KATAKANA LETTER SI
+ 0x04bd: 0x30B9, // XK_kana_SU: KATAKANA LETTER SU
+ 0x04be: 0x30BB, // XK_kana_SE: KATAKANA LETTER SE
+ 0x04bf: 0x30BD, // XK_kana_SO: KATAKANA LETTER SO
+ 0x04c0: 0x30BF, // XK_kana_TA: KATAKANA LETTER TA
+ 0x04c1: 0x30C1, // XK_kana_CHI: KATAKANA LETTER TI
+ 0x04c2: 0x30C4, // XK_kana_TSU: KATAKANA LETTER TU
+ 0x04c3: 0x30C6, // XK_kana_TE: KATAKANA LETTER TE
+ 0x04c4: 0x30C8, // XK_kana_TO: KATAKANA LETTER TO
+ 0x04c5: 0x30CA, // XK_kana_NA: KATAKANA LETTER NA
+ 0x04c6: 0x30CB, // XK_kana_NI: KATAKANA LETTER NI
+ 0x04c7: 0x30CC, // XK_kana_NU: KATAKANA LETTER NU
+ 0x04c8: 0x30CD, // XK_kana_NE: KATAKANA LETTER NE
+ 0x04c9: 0x30CE, // XK_kana_NO: KATAKANA LETTER NO
+ 0x04ca: 0x30CF, // XK_kana_HA: KATAKANA LETTER HA
+ 0x04cb: 0x30D2, // XK_kana_HI: KATAKANA LETTER HI
+ 0x04cc: 0x30D5, // XK_kana_FU: KATAKANA LETTER HU
+ 0x04cd: 0x30D8, // XK_kana_HE: KATAKANA LETTER HE
+ 0x04ce: 0x30DB, // XK_kana_HO: KATAKANA LETTER HO
+ 0x04cf: 0x30DE, // XK_kana_MA: KATAKANA LETTER MA
+ 0x04d0: 0x30DF, // XK_kana_MI: KATAKANA LETTER MI
+ 0x04d1: 0x30E0, // XK_kana_MU: KATAKANA LETTER MU
+ 0x04d2: 0x30E1, // XK_kana_ME: KATAKANA LETTER ME
+ 0x04d3: 0x30E2, // XK_kana_MO: KATAKANA LETTER MO
+ 0x04d4: 0x30E4, // XK_kana_YA: KATAKANA LETTER YA
+ 0x04d5: 0x30E6, // XK_kana_YU: KATAKANA LETTER YU
+ 0x04d6: 0x30E8, // XK_kana_YO: KATAKANA LETTER YO
+ 0x04d7: 0x30E9, // XK_kana_RA: KATAKANA LETTER RA
+ 0x04d8: 0x30EA, // XK_kana_RI: KATAKANA LETTER RI
+ 0x04d9: 0x30EB, // XK_kana_RU: KATAKANA LETTER RU
+ 0x04da: 0x30EC, // XK_kana_RE: KATAKANA LETTER RE
+ 0x04db: 0x30ED, // XK_kana_RO: KATAKANA LETTER RO
+ 0x04dc: 0x30EF, // XK_kana_WA: KATAKANA LETTER WA
+ 0x04dd: 0x30F3, // XK_kana_N: KATAKANA LETTER N
+ 0x04de: 0x309B, // XK_voicedsound: KATAKANA-HIRAGANA VOICED SOUND MARK
+ 0x04df: 0x309C, // XK_semivoicedsound: KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+ 0x10006f0: 0x06F0, // XK_Farsi_0: EXTENDED ARABIC-INDIC DIGIT ZERO
+ 0x10006f1: 0x06F1, // XK_Farsi_1: EXTENDED ARABIC-INDIC DIGIT ONE
+ 0x10006f2: 0x06F2, // XK_Farsi_2: EXTENDED ARABIC-INDIC DIGIT TWO
+ 0x10006f3: 0x06F3, // XK_Farsi_3: EXTENDED ARABIC-INDIC DIGIT THREE
+ 0x10006f4: 0x06F4, // XK_Farsi_4: EXTENDED ARABIC-INDIC DIGIT FOUR
+ 0x10006f5: 0x06F5, // XK_Farsi_5: EXTENDED ARABIC-INDIC DIGIT FIVE
+ 0x10006f6: 0x06F6, // XK_Farsi_6: EXTENDED ARABIC-INDIC DIGIT SIX
+ 0x10006f7: 0x06F7, // XK_Farsi_7: EXTENDED ARABIC-INDIC DIGIT SEVEN
+ 0x10006f8: 0x06F8, // XK_Farsi_8: EXTENDED ARABIC-INDIC DIGIT EIGHT
+ 0x10006f9: 0x06F9, // XK_Farsi_9: EXTENDED ARABIC-INDIC DIGIT NINE
+ 0x100066a: 0x066A, // XK_Arabic_percent: ARABIC PERCENT SIGN
+ 0x1000670: 0x0670, // XK_Arabic_superscript_alef: ARABIC LETTER SUPERSCRIPT ALEF
+ 0x1000679: 0x0679, // XK_Arabic_tteh: ARABIC LETTER TTEH
+ 0x100067e: 0x067E, // XK_Arabic_peh: ARABIC LETTER PEH
+ 0x1000686: 0x0686, // XK_Arabic_tcheh: ARABIC LETTER TCHEH
+ 0x1000688: 0x0688, // XK_Arabic_ddal: ARABIC LETTER DDAL
+ 0x1000691: 0x0691, // XK_Arabic_rreh: ARABIC LETTER RREH
+ 0x05ac: 0x060C, // XK_Arabic_comma: ARABIC COMMA
+ 0x10006d4: 0x06D4, // XK_Arabic_fullstop: ARABIC FULL STOP
+ 0x1000660: 0x0660, // XK_Arabic_0: ARABIC-INDIC DIGIT ZERO
+ 0x1000661: 0x0661, // XK_Arabic_1: ARABIC-INDIC DIGIT ONE
+ 0x1000662: 0x0662, // XK_Arabic_2: ARABIC-INDIC DIGIT TWO
+ 0x1000663: 0x0663, // XK_Arabic_3: ARABIC-INDIC DIGIT THREE
+ 0x1000664: 0x0664, // XK_Arabic_4: ARABIC-INDIC DIGIT FOUR
+ 0x1000665: 0x0665, // XK_Arabic_5: ARABIC-INDIC DIGIT FIVE
+ 0x1000666: 0x0666, // XK_Arabic_6: ARABIC-INDIC DIGIT SIX
+ 0x1000667: 0x0667, // XK_Arabic_7: ARABIC-INDIC DIGIT SEVEN
+ 0x1000668: 0x0668, // XK_Arabic_8: ARABIC-INDIC DIGIT EIGHT
+ 0x1000669: 0x0669, // XK_Arabic_9: ARABIC-INDIC DIGIT NINE
+ 0x05bb: 0x061B, // XK_Arabic_semicolon: ARABIC SEMICOLON
+ 0x05bf: 0x061F, // XK_Arabic_question_mark: ARABIC QUESTION MARK
+ 0x05c1: 0x0621, // XK_Arabic_hamza: ARABIC LETTER HAMZA
+ 0x05c2: 0x0622, // XK_Arabic_maddaonalef: ARABIC LETTER ALEF WITH MADDA ABOVE
+ 0x05c3: 0x0623, // XK_Arabic_hamzaonalef: ARABIC LETTER ALEF WITH HAMZA ABOVE
+ 0x05c4: 0x0624, // XK_Arabic_hamzaonwaw: ARABIC LETTER WAW WITH HAMZA ABOVE
+ 0x05c5: 0x0625, // XK_Arabic_hamzaunderalef: ARABIC LETTER ALEF WITH HAMZA BELOW
+ 0x05c6: 0x0626, // XK_Arabic_hamzaonyeh: ARABIC LETTER YEH WITH HAMZA ABOVE
+ 0x05c7: 0x0627, // XK_Arabic_alef: ARABIC LETTER ALEF
+ 0x05c8: 0x0628, // XK_Arabic_beh: ARABIC LETTER BEH
+ 0x05c9: 0x0629, // XK_Arabic_tehmarbuta: ARABIC LETTER TEH MARBUTA
+ 0x05ca: 0x062A, // XK_Arabic_teh: ARABIC LETTER TEH
+ 0x05cb: 0x062B, // XK_Arabic_theh: ARABIC LETTER THEH
+ 0x05cc: 0x062C, // XK_Arabic_jeem: ARABIC LETTER JEEM
+ 0x05cd: 0x062D, // XK_Arabic_hah: ARABIC LETTER HAH
+ 0x05ce: 0x062E, // XK_Arabic_khah: ARABIC LETTER KHAH
+ 0x05cf: 0x062F, // XK_Arabic_dal: ARABIC LETTER DAL
+ 0x05d0: 0x0630, // XK_Arabic_thal: ARABIC LETTER THAL
+ 0x05d1: 0x0631, // XK_Arabic_ra: ARABIC LETTER REH
+ 0x05d2: 0x0632, // XK_Arabic_zain: ARABIC LETTER ZAIN
+ 0x05d3: 0x0633, // XK_Arabic_seen: ARABIC LETTER SEEN
+ 0x05d4: 0x0634, // XK_Arabic_sheen: ARABIC LETTER SHEEN
+ 0x05d5: 0x0635, // XK_Arabic_sad: ARABIC LETTER SAD
+ 0x05d6: 0x0636, // XK_Arabic_dad: ARABIC LETTER DAD
+ 0x05d7: 0x0637, // XK_Arabic_tah: ARABIC LETTER TAH
+ 0x05d8: 0x0638, // XK_Arabic_zah: ARABIC LETTER ZAH
+ 0x05d9: 0x0639, // XK_Arabic_ain: ARABIC LETTER AIN
+ 0x05da: 0x063A, // XK_Arabic_ghain: ARABIC LETTER GHAIN
+ 0x05e0: 0x0640, // XK_Arabic_tatweel: ARABIC TATWEEL
+ 0x05e1: 0x0641, // XK_Arabic_feh: ARABIC LETTER FEH
+ 0x05e2: 0x0642, // XK_Arabic_qaf: ARABIC LETTER QAF
+ 0x05e3: 0x0643, // XK_Arabic_kaf: ARABIC LETTER KAF
+ 0x05e4: 0x0644, // XK_Arabic_lam: ARABIC LETTER LAM
+ 0x05e5: 0x0645, // XK_Arabic_meem: ARABIC LETTER MEEM
+ 0x05e6: 0x0646, // XK_Arabic_noon: ARABIC LETTER NOON
+ 0x05e7: 0x0647, // XK_Arabic_ha: ARABIC LETTER HEH
+ 0x05e8: 0x0648, // XK_Arabic_waw: ARABIC LETTER WAW
+ 0x05e9: 0x0649, // XK_Arabic_alefmaksura: ARABIC LETTER ALEF MAKSURA
+ 0x05ea: 0x064A, // XK_Arabic_yeh: ARABIC LETTER YEH
+ 0x05eb: 0x064B, // XK_Arabic_fathatan: ARABIC FATHATAN
+ 0x05ec: 0x064C, // XK_Arabic_dammatan: ARABIC DAMMATAN
+ 0x05ed: 0x064D, // XK_Arabic_kasratan: ARABIC KASRATAN
+ 0x05ee: 0x064E, // XK_Arabic_fatha: ARABIC FATHA
+ 0x05ef: 0x064F, // XK_Arabic_damma: ARABIC DAMMA
+ 0x05f0: 0x0650, // XK_Arabic_kasra: ARABIC KASRA
+ 0x05f1: 0x0651, // XK_Arabic_shadda: ARABIC SHADDA
+ 0x05f2: 0x0652, // XK_Arabic_sukun: ARABIC SUKUN
+ 0x1000653: 0x0653, // XK_Arabic_madda_above: ARABIC MADDAH ABOVE
+ 0x1000654: 0x0654, // XK_Arabic_hamza_above: ARABIC HAMZA ABOVE
+ 0x1000655: 0x0655, // XK_Arabic_hamza_below: ARABIC HAMZA BELOW
+ 0x1000698: 0x0698, // XK_Arabic_jeh: ARABIC LETTER JEH
+ 0x10006a4: 0x06A4, // XK_Arabic_veh: ARABIC LETTER VEH
+ 0x10006a9: 0x06A9, // XK_Arabic_keheh: ARABIC LETTER KEHEH
+ 0x10006af: 0x06AF, // XK_Arabic_gaf: ARABIC LETTER GAF
+ 0x10006ba: 0x06BA, // XK_Arabic_noon_ghunna: ARABIC LETTER NOON GHUNNA
+ 0x10006be: 0x06BE, // XK_Arabic_heh_doachashmee: ARABIC LETTER HEH DOACHASHMEE
+ 0x10006cc: 0x06CC, // XK_Farsi_yeh: ARABIC LETTER FARSI YEH
+ 0x10006d2: 0x06D2, // XK_Arabic_yeh_baree: ARABIC LETTER YEH BARREE
+ 0x10006c1: 0x06C1, // XK_Arabic_heh_goal: ARABIC LETTER HEH GOAL
+ 0x1000492: 0x0492, // XK_Cyrillic_GHE_bar: CYRILLIC CAPITAL LETTER GHE WITH STROKE
+ 0x1000493: 0x0493, // XK_Cyrillic_ghe_bar: CYRILLIC SMALL LETTER GHE WITH STROKE
+ 0x1000496: 0x0496, // XK_Cyrillic_ZHE_descender: CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+ 0x1000497: 0x0497, // XK_Cyrillic_zhe_descender: CYRILLIC SMALL LETTER ZHE WITH DESCENDER
+ 0x100049a: 0x049A, // XK_Cyrillic_KA_descender: CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+ 0x100049b: 0x049B, // XK_Cyrillic_ka_descender: CYRILLIC SMALL LETTER KA WITH DESCENDER
+ 0x100049c: 0x049C, // XK_Cyrillic_KA_vertstroke: CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+ 0x100049d: 0x049D, // XK_Cyrillic_ka_vertstroke: CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE
+ 0x10004a2: 0x04A2, // XK_Cyrillic_EN_descender: CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+ 0x10004a3: 0x04A3, // XK_Cyrillic_en_descender: CYRILLIC SMALL LETTER EN WITH DESCENDER
+ 0x10004ae: 0x04AE, // XK_Cyrillic_U_straight: CYRILLIC CAPITAL LETTER STRAIGHT U
+ 0x10004af: 0x04AF, // XK_Cyrillic_u_straight: CYRILLIC SMALL LETTER STRAIGHT U
+ 0x10004b0: 0x04B0, // XK_Cyrillic_U_straight_bar: CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+ 0x10004b1: 0x04B1, // XK_Cyrillic_u_straight_bar: CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+ 0x10004b2: 0x04B2, // XK_Cyrillic_HA_descender: CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+ 0x10004b3: 0x04B3, // XK_Cyrillic_ha_descender: CYRILLIC SMALL LETTER HA WITH DESCENDER
+ 0x10004b6: 0x04B6, // XK_Cyrillic_CHE_descender: CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+ 0x10004b7: 0x04B7, // XK_Cyrillic_che_descender: CYRILLIC SMALL LETTER CHE WITH DESCENDER
+ 0x10004b8: 0x04B8, // XK_Cyrillic_CHE_vertstroke: CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+ 0x10004b9: 0x04B9, // XK_Cyrillic_che_vertstroke: CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE
+ 0x10004ba: 0x04BA, // XK_Cyrillic_SHHA: CYRILLIC CAPITAL LETTER SHHA
+ 0x10004bb: 0x04BB, // XK_Cyrillic_shha: CYRILLIC SMALL LETTER SHHA
+ 0x10004d8: 0x04D8, // XK_Cyrillic_SCHWA: CYRILLIC CAPITAL LETTER SCHWA
+ 0x10004d9: 0x04D9, // XK_Cyrillic_schwa: CYRILLIC SMALL LETTER SCHWA
+ 0x10004e2: 0x04E2, // XK_Cyrillic_I_macron: CYRILLIC CAPITAL LETTER I WITH MACRON
+ 0x10004e3: 0x04E3, // XK_Cyrillic_i_macron: CYRILLIC SMALL LETTER I WITH MACRON
+ 0x10004e8: 0x04E8, // XK_Cyrillic_O_bar: CYRILLIC CAPITAL LETTER BARRED O
+ 0x10004e9: 0x04E9, // XK_Cyrillic_o_bar: CYRILLIC SMALL LETTER BARRED O
+ 0x10004ee: 0x04EE, // XK_Cyrillic_U_macron: CYRILLIC CAPITAL LETTER U WITH MACRON
+ 0x10004ef: 0x04EF, // XK_Cyrillic_u_macron: CYRILLIC SMALL LETTER U WITH MACRON
+ 0x06a1: 0x0452, // XK_Serbian_dje: CYRILLIC SMALL LETTER DJE
+ 0x06a2: 0x0453, // XK_Macedonia_gje: CYRILLIC SMALL LETTER GJE
+ 0x06a3: 0x0451, // XK_Cyrillic_io: CYRILLIC SMALL LETTER IO
+ 0x06a4: 0x0454, // XK_Ukrainian_ie: CYRILLIC SMALL LETTER UKRAINIAN IE
+ 0x06a5: 0x0455, // XK_Macedonia_dse: CYRILLIC SMALL LETTER DZE
+ 0x06a6: 0x0456, // XK_Ukrainian_i: CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ 0x06a7: 0x0457, // XK_Ukrainian_yi: CYRILLIC SMALL LETTER YI
+ 0x06a8: 0x0458, // XK_Cyrillic_je: CYRILLIC SMALL LETTER JE
+ 0x06a9: 0x0459, // XK_Cyrillic_lje: CYRILLIC SMALL LETTER LJE
+ 0x06aa: 0x045A, // XK_Cyrillic_nje: CYRILLIC SMALL LETTER NJE
+ 0x06ab: 0x045B, // XK_Serbian_tshe: CYRILLIC SMALL LETTER TSHE
+ 0x06ac: 0x045C, // XK_Macedonia_kje: CYRILLIC SMALL LETTER KJE
+ 0x06ad: 0x0491, // XK_Ukrainian_ghe_with_upturn: CYRILLIC SMALL LETTER GHE WITH UPTURN
+ 0x06ae: 0x045E, // XK_Byelorussian_shortu: CYRILLIC SMALL LETTER SHORT U
+ 0x06af: 0x045F, // XK_Cyrillic_dzhe: CYRILLIC SMALL LETTER DZHE
+ 0x06b0: 0x2116, // XK_numerosign: NUMERO SIGN
+ 0x06b1: 0x0402, // XK_Serbian_DJE: CYRILLIC CAPITAL LETTER DJE
+ 0x06b2: 0x0403, // XK_Macedonia_GJE: CYRILLIC CAPITAL LETTER GJE
+ 0x06b3: 0x0401, // XK_Cyrillic_IO: CYRILLIC CAPITAL LETTER IO
+ 0x06b4: 0x0404, // XK_Ukrainian_IE: CYRILLIC CAPITAL LETTER UKRAINIAN IE
+ 0x06b5: 0x0405, // XK_Macedonia_DSE: CYRILLIC CAPITAL LETTER DZE
+ 0x06b6: 0x0406, // XK_Ukrainian_I: CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ 0x06b7: 0x0407, // XK_Ukrainian_YI: CYRILLIC CAPITAL LETTER YI
+ 0x06b8: 0x0408, // XK_Cyrillic_JE: CYRILLIC CAPITAL LETTER JE
+ 0x06b9: 0x0409, // XK_Cyrillic_LJE: CYRILLIC CAPITAL LETTER LJE
+ 0x06ba: 0x040A, // XK_Cyrillic_NJE: CYRILLIC CAPITAL LETTER NJE
+ 0x06bb: 0x040B, // XK_Serbian_TSHE: CYRILLIC CAPITAL LETTER TSHE
+ 0x06bc: 0x040C, // XK_Macedonia_KJE: CYRILLIC CAPITAL LETTER KJE
+ 0x06bd: 0x0490, // XK_Ukrainian_GHE_WITH_UPTURN: CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ 0x06be: 0x040E, // XK_Byelorussian_SHORTU: CYRILLIC CAPITAL LETTER SHORT U
+ 0x06bf: 0x040F, // XK_Cyrillic_DZHE: CYRILLIC CAPITAL LETTER DZHE
+ 0x06c0: 0x044E, // XK_Cyrillic_yu: CYRILLIC SMALL LETTER YU
+ 0x06c1: 0x0430, // XK_Cyrillic_a: CYRILLIC SMALL LETTER A
+ 0x06c2: 0x0431, // XK_Cyrillic_be: CYRILLIC SMALL LETTER BE
+ 0x06c3: 0x0446, // XK_Cyrillic_tse: CYRILLIC SMALL LETTER TSE
+ 0x06c4: 0x0434, // XK_Cyrillic_de: CYRILLIC SMALL LETTER DE
+ 0x06c5: 0x0435, // XK_Cyrillic_ie: CYRILLIC SMALL LETTER IE
+ 0x06c6: 0x0444, // XK_Cyrillic_ef: CYRILLIC SMALL LETTER EF
+ 0x06c7: 0x0433, // XK_Cyrillic_ghe: CYRILLIC SMALL LETTER GHE
+ 0x06c8: 0x0445, // XK_Cyrillic_ha: CYRILLIC SMALL LETTER HA
+ 0x06c9: 0x0438, // XK_Cyrillic_i: CYRILLIC SMALL LETTER I
+ 0x06ca: 0x0439, // XK_Cyrillic_shorti: CYRILLIC SMALL LETTER SHORT I
+ 0x06cb: 0x043A, // XK_Cyrillic_ka: CYRILLIC SMALL LETTER KA
+ 0x06cc: 0x043B, // XK_Cyrillic_el: CYRILLIC SMALL LETTER EL
+ 0x06cd: 0x043C, // XK_Cyrillic_em: CYRILLIC SMALL LETTER EM
+ 0x06ce: 0x043D, // XK_Cyrillic_en: CYRILLIC SMALL LETTER EN
+ 0x06cf: 0x043E, // XK_Cyrillic_o: CYRILLIC SMALL LETTER O
+ 0x06d0: 0x043F, // XK_Cyrillic_pe: CYRILLIC SMALL LETTER PE
+ 0x06d1: 0x044F, // XK_Cyrillic_ya: CYRILLIC SMALL LETTER YA
+ 0x06d2: 0x0440, // XK_Cyrillic_er: CYRILLIC SMALL LETTER ER
+ 0x06d3: 0x0441, // XK_Cyrillic_es: CYRILLIC SMALL LETTER ES
+ 0x06d4: 0x0442, // XK_Cyrillic_te: CYRILLIC SMALL LETTER TE
+ 0x06d5: 0x0443, // XK_Cyrillic_u: CYRILLIC SMALL LETTER U
+ 0x06d6: 0x0436, // XK_Cyrillic_zhe: CYRILLIC SMALL LETTER ZHE
+ 0x06d7: 0x0432, // XK_Cyrillic_ve: CYRILLIC SMALL LETTER VE
+ 0x06d8: 0x044C, // XK_Cyrillic_softsign: CYRILLIC SMALL LETTER SOFT SIGN
+ 0x06d9: 0x044B, // XK_Cyrillic_yeru: CYRILLIC SMALL LETTER YERU
+ 0x06da: 0x0437, // XK_Cyrillic_ze: CYRILLIC SMALL LETTER ZE
+ 0x06db: 0x0448, // XK_Cyrillic_sha: CYRILLIC SMALL LETTER SHA
+ 0x06dc: 0x044D, // XK_Cyrillic_e: CYRILLIC SMALL LETTER E
+ 0x06dd: 0x0449, // XK_Cyrillic_shcha: CYRILLIC SMALL LETTER SHCHA
+ 0x06de: 0x0447, // XK_Cyrillic_che: CYRILLIC SMALL LETTER CHE
+ 0x06df: 0x044A, // XK_Cyrillic_hardsign: CYRILLIC SMALL LETTER HARD SIGN
+ 0x06e0: 0x042E, // XK_Cyrillic_YU: CYRILLIC CAPITAL LETTER YU
+ 0x06e1: 0x0410, // XK_Cyrillic_A: CYRILLIC CAPITAL LETTER A
+ 0x06e2: 0x0411, // XK_Cyrillic_BE: CYRILLIC CAPITAL LETTER BE
+ 0x06e3: 0x0426, // XK_Cyrillic_TSE: CYRILLIC CAPITAL LETTER TSE
+ 0x06e4: 0x0414, // XK_Cyrillic_DE: CYRILLIC CAPITAL LETTER DE
+ 0x06e5: 0x0415, // XK_Cyrillic_IE: CYRILLIC CAPITAL LETTER IE
+ 0x06e6: 0x0424, // XK_Cyrillic_EF: CYRILLIC CAPITAL LETTER EF
+ 0x06e7: 0x0413, // XK_Cyrillic_GHE: CYRILLIC CAPITAL LETTER GHE
+ 0x06e8: 0x0425, // XK_Cyrillic_HA: CYRILLIC CAPITAL LETTER HA
+ 0x06e9: 0x0418, // XK_Cyrillic_I: CYRILLIC CAPITAL LETTER I
+ 0x06ea: 0x0419, // XK_Cyrillic_SHORTI: CYRILLIC CAPITAL LETTER SHORT I
+ 0x06eb: 0x041A, // XK_Cyrillic_KA: CYRILLIC CAPITAL LETTER KA
+ 0x06ec: 0x041B, // XK_Cyrillic_EL: CYRILLIC CAPITAL LETTER EL
+ 0x06ed: 0x041C, // XK_Cyrillic_EM: CYRILLIC CAPITAL LETTER EM
+ 0x06ee: 0x041D, // XK_Cyrillic_EN: CYRILLIC CAPITAL LETTER EN
+ 0x06ef: 0x041E, // XK_Cyrillic_O: CYRILLIC CAPITAL LETTER O
+ 0x06f0: 0x041F, // XK_Cyrillic_PE: CYRILLIC CAPITAL LETTER PE
+ 0x06f1: 0x042F, // XK_Cyrillic_YA: CYRILLIC CAPITAL LETTER YA
+ 0x06f2: 0x0420, // XK_Cyrillic_ER: CYRILLIC CAPITAL LETTER ER
+ 0x06f3: 0x0421, // XK_Cyrillic_ES: CYRILLIC CAPITAL LETTER ES
+ 0x06f4: 0x0422, // XK_Cyrillic_TE: CYRILLIC CAPITAL LETTER TE
+ 0x06f5: 0x0423, // XK_Cyrillic_U: CYRILLIC CAPITAL LETTER U
+ 0x06f6: 0x0416, // XK_Cyrillic_ZHE: CYRILLIC CAPITAL LETTER ZHE
+ 0x06f7: 0x0412, // XK_Cyrillic_VE: CYRILLIC CAPITAL LETTER VE
+ 0x06f8: 0x042C, // XK_Cyrillic_SOFTSIGN: CYRILLIC CAPITAL LETTER SOFT SIGN
+ 0x06f9: 0x042B, // XK_Cyrillic_YERU: CYRILLIC CAPITAL LETTER YERU
+ 0x06fa: 0x0417, // XK_Cyrillic_ZE: CYRILLIC CAPITAL LETTER ZE
+ 0x06fb: 0x0428, // XK_Cyrillic_SHA: CYRILLIC CAPITAL LETTER SHA
+ 0x06fc: 0x042D, // XK_Cyrillic_E: CYRILLIC CAPITAL LETTER E
+ 0x06fd: 0x0429, // XK_Cyrillic_SHCHA: CYRILLIC CAPITAL LETTER SHCHA
+ 0x06fe: 0x0427, // XK_Cyrillic_CHE: CYRILLIC CAPITAL LETTER CHE
+ 0x06ff: 0x042A, // XK_Cyrillic_HARDSIGN: CYRILLIC CAPITAL LETTER HARD SIGN
+ 0x07a1: 0x0386, // XK_Greek_ALPHAaccent: GREEK CAPITAL LETTER ALPHA WITH TONOS
+ 0x07a2: 0x0388, // XK_Greek_EPSILONaccent: GREEK CAPITAL LETTER EPSILON WITH TONOS
+ 0x07a3: 0x0389, // XK_Greek_ETAaccent: GREEK CAPITAL LETTER ETA WITH TONOS
+ 0x07a4: 0x038A, // XK_Greek_IOTAaccent: GREEK CAPITAL LETTER IOTA WITH TONOS
+ 0x07a5: 0x03AA, // XK_Greek_IOTAdieresis: GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+ 0x07a7: 0x038C, // XK_Greek_OMICRONaccent: GREEK CAPITAL LETTER OMICRON WITH TONOS
+ 0x07a8: 0x038E, // XK_Greek_UPSILONaccent: GREEK CAPITAL LETTER UPSILON WITH TONOS
+ 0x07a9: 0x03AB, // XK_Greek_UPSILONdieresis: GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+ 0x07ab: 0x038F, // XK_Greek_OMEGAaccent: GREEK CAPITAL LETTER OMEGA WITH TONOS
+ 0x07ae: 0x0385, // XK_Greek_accentdieresis: GREEK DIALYTIKA TONOS
+ 0x07af: 0x2015, // XK_Greek_horizbar: HORIZONTAL BAR
+ 0x07b1: 0x03AC, // XK_Greek_alphaaccent: GREEK SMALL LETTER ALPHA WITH TONOS
+ 0x07b2: 0x03AD, // XK_Greek_epsilonaccent: GREEK SMALL LETTER EPSILON WITH TONOS
+ 0x07b3: 0x03AE, // XK_Greek_etaaccent: GREEK SMALL LETTER ETA WITH TONOS
+ 0x07b4: 0x03AF, // XK_Greek_iotaaccent: GREEK SMALL LETTER IOTA WITH TONOS
+ 0x07b5: 0x03CA, // XK_Greek_iotadieresis: GREEK SMALL LETTER IOTA WITH DIALYTIKA
+ 0x07b6: 0x0390, // XK_Greek_iotaaccentdieresis: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+ 0x07b7: 0x03CC, // XK_Greek_omicronaccent: GREEK SMALL LETTER OMICRON WITH TONOS
+ 0x07b8: 0x03CD, // XK_Greek_upsilonaccent: GREEK SMALL LETTER UPSILON WITH TONOS
+ 0x07b9: 0x03CB, // XK_Greek_upsilondieresis: GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+ 0x07ba: 0x03B0, // XK_Greek_upsilonaccentdieresis: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+ 0x07bb: 0x03CE, // XK_Greek_omegaaccent: GREEK SMALL LETTER OMEGA WITH TONOS
+ 0x07c1: 0x0391, // XK_Greek_ALPHA: GREEK CAPITAL LETTER ALPHA
+ 0x07c2: 0x0392, // XK_Greek_BETA: GREEK CAPITAL LETTER BETA
+ 0x07c3: 0x0393, // XK_Greek_GAMMA: GREEK CAPITAL LETTER GAMMA
+ 0x07c4: 0x0394, // XK_Greek_DELTA: GREEK CAPITAL LETTER DELTA
+ 0x07c5: 0x0395, // XK_Greek_EPSILON: GREEK CAPITAL LETTER EPSILON
+ 0x07c6: 0x0396, // XK_Greek_ZETA: GREEK CAPITAL LETTER ZETA
+ 0x07c7: 0x0397, // XK_Greek_ETA: GREEK CAPITAL LETTER ETA
+ 0x07c8: 0x0398, // XK_Greek_THETA: GREEK CAPITAL LETTER THETA
+ 0x07c9: 0x0399, // XK_Greek_IOTA: GREEK CAPITAL LETTER IOTA
+ 0x07ca: 0x039A, // XK_Greek_KAPPA: GREEK CAPITAL LETTER KAPPA
+ 0x07cb: 0x039B, // XK_Greek_LAMDA: GREEK CAPITAL LETTER LAMDA
+ 0x07cc: 0x039C, // XK_Greek_MU: GREEK CAPITAL LETTER MU
+ 0x07cd: 0x039D, // XK_Greek_NU: GREEK CAPITAL LETTER NU
+ 0x07ce: 0x039E, // XK_Greek_XI: GREEK CAPITAL LETTER XI
+ 0x07cf: 0x039F, // XK_Greek_OMICRON: GREEK CAPITAL LETTER OMICRON
+ 0x07d0: 0x03A0, // XK_Greek_PI: GREEK CAPITAL LETTER PI
+ 0x07d1: 0x03A1, // XK_Greek_RHO: GREEK CAPITAL LETTER RHO
+ 0x07d2: 0x03A3, // XK_Greek_SIGMA: GREEK CAPITAL LETTER SIGMA
+ 0x07d4: 0x03A4, // XK_Greek_TAU: GREEK CAPITAL LETTER TAU
+ 0x07d5: 0x03A5, // XK_Greek_UPSILON: GREEK CAPITAL LETTER UPSILON
+ 0x07d6: 0x03A6, // XK_Greek_PHI: GREEK CAPITAL LETTER PHI
+ 0x07d7: 0x03A7, // XK_Greek_CHI: GREEK CAPITAL LETTER CHI
+ 0x07d8: 0x03A8, // XK_Greek_PSI: GREEK CAPITAL LETTER PSI
+ 0x07d9: 0x03A9, // XK_Greek_OMEGA: GREEK CAPITAL LETTER OMEGA
+ 0x07e1: 0x03B1, // XK_Greek_alpha: GREEK SMALL LETTER ALPHA
+ 0x07e2: 0x03B2, // XK_Greek_beta: GREEK SMALL LETTER BETA
+ 0x07e3: 0x03B3, // XK_Greek_gamma: GREEK SMALL LETTER GAMMA
+ 0x07e4: 0x03B4, // XK_Greek_delta: GREEK SMALL LETTER DELTA
+ 0x07e5: 0x03B5, // XK_Greek_epsilon: GREEK SMALL LETTER EPSILON
+ 0x07e6: 0x03B6, // XK_Greek_zeta: GREEK SMALL LETTER ZETA
+ 0x07e7: 0x03B7, // XK_Greek_eta: GREEK SMALL LETTER ETA
+ 0x07e8: 0x03B8, // XK_Greek_theta: GREEK SMALL LETTER THETA
+ 0x07e9: 0x03B9, // XK_Greek_iota: GREEK SMALL LETTER IOTA
+ 0x07ea: 0x03BA, // XK_Greek_kappa: GREEK SMALL LETTER KAPPA
+ 0x07eb: 0x03BB, // XK_Greek_lamda: GREEK SMALL LETTER LAMDA
+ 0x07ec: 0x03BC, // XK_Greek_mu: GREEK SMALL LETTER MU
+ 0x07ed: 0x03BD, // XK_Greek_nu: GREEK SMALL LETTER NU
+ 0x07ee: 0x03BE, // XK_Greek_xi: GREEK SMALL LETTER XI
+ 0x07ef: 0x03BF, // XK_Greek_omicron: GREEK SMALL LETTER OMICRON
+ 0x07f0: 0x03C0, // XK_Greek_pi: GREEK SMALL LETTER PI
+ 0x07f1: 0x03C1, // XK_Greek_rho: GREEK SMALL LETTER RHO
+ 0x07f2: 0x03C3, // XK_Greek_sigma: GREEK SMALL LETTER SIGMA
+ 0x07f3: 0x03C2, // XK_Greek_finalsmallsigma: GREEK SMALL LETTER FINAL SIGMA
+ 0x07f4: 0x03C4, // XK_Greek_tau: GREEK SMALL LETTER TAU
+ 0x07f5: 0x03C5, // XK_Greek_upsilon: GREEK SMALL LETTER UPSILON
+ 0x07f6: 0x03C6, // XK_Greek_phi: GREEK SMALL LETTER PHI
+ 0x07f7: 0x03C7, // XK_Greek_chi: GREEK SMALL LETTER CHI
+ 0x07f8: 0x03C8, // XK_Greek_psi: GREEK SMALL LETTER PSI
+ 0x07f9: 0x03C9, // XK_Greek_omega: GREEK SMALL LETTER OMEGA
+ 0x08a1: 0x23B7, // XK_leftradical: RADICAL SYMBOL BOTTOM
+ 0x08a2: 0x250C, // XK_topleftradical: BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0x08a3: 0x2500, // XK_horizconnector: BOX DRAWINGS LIGHT HORIZONTAL
+ 0x08a4: 0x2320, // XK_topintegral: TOP HALF INTEGRAL
+ 0x08a5: 0x2321, // XK_botintegral: BOTTOM HALF INTEGRAL
+ 0x08a6: 0x2502, // XK_vertconnector: BOX DRAWINGS LIGHT VERTICAL
+ 0x08a7: 0x23A1, // XK_topleftsqbracket: LEFT SQUARE BRACKET UPPER CORNER
+ 0x08a8: 0x23A3, // XK_botleftsqbracket: LEFT SQUARE BRACKET LOWER CORNER
+ 0x08a9: 0x23A4, // XK_toprightsqbracket: RIGHT SQUARE BRACKET UPPER CORNER
+ 0x08aa: 0x23A6, // XK_botrightsqbracket: RIGHT SQUARE BRACKET LOWER CORNER
+ 0x08ab: 0x239B, // XK_topleftparens: LEFT PARENTHESIS UPPER HOOK
+ 0x08ac: 0x239D, // XK_botleftparens: LEFT PARENTHESIS LOWER HOOK
+ 0x08ad: 0x239E, // XK_toprightparens: RIGHT PARENTHESIS UPPER HOOK
+ 0x08ae: 0x23A0, // XK_botrightparens: RIGHT PARENTHESIS LOWER HOOK
+ 0x08af: 0x23A8, // XK_leftmiddlecurlybrace: LEFT CURLY BRACKET MIDDLE PIECE
+ 0x08b0: 0x23AC, // XK_rightmiddlecurlybrace: RIGHT CURLY BRACKET MIDDLE PIECE
+ 0x08bc: 0x2264, // XK_lessthanequal: LESS-THAN OR EQUAL TO
+ 0x08bd: 0x2260, // XK_notequal: NOT EQUAL TO
+ 0x08be: 0x2265, // XK_greaterthanequal: GREATER-THAN OR EQUAL TO
+ 0x08bf: 0x222B, // XK_integral: INTEGRAL
+ 0x08c0: 0x2234, // XK_therefore: THEREFORE
+ 0x08c1: 0x221D, // XK_variation: PROPORTIONAL TO
+ 0x08c2: 0x221E, // XK_infinity: INFINITY
+ 0x08c5: 0x2207, // XK_nabla: NABLA
+ 0x08c8: 0x223C, // XK_approximate: TILDE OPERATOR
+ 0x08c9: 0x2243, // XK_similarequal: ASYMPTOTICALLY EQUAL TO
+ 0x08cd: 0x21D4, // XK_ifonlyif: LEFT RIGHT DOUBLE ARROW
+ 0x08ce: 0x21D2, // XK_implies: RIGHTWARDS DOUBLE ARROW
+ 0x08cf: 0x2261, // XK_identical: IDENTICAL TO
+ 0x08d6: 0x221A, // XK_radical: SQUARE ROOT
+ 0x08da: 0x2282, // XK_includedin: SUBSET OF
+ 0x08db: 0x2283, // XK_includes: SUPERSET OF
+ 0x08dc: 0x2229, // XK_intersection: INTERSECTION
+ 0x08dd: 0x222A, // XK_union: UNION
+ 0x08de: 0x2227, // XK_logicaland: LOGICAL AND
+ 0x08df: 0x2228, // XK_logicalor: LOGICAL OR
+ 0x08ef: 0x2202, // XK_partialderivative: PARTIAL DIFFERENTIAL
+ 0x08f6: 0x0192, // XK_function: LATIN SMALL LETTER F WITH HOOK
+ 0x08fb: 0x2190, // XK_leftarrow: LEFTWARDS ARROW
+ 0x08fc: 0x2191, // XK_uparrow: UPWARDS ARROW
+ 0x08fd: 0x2192, // XK_rightarrow: RIGHTWARDS ARROW
+ 0x08fe: 0x2193, // XK_downarrow: DOWNWARDS ARROW
+ 0x09e0: 0x25C6, // XK_soliddiamond: BLACK DIAMOND
+ 0x09e1: 0x2592, // XK_checkerboard: MEDIUM SHADE
+ 0x09e2: 0x2409, // XK_ht: SYMBOL FOR HORIZONTAL TABULATION
+ 0x09e3: 0x240C, // XK_ff: SYMBOL FOR FORM FEED
+ 0x09e4: 0x240D, // XK_cr: SYMBOL FOR CARRIAGE RETURN
+ 0x09e5: 0x240A, // XK_lf: SYMBOL FOR LINE FEED
+ 0x09e8: 0x2424, // XK_nl: SYMBOL FOR NEWLINE
+ 0x09e9: 0x240B, // XK_vt: SYMBOL FOR VERTICAL TABULATION
+ 0x09ea: 0x2518, // XK_lowrightcorner: BOX DRAWINGS LIGHT UP AND LEFT
+ 0x09eb: 0x2510, // XK_uprightcorner: BOX DRAWINGS LIGHT DOWN AND LEFT
+ 0x09ec: 0x250C, // XK_upleftcorner: BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0x09ed: 0x2514, // XK_lowleftcorner: BOX DRAWINGS LIGHT UP AND RIGHT
+ 0x09ee: 0x253C, // XK_crossinglines: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 0x09ef: 0x23BA, // XK_horizlinescan1: HORIZONTAL SCAN LINE-1
+ 0x09f0: 0x23BB, // XK_horizlinescan3: HORIZONTAL SCAN LINE-3
+ 0x09f1: 0x2500, // XK_horizlinescan5: BOX DRAWINGS LIGHT HORIZONTAL
+ 0x09f2: 0x23BC, // XK_horizlinescan7: HORIZONTAL SCAN LINE-7
+ 0x09f3: 0x23BD, // XK_horizlinescan9: HORIZONTAL SCAN LINE-9
+ 0x09f4: 0x251C, // XK_leftt: BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 0x09f5: 0x2524, // XK_rightt: BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 0x09f6: 0x2534, // XK_bott: BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 0x09f7: 0x252C, // XK_topt: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 0x09f8: 0x2502, // XK_vertbar: BOX DRAWINGS LIGHT VERTICAL
+ 0x0aa1: 0x2003, // XK_emspace: EM SPACE
+ 0x0aa2: 0x2002, // XK_enspace: EN SPACE
+ 0x0aa3: 0x2004, // XK_em3space: THREE-PER-EM SPACE
+ 0x0aa4: 0x2005, // XK_em4space: FOUR-PER-EM SPACE
+ 0x0aa5: 0x2007, // XK_digitspace: FIGURE SPACE
+ 0x0aa6: 0x2008, // XK_punctspace: PUNCTUATION SPACE
+ 0x0aa7: 0x2009, // XK_thinspace: THIN SPACE
+ 0x0aa8: 0x200A, // XK_hairspace: HAIR SPACE
+ 0x0aa9: 0x2014, // XK_emdash: EM DASH
+ 0x0aaa: 0x2013, // XK_endash: EN DASH
+ 0x0aac: 0x2423, // XK_signifblank: OPEN BOX
+ 0x0aae: 0x2026, // XK_ellipsis: HORIZONTAL ELLIPSIS
+ 0x0aaf: 0x2025, // XK_doubbaselinedot: TWO DOT LEADER
+ 0x0ab0: 0x2153, // XK_onethird: VULGAR FRACTION ONE THIRD
+ 0x0ab1: 0x2154, // XK_twothirds: VULGAR FRACTION TWO THIRDS
+ 0x0ab2: 0x2155, // XK_onefifth: VULGAR FRACTION ONE FIFTH
+ 0x0ab3: 0x2156, // XK_twofifths: VULGAR FRACTION TWO FIFTHS
+ 0x0ab4: 0x2157, // XK_threefifths: VULGAR FRACTION THREE FIFTHS
+ 0x0ab5: 0x2158, // XK_fourfifths: VULGAR FRACTION FOUR FIFTHS
+ 0x0ab6: 0x2159, // XK_onesixth: VULGAR FRACTION ONE SIXTH
+ 0x0ab7: 0x215A, // XK_fivesixths: VULGAR FRACTION FIVE SIXTHS
+ 0x0ab8: 0x2105, // XK_careof: CARE OF
+ 0x0abb: 0x2012, // XK_figdash: FIGURE DASH
+ 0x0abc: 0x27E8, // XK_leftanglebracket: MATHEMATICAL LEFT ANGLE BRACKET
+ 0x0abd: 0x002E, // XK_decimalpoint: FULL STOP
+ 0x0abe: 0x27E9, // XK_rightanglebracket: MATHEMATICAL RIGHT ANGLE BRACKET
+ 0x0ac3: 0x215B, // XK_oneeighth: VULGAR FRACTION ONE EIGHTH
+ 0x0ac4: 0x215C, // XK_threeeighths: VULGAR FRACTION THREE EIGHTHS
+ 0x0ac5: 0x215D, // XK_fiveeighths: VULGAR FRACTION FIVE EIGHTHS
+ 0x0ac6: 0x215E, // XK_seveneighths: VULGAR FRACTION SEVEN EIGHTHS
+ 0x0ac9: 0x2122, // XK_trademark: TRADE MARK SIGN
+ 0x0aca: 0x2613, // XK_signaturemark: SALTIRE
+ 0x0acc: 0x25C1, // XK_leftopentriangle: WHITE LEFT-POINTING TRIANGLE
+ 0x0acd: 0x25B7, // XK_rightopentriangle: WHITE RIGHT-POINTING TRIANGLE
+ 0x0ace: 0x25CB, // XK_emopencircle: WHITE CIRCLE
+ 0x0acf: 0x25AF, // XK_emopenrectangle: WHITE VERTICAL RECTANGLE
+ 0x0ad0: 0x2018, // XK_leftsinglequotemark: LEFT SINGLE QUOTATION MARK
+ 0x0ad1: 0x2019, // XK_rightsinglequotemark: RIGHT SINGLE QUOTATION MARK
+ 0x0ad2: 0x201C, // XK_leftdoublequotemark: LEFT DOUBLE QUOTATION MARK
+ 0x0ad3: 0x201D, // XK_rightdoublequotemark: RIGHT DOUBLE QUOTATION MARK
+ 0x0ad4: 0x211E, // XK_prescription: PRESCRIPTION TAKE
+ 0x0ad5: 0x2030, // XK_permille: PER MILLE SIGN
+ 0x0ad6: 0x2032, // XK_minutes: PRIME
+ 0x0ad7: 0x2033, // XK_seconds: DOUBLE PRIME
+ 0x0ad9: 0x271D, // XK_latincross: LATIN CROSS
+ 0x0adb: 0x25AC, // XK_filledrectbullet: BLACK RECTANGLE
+ 0x0adc: 0x25C0, // XK_filledlefttribullet: BLACK LEFT-POINTING TRIANGLE
+ 0x0add: 0x25B6, // XK_filledrighttribullet: BLACK RIGHT-POINTING TRIANGLE
+ 0x0ade: 0x25CF, // XK_emfilledcircle: BLACK CIRCLE
+ 0x0adf: 0x25AE, // XK_emfilledrect: BLACK VERTICAL RECTANGLE
+ 0x0ae0: 0x25E6, // XK_enopencircbullet: WHITE BULLET
+ 0x0ae1: 0x25AB, // XK_enopensquarebullet: WHITE SMALL SQUARE
+ 0x0ae2: 0x25AD, // XK_openrectbullet: WHITE RECTANGLE
+ 0x0ae3: 0x25B3, // XK_opentribulletup: WHITE UP-POINTING TRIANGLE
+ 0x0ae4: 0x25BD, // XK_opentribulletdown: WHITE DOWN-POINTING TRIANGLE
+ 0x0ae5: 0x2606, // XK_openstar: WHITE STAR
+ 0x0ae6: 0x2022, // XK_enfilledcircbullet: BULLET
+ 0x0ae7: 0x25AA, // XK_enfilledsqbullet: BLACK SMALL SQUARE
+ 0x0ae8: 0x25B2, // XK_filledtribulletup: BLACK UP-POINTING TRIANGLE
+ 0x0ae9: 0x25BC, // XK_filledtribulletdown: BLACK DOWN-POINTING TRIANGLE
+ 0x0aea: 0x261C, // XK_leftpointer: WHITE LEFT POINTING INDEX
+ 0x0aeb: 0x261E, // XK_rightpointer: WHITE RIGHT POINTING INDEX
+ 0x0aec: 0x2663, // XK_club: BLACK CLUB SUIT
+ 0x0aed: 0x2666, // XK_diamond: BLACK DIAMOND SUIT
+ 0x0aee: 0x2665, // XK_heart: BLACK HEART SUIT
+ 0x0af0: 0x2720, // XK_maltesecross: MALTESE CROSS
+ 0x0af1: 0x2020, // XK_dagger: DAGGER
+ 0x0af2: 0x2021, // XK_doubledagger: DOUBLE DAGGER
+ 0x0af3: 0x2713, // XK_checkmark: CHECK MARK
+ 0x0af4: 0x2717, // XK_ballotcross: BALLOT X
+ 0x0af5: 0x266F, // XK_musicalsharp: MUSIC SHARP SIGN
+ 0x0af6: 0x266D, // XK_musicalflat: MUSIC FLAT SIGN
+ 0x0af7: 0x2642, // XK_malesymbol: MALE SIGN
+ 0x0af8: 0x2640, // XK_femalesymbol: FEMALE SIGN
+ 0x0af9: 0x260E, // XK_telephone: BLACK TELEPHONE
+ 0x0afa: 0x2315, // XK_telephonerecorder: TELEPHONE RECORDER
+ 0x0afb: 0x2117, // XK_phonographcopyright: SOUND RECORDING COPYRIGHT
+ 0x0afc: 0x2038, // XK_caret: CARET
+ 0x0afd: 0x201A, // XK_singlelowquotemark: SINGLE LOW-9 QUOTATION MARK
+ 0x0afe: 0x201E, // XK_doublelowquotemark: DOUBLE LOW-9 QUOTATION MARK
+ 0x0ba3: 0x003C, // XK_leftcaret: LESS-THAN SIGN
+ 0x0ba6: 0x003E, // XK_rightcaret: GREATER-THAN SIGN
+ 0x0ba8: 0x2228, // XK_downcaret: LOGICAL OR
+ 0x0ba9: 0x2227, // XK_upcaret: LOGICAL AND
+ 0x0bc0: 0x00AF, // XK_overbar: MACRON
+ 0x0bc2: 0x22A4, // XK_downtack: DOWN TACK
+ 0x0bc3: 0x2229, // XK_upshoe: INTERSECTION
+ 0x0bc4: 0x230A, // XK_downstile: LEFT FLOOR
+ 0x0bc6: 0x005F, // XK_underbar: LOW LINE
+ 0x0bca: 0x2218, // XK_jot: RING OPERATOR
+ 0x0bcc: 0x2395, // XK_quad: APL FUNCTIONAL SYMBOL QUAD
+ 0x0bce: 0x22A5, // XK_uptack: UP TACK
+ 0x0bcf: 0x25CB, // XK_circle: WHITE CIRCLE
+ 0x0bd3: 0x2308, // XK_upstile: LEFT CEILING
+ 0x0bd6: 0x222A, // XK_downshoe: UNION
+ 0x0bd8: 0x2283, // XK_rightshoe: SUPERSET OF
+ 0x0bda: 0x2282, // XK_leftshoe: SUBSET OF
+ 0x0bdc: 0x22A3, // XK_lefttack: LEFT TACK
+ 0x0bfc: 0x22A2, // XK_righttack: RIGHT TACK
+ 0x0cdf: 0x2017, // XK_hebrew_doublelowline: DOUBLE LOW LINE
+ 0x0ce0: 0x05D0, // XK_hebrew_aleph: HEBREW LETTER ALEF
+ 0x0ce1: 0x05D1, // XK_hebrew_bet: HEBREW LETTER BET
+ 0x0ce2: 0x05D2, // XK_hebrew_gimel: HEBREW LETTER GIMEL
+ 0x0ce3: 0x05D3, // XK_hebrew_dalet: HEBREW LETTER DALET
+ 0x0ce4: 0x05D4, // XK_hebrew_he: HEBREW LETTER HE
+ 0x0ce5: 0x05D5, // XK_hebrew_waw: HEBREW LETTER VAV
+ 0x0ce6: 0x05D6, // XK_hebrew_zain: HEBREW LETTER ZAYIN
+ 0x0ce7: 0x05D7, // XK_hebrew_chet: HEBREW LETTER HET
+ 0x0ce8: 0x05D8, // XK_hebrew_tet: HEBREW LETTER TET
+ 0x0ce9: 0x05D9, // XK_hebrew_yod: HEBREW LETTER YOD
+ 0x0cea: 0x05DA, // XK_hebrew_finalkaph: HEBREW LETTER FINAL KAF
+ 0x0ceb: 0x05DB, // XK_hebrew_kaph: HEBREW LETTER KAF
+ 0x0cec: 0x05DC, // XK_hebrew_lamed: HEBREW LETTER LAMED
+ 0x0ced: 0x05DD, // XK_hebrew_finalmem: HEBREW LETTER FINAL MEM
+ 0x0cee: 0x05DE, // XK_hebrew_mem: HEBREW LETTER MEM
+ 0x0cef: 0x05DF, // XK_hebrew_finalnun: HEBREW LETTER FINAL NUN
+ 0x0cf0: 0x05E0, // XK_hebrew_nun: HEBREW LETTER NUN
+ 0x0cf1: 0x05E1, // XK_hebrew_samech: HEBREW LETTER SAMEKH
+ 0x0cf2: 0x05E2, // XK_hebrew_ayin: HEBREW LETTER AYIN
+ 0x0cf3: 0x05E3, // XK_hebrew_finalpe: HEBREW LETTER FINAL PE
+ 0x0cf4: 0x05E4, // XK_hebrew_pe: HEBREW LETTER PE
+ 0x0cf5: 0x05E5, // XK_hebrew_finalzade: HEBREW LETTER FINAL TSADI
+ 0x0cf6: 0x05E6, // XK_hebrew_zade: HEBREW LETTER TSADI
+ 0x0cf7: 0x05E7, // XK_hebrew_qoph: HEBREW LETTER QOF
+ 0x0cf8: 0x05E8, // XK_hebrew_resh: HEBREW LETTER RESH
+ 0x0cf9: 0x05E9, // XK_hebrew_shin: HEBREW LETTER SHIN
+ 0x0cfa: 0x05EA, // XK_hebrew_taw: HEBREW LETTER TAV
+ 0x0da1: 0x0E01, // XK_Thai_kokai: THAI CHARACTER KO KAI
+ 0x0da2: 0x0E02, // XK_Thai_khokhai: THAI CHARACTER KHO KHAI
+ 0x0da3: 0x0E03, // XK_Thai_khokhuat: THAI CHARACTER KHO KHUAT
+ 0x0da4: 0x0E04, // XK_Thai_khokhwai: THAI CHARACTER KHO KHWAI
+ 0x0da5: 0x0E05, // XK_Thai_khokhon: THAI CHARACTER KHO KHON
+ 0x0da6: 0x0E06, // XK_Thai_khorakhang: THAI CHARACTER KHO RAKHANG
+ 0x0da7: 0x0E07, // XK_Thai_ngongu: THAI CHARACTER NGO NGU
+ 0x0da8: 0x0E08, // XK_Thai_chochan: THAI CHARACTER CHO CHAN
+ 0x0da9: 0x0E09, // XK_Thai_choching: THAI CHARACTER CHO CHING
+ 0x0daa: 0x0E0A, // XK_Thai_chochang: THAI CHARACTER CHO CHANG
+ 0x0dab: 0x0E0B, // XK_Thai_soso: THAI CHARACTER SO SO
+ 0x0dac: 0x0E0C, // XK_Thai_chochoe: THAI CHARACTER CHO CHOE
+ 0x0dad: 0x0E0D, // XK_Thai_yoying: THAI CHARACTER YO YING
+ 0x0dae: 0x0E0E, // XK_Thai_dochada: THAI CHARACTER DO CHADA
+ 0x0daf: 0x0E0F, // XK_Thai_topatak: THAI CHARACTER TO PATAK
+ 0x0db0: 0x0E10, // XK_Thai_thothan: THAI CHARACTER THO THAN
+ 0x0db1: 0x0E11, // XK_Thai_thonangmontho: THAI CHARACTER THO NANGMONTHO
+ 0x0db2: 0x0E12, // XK_Thai_thophuthao: THAI CHARACTER THO PHUTHAO
+ 0x0db3: 0x0E13, // XK_Thai_nonen: THAI CHARACTER NO NEN
+ 0x0db4: 0x0E14, // XK_Thai_dodek: THAI CHARACTER DO DEK
+ 0x0db5: 0x0E15, // XK_Thai_totao: THAI CHARACTER TO TAO
+ 0x0db6: 0x0E16, // XK_Thai_thothung: THAI CHARACTER THO THUNG
+ 0x0db7: 0x0E17, // XK_Thai_thothahan: THAI CHARACTER THO THAHAN
+ 0x0db8: 0x0E18, // XK_Thai_thothong: THAI CHARACTER THO THONG
+ 0x0db9: 0x0E19, // XK_Thai_nonu: THAI CHARACTER NO NU
+ 0x0dba: 0x0E1A, // XK_Thai_bobaimai: THAI CHARACTER BO BAIMAI
+ 0x0dbb: 0x0E1B, // XK_Thai_popla: THAI CHARACTER PO PLA
+ 0x0dbc: 0x0E1C, // XK_Thai_phophung: THAI CHARACTER PHO PHUNG
+ 0x0dbd: 0x0E1D, // XK_Thai_fofa: THAI CHARACTER FO FA
+ 0x0dbe: 0x0E1E, // XK_Thai_phophan: THAI CHARACTER PHO PHAN
+ 0x0dbf: 0x0E1F, // XK_Thai_fofan: THAI CHARACTER FO FAN
+ 0x0dc0: 0x0E20, // XK_Thai_phosamphao: THAI CHARACTER PHO SAMPHAO
+ 0x0dc1: 0x0E21, // XK_Thai_moma: THAI CHARACTER MO MA
+ 0x0dc2: 0x0E22, // XK_Thai_yoyak: THAI CHARACTER YO YAK
+ 0x0dc3: 0x0E23, // XK_Thai_rorua: THAI CHARACTER RO RUA
+ 0x0dc4: 0x0E24, // XK_Thai_ru: THAI CHARACTER RU
+ 0x0dc5: 0x0E25, // XK_Thai_loling: THAI CHARACTER LO LING
+ 0x0dc6: 0x0E26, // XK_Thai_lu: THAI CHARACTER LU
+ 0x0dc7: 0x0E27, // XK_Thai_wowaen: THAI CHARACTER WO WAEN
+ 0x0dc8: 0x0E28, // XK_Thai_sosala: THAI CHARACTER SO SALA
+ 0x0dc9: 0x0E29, // XK_Thai_sorusi: THAI CHARACTER SO RUSI
+ 0x0dca: 0x0E2A, // XK_Thai_sosua: THAI CHARACTER SO SUA
+ 0x0dcb: 0x0E2B, // XK_Thai_hohip: THAI CHARACTER HO HIP
+ 0x0dcc: 0x0E2C, // XK_Thai_lochula: THAI CHARACTER LO CHULA
+ 0x0dcd: 0x0E2D, // XK_Thai_oang: THAI CHARACTER O ANG
+ 0x0dce: 0x0E2E, // XK_Thai_honokhuk: THAI CHARACTER HO NOKHUK
+ 0x0dcf: 0x0E2F, // XK_Thai_paiyannoi: THAI CHARACTER PAIYANNOI
+ 0x0dd0: 0x0E30, // XK_Thai_saraa: THAI CHARACTER SARA A
+ 0x0dd1: 0x0E31, // XK_Thai_maihanakat: THAI CHARACTER MAI HAN-AKAT
+ 0x0dd2: 0x0E32, // XK_Thai_saraaa: THAI CHARACTER SARA AA
+ 0x0dd3: 0x0E33, // XK_Thai_saraam: THAI CHARACTER SARA AM
+ 0x0dd4: 0x0E34, // XK_Thai_sarai: THAI CHARACTER SARA I
+ 0x0dd5: 0x0E35, // XK_Thai_saraii: THAI CHARACTER SARA II
+ 0x0dd6: 0x0E36, // XK_Thai_saraue: THAI CHARACTER SARA UE
+ 0x0dd7: 0x0E37, // XK_Thai_sarauee: THAI CHARACTER SARA UEE
+ 0x0dd8: 0x0E38, // XK_Thai_sarau: THAI CHARACTER SARA U
+ 0x0dd9: 0x0E39, // XK_Thai_sarauu: THAI CHARACTER SARA UU
+ 0x0dda: 0x0E3A, // XK_Thai_phinthu: THAI CHARACTER PHINTHU
+ 0x0ddf: 0x0E3F, // XK_Thai_baht: THAI CURRENCY SYMBOL BAHT
+ 0x0de0: 0x0E40, // XK_Thai_sarae: THAI CHARACTER SARA E
+ 0x0de1: 0x0E41, // XK_Thai_saraae: THAI CHARACTER SARA AE
+ 0x0de2: 0x0E42, // XK_Thai_sarao: THAI CHARACTER SARA O
+ 0x0de3: 0x0E43, // XK_Thai_saraaimaimuan: THAI CHARACTER SARA AI MAIMUAN
+ 0x0de4: 0x0E44, // XK_Thai_saraaimaimalai: THAI CHARACTER SARA AI MAIMALAI
+ 0x0de5: 0x0E45, // XK_Thai_lakkhangyao: THAI CHARACTER LAKKHANGYAO
+ 0x0de6: 0x0E46, // XK_Thai_maiyamok: THAI CHARACTER MAIYAMOK
+ 0x0de7: 0x0E47, // XK_Thai_maitaikhu: THAI CHARACTER MAITAIKHU
+ 0x0de8: 0x0E48, // XK_Thai_maiek: THAI CHARACTER MAI EK
+ 0x0de9: 0x0E49, // XK_Thai_maitho: THAI CHARACTER MAI THO
+ 0x0dea: 0x0E4A, // XK_Thai_maitri: THAI CHARACTER MAI TRI
+ 0x0deb: 0x0E4B, // XK_Thai_maichattawa: THAI CHARACTER MAI CHATTAWA
+ 0x0dec: 0x0E4C, // XK_Thai_thanthakhat: THAI CHARACTER THANTHAKHAT
+ 0x0ded: 0x0E4D, // XK_Thai_nikhahit: THAI CHARACTER NIKHAHIT
+ 0x0df0: 0x0E50, // XK_Thai_leksun: THAI DIGIT ZERO
+ 0x0df1: 0x0E51, // XK_Thai_leknung: THAI DIGIT ONE
+ 0x0df2: 0x0E52, // XK_Thai_leksong: THAI DIGIT TWO
+ 0x0df3: 0x0E53, // XK_Thai_leksam: THAI DIGIT THREE
+ 0x0df4: 0x0E54, // XK_Thai_leksi: THAI DIGIT FOUR
+ 0x0df5: 0x0E55, // XK_Thai_lekha: THAI DIGIT FIVE
+ 0x0df6: 0x0E56, // XK_Thai_lekhok: THAI DIGIT SIX
+ 0x0df7: 0x0E57, // XK_Thai_lekchet: THAI DIGIT SEVEN
+ 0x0df8: 0x0E58, // XK_Thai_lekpaet: THAI DIGIT EIGHT
+ 0x0df9: 0x0E59, // XK_Thai_lekkao: THAI DIGIT NINE
+ 0x0eff: 0x20A9, // XK_Korean_Won: WON SIGN
+ 0x1000587: 0x0587, // XK_Armenian_ligature_ew: ARMENIAN SMALL LIGATURE ECH YIWN
+ 0x1000589: 0x0589, // XK_Armenian_full_stop: ARMENIAN FULL STOP
+ 0x100055d: 0x055D, // XK_Armenian_separation_mark: ARMENIAN COMMA
+ 0x100058a: 0x058A, // XK_Armenian_hyphen: ARMENIAN HYPHEN
+ 0x100055c: 0x055C, // XK_Armenian_exclam: ARMENIAN EXCLAMATION MARK
+ 0x100055b: 0x055B, // XK_Armenian_accent: ARMENIAN EMPHASIS MARK
+ 0x100055e: 0x055E, // XK_Armenian_question: ARMENIAN QUESTION MARK
+ 0x1000531: 0x0531, // XK_Armenian_AYB: ARMENIAN CAPITAL LETTER AYB
+ 0x1000561: 0x0561, // XK_Armenian_ayb: ARMENIAN SMALL LETTER AYB
+ 0x1000532: 0x0532, // XK_Armenian_BEN: ARMENIAN CAPITAL LETTER BEN
+ 0x1000562: 0x0562, // XK_Armenian_ben: ARMENIAN SMALL LETTER BEN
+ 0x1000533: 0x0533, // XK_Armenian_GIM: ARMENIAN CAPITAL LETTER GIM
+ 0x1000563: 0x0563, // XK_Armenian_gim: ARMENIAN SMALL LETTER GIM
+ 0x1000534: 0x0534, // XK_Armenian_DA: ARMENIAN CAPITAL LETTER DA
+ 0x1000564: 0x0564, // XK_Armenian_da: ARMENIAN SMALL LETTER DA
+ 0x1000535: 0x0535, // XK_Armenian_YECH: ARMENIAN CAPITAL LETTER ECH
+ 0x1000565: 0x0565, // XK_Armenian_yech: ARMENIAN SMALL LETTER ECH
+ 0x1000536: 0x0536, // XK_Armenian_ZA: ARMENIAN CAPITAL LETTER ZA
+ 0x1000566: 0x0566, // XK_Armenian_za: ARMENIAN SMALL LETTER ZA
+ 0x1000537: 0x0537, // XK_Armenian_E: ARMENIAN CAPITAL LETTER EH
+ 0x1000567: 0x0567, // XK_Armenian_e: ARMENIAN SMALL LETTER EH
+ 0x1000538: 0x0538, // XK_Armenian_AT: ARMENIAN CAPITAL LETTER ET
+ 0x1000568: 0x0568, // XK_Armenian_at: ARMENIAN SMALL LETTER ET
+ 0x1000539: 0x0539, // XK_Armenian_TO: ARMENIAN CAPITAL LETTER TO
+ 0x1000569: 0x0569, // XK_Armenian_to: ARMENIAN SMALL LETTER TO
+ 0x100053a: 0x053A, // XK_Armenian_ZHE: ARMENIAN CAPITAL LETTER ZHE
+ 0x100056a: 0x056A, // XK_Armenian_zhe: ARMENIAN SMALL LETTER ZHE
+ 0x100053b: 0x053B, // XK_Armenian_INI: ARMENIAN CAPITAL LETTER INI
+ 0x100056b: 0x056B, // XK_Armenian_ini: ARMENIAN SMALL LETTER INI
+ 0x100053c: 0x053C, // XK_Armenian_LYUN: ARMENIAN CAPITAL LETTER LIWN
+ 0x100056c: 0x056C, // XK_Armenian_lyun: ARMENIAN SMALL LETTER LIWN
+ 0x100053d: 0x053D, // XK_Armenian_KHE: ARMENIAN CAPITAL LETTER XEH
+ 0x100056d: 0x056D, // XK_Armenian_khe: ARMENIAN SMALL LETTER XEH
+ 0x100053e: 0x053E, // XK_Armenian_TSA: ARMENIAN CAPITAL LETTER CA
+ 0x100056e: 0x056E, // XK_Armenian_tsa: ARMENIAN SMALL LETTER CA
+ 0x100053f: 0x053F, // XK_Armenian_KEN: ARMENIAN CAPITAL LETTER KEN
+ 0x100056f: 0x056F, // XK_Armenian_ken: ARMENIAN SMALL LETTER KEN
+ 0x1000540: 0x0540, // XK_Armenian_HO: ARMENIAN CAPITAL LETTER HO
+ 0x1000570: 0x0570, // XK_Armenian_ho: ARMENIAN SMALL LETTER HO
+ 0x1000541: 0x0541, // XK_Armenian_DZA: ARMENIAN CAPITAL LETTER JA
+ 0x1000571: 0x0571, // XK_Armenian_dza: ARMENIAN SMALL LETTER JA
+ 0x1000542: 0x0542, // XK_Armenian_GHAT: ARMENIAN CAPITAL LETTER GHAD
+ 0x1000572: 0x0572, // XK_Armenian_ghat: ARMENIAN SMALL LETTER GHAD
+ 0x1000543: 0x0543, // XK_Armenian_TCHE: ARMENIAN CAPITAL LETTER CHEH
+ 0x1000573: 0x0573, // XK_Armenian_tche: ARMENIAN SMALL LETTER CHEH
+ 0x1000544: 0x0544, // XK_Armenian_MEN: ARMENIAN CAPITAL LETTER MEN
+ 0x1000574: 0x0574, // XK_Armenian_men: ARMENIAN SMALL LETTER MEN
+ 0x1000545: 0x0545, // XK_Armenian_HI: ARMENIAN CAPITAL LETTER YI
+ 0x1000575: 0x0575, // XK_Armenian_hi: ARMENIAN SMALL LETTER YI
+ 0x1000546: 0x0546, // XK_Armenian_NU: ARMENIAN CAPITAL LETTER NOW
+ 0x1000576: 0x0576, // XK_Armenian_nu: ARMENIAN SMALL LETTER NOW
+ 0x1000547: 0x0547, // XK_Armenian_SHA: ARMENIAN CAPITAL LETTER SHA
+ 0x1000577: 0x0577, // XK_Armenian_sha: ARMENIAN SMALL LETTER SHA
+ 0x1000548: 0x0548, // XK_Armenian_VO: ARMENIAN CAPITAL LETTER VO
+ 0x1000578: 0x0578, // XK_Armenian_vo: ARMENIAN SMALL LETTER VO
+ 0x1000549: 0x0549, // XK_Armenian_CHA: ARMENIAN CAPITAL LETTER CHA
+ 0x1000579: 0x0579, // XK_Armenian_cha: ARMENIAN SMALL LETTER CHA
+ 0x100054a: 0x054A, // XK_Armenian_PE: ARMENIAN CAPITAL LETTER PEH
+ 0x100057a: 0x057A, // XK_Armenian_pe: ARMENIAN SMALL LETTER PEH
+ 0x100054b: 0x054B, // XK_Armenian_JE: ARMENIAN CAPITAL LETTER JHEH
+ 0x100057b: 0x057B, // XK_Armenian_je: ARMENIAN SMALL LETTER JHEH
+ 0x100054c: 0x054C, // XK_Armenian_RA: ARMENIAN CAPITAL LETTER RA
+ 0x100057c: 0x057C, // XK_Armenian_ra: ARMENIAN SMALL LETTER RA
+ 0x100054d: 0x054D, // XK_Armenian_SE: ARMENIAN CAPITAL LETTER SEH
+ 0x100057d: 0x057D, // XK_Armenian_se: ARMENIAN SMALL LETTER SEH
+ 0x100054e: 0x054E, // XK_Armenian_VEV: ARMENIAN CAPITAL LETTER VEW
+ 0x100057e: 0x057E, // XK_Armenian_vev: ARMENIAN SMALL LETTER VEW
+ 0x100054f: 0x054F, // XK_Armenian_TYUN: ARMENIAN CAPITAL LETTER TIWN
+ 0x100057f: 0x057F, // XK_Armenian_tyun: ARMENIAN SMALL LETTER TIWN
+ 0x1000550: 0x0550, // XK_Armenian_RE: ARMENIAN CAPITAL LETTER REH
+ 0x1000580: 0x0580, // XK_Armenian_re: ARMENIAN SMALL LETTER REH
+ 0x1000551: 0x0551, // XK_Armenian_TSO: ARMENIAN CAPITAL LETTER CO
+ 0x1000581: 0x0581, // XK_Armenian_tso: ARMENIAN SMALL LETTER CO
+ 0x1000552: 0x0552, // XK_Armenian_VYUN: ARMENIAN CAPITAL LETTER YIWN
+ 0x1000582: 0x0582, // XK_Armenian_vyun: ARMENIAN SMALL LETTER YIWN
+ 0x1000553: 0x0553, // XK_Armenian_PYUR: ARMENIAN CAPITAL LETTER PIWR
+ 0x1000583: 0x0583, // XK_Armenian_pyur: ARMENIAN SMALL LETTER PIWR
+ 0x1000554: 0x0554, // XK_Armenian_KE: ARMENIAN CAPITAL LETTER KEH
+ 0x1000584: 0x0584, // XK_Armenian_ke: ARMENIAN SMALL LETTER KEH
+ 0x1000555: 0x0555, // XK_Armenian_O: ARMENIAN CAPITAL LETTER OH
+ 0x1000585: 0x0585, // XK_Armenian_o: ARMENIAN SMALL LETTER OH
+ 0x1000556: 0x0556, // XK_Armenian_FE: ARMENIAN CAPITAL LETTER FEH
+ 0x1000586: 0x0586, // XK_Armenian_fe: ARMENIAN SMALL LETTER FEH
+ 0x100055a: 0x055A, // XK_Armenian_apostrophe: ARMENIAN APOSTROPHE
+ 0x10010d0: 0x10D0, // XK_Georgian_an: GEORGIAN LETTER AN
+ 0x10010d1: 0x10D1, // XK_Georgian_ban: GEORGIAN LETTER BAN
+ 0x10010d2: 0x10D2, // XK_Georgian_gan: GEORGIAN LETTER GAN
+ 0x10010d3: 0x10D3, // XK_Georgian_don: GEORGIAN LETTER DON
+ 0x10010d4: 0x10D4, // XK_Georgian_en: GEORGIAN LETTER EN
+ 0x10010d5: 0x10D5, // XK_Georgian_vin: GEORGIAN LETTER VIN
+ 0x10010d6: 0x10D6, // XK_Georgian_zen: GEORGIAN LETTER ZEN
+ 0x10010d7: 0x10D7, // XK_Georgian_tan: GEORGIAN LETTER TAN
+ 0x10010d8: 0x10D8, // XK_Georgian_in: GEORGIAN LETTER IN
+ 0x10010d9: 0x10D9, // XK_Georgian_kan: GEORGIAN LETTER KAN
+ 0x10010da: 0x10DA, // XK_Georgian_las: GEORGIAN LETTER LAS
+ 0x10010db: 0x10DB, // XK_Georgian_man: GEORGIAN LETTER MAN
+ 0x10010dc: 0x10DC, // XK_Georgian_nar: GEORGIAN LETTER NAR
+ 0x10010dd: 0x10DD, // XK_Georgian_on: GEORGIAN LETTER ON
+ 0x10010de: 0x10DE, // XK_Georgian_par: GEORGIAN LETTER PAR
+ 0x10010df: 0x10DF, // XK_Georgian_zhar: GEORGIAN LETTER ZHAR
+ 0x10010e0: 0x10E0, // XK_Georgian_rae: GEORGIAN LETTER RAE
+ 0x10010e1: 0x10E1, // XK_Georgian_san: GEORGIAN LETTER SAN
+ 0x10010e2: 0x10E2, // XK_Georgian_tar: GEORGIAN LETTER TAR
+ 0x10010e3: 0x10E3, // XK_Georgian_un: GEORGIAN LETTER UN
+ 0x10010e4: 0x10E4, // XK_Georgian_phar: GEORGIAN LETTER PHAR
+ 0x10010e5: 0x10E5, // XK_Georgian_khar: GEORGIAN LETTER KHAR
+ 0x10010e6: 0x10E6, // XK_Georgian_ghan: GEORGIAN LETTER GHAN
+ 0x10010e7: 0x10E7, // XK_Georgian_qar: GEORGIAN LETTER QAR
+ 0x10010e8: 0x10E8, // XK_Georgian_shin: GEORGIAN LETTER SHIN
+ 0x10010e9: 0x10E9, // XK_Georgian_chin: GEORGIAN LETTER CHIN
+ 0x10010ea: 0x10EA, // XK_Georgian_can: GEORGIAN LETTER CAN
+ 0x10010eb: 0x10EB, // XK_Georgian_jil: GEORGIAN LETTER JIL
+ 0x10010ec: 0x10EC, // XK_Georgian_cil: GEORGIAN LETTER CIL
+ 0x10010ed: 0x10ED, // XK_Georgian_char: GEORGIAN LETTER CHAR
+ 0x10010ee: 0x10EE, // XK_Georgian_xan: GEORGIAN LETTER XAN
+ 0x10010ef: 0x10EF, // XK_Georgian_jhan: GEORGIAN LETTER JHAN
+ 0x10010f0: 0x10F0, // XK_Georgian_hae: GEORGIAN LETTER HAE
+ 0x10010f1: 0x10F1, // XK_Georgian_he: GEORGIAN LETTER HE
+ 0x10010f2: 0x10F2, // XK_Georgian_hie: GEORGIAN LETTER HIE
+ 0x10010f3: 0x10F3, // XK_Georgian_we: GEORGIAN LETTER WE
+ 0x10010f4: 0x10F4, // XK_Georgian_har: GEORGIAN LETTER HAR
+ 0x10010f5: 0x10F5, // XK_Georgian_hoe: GEORGIAN LETTER HOE
+ 0x10010f6: 0x10F6, // XK_Georgian_fi: GEORGIAN LETTER FI
+ 0x1001e8a: 0x1E8A, // XK_Xabovedot: LATIN CAPITAL LETTER X WITH DOT ABOVE
+ 0x100012c: 0x012C, // XK_Ibreve: LATIN CAPITAL LETTER I WITH BREVE
+ 0x10001b5: 0x01B5, // XK_Zstroke: LATIN CAPITAL LETTER Z WITH STROKE
+ 0x10001e6: 0x01E6, // XK_Gcaron: LATIN CAPITAL LETTER G WITH CARON
+ 0x10001d1: 0x01D2, // XK_Ocaron: LATIN CAPITAL LETTER O WITH CARON
+ 0x100019f: 0x019F, // XK_Obarred: LATIN CAPITAL LETTER O WITH MIDDLE TILDE
+ 0x1001e8b: 0x1E8B, // XK_xabovedot: LATIN SMALL LETTER X WITH DOT ABOVE
+ 0x100012d: 0x012D, // XK_ibreve: LATIN SMALL LETTER I WITH BREVE
+ 0x10001b6: 0x01B6, // XK_zstroke: LATIN SMALL LETTER Z WITH STROKE
+ 0x10001e7: 0x01E7, // XK_gcaron: LATIN SMALL LETTER G WITH CARON
+ 0x10001d2: 0x01D2, // XK_ocaron: LATIN SMALL LETTER O WITH CARON
+ 0x1000275: 0x0275, // XK_obarred: LATIN SMALL LETTER BARRED O
+ 0x100018f: 0x018F, // XK_SCHWA: LATIN CAPITAL LETTER SCHWA
+ 0x1000259: 0x0259, // XK_schwa: LATIN SMALL LETTER SCHWA
+ 0x10001b7: 0x01B7, // XK_EZH: LATIN CAPITAL LETTER EZH
+ 0x1000292: 0x0292, // XK_ezh: LATIN SMALL LETTER EZH
+ 0x1001e36: 0x1E36, // XK_Lbelowdot: LATIN CAPITAL LETTER L WITH DOT BELOW
+ 0x1001e37: 0x1E37, // XK_lbelowdot: LATIN SMALL LETTER L WITH DOT BELOW
+ 0x1001ea0: 0x1EA0, // XK_Abelowdot: LATIN CAPITAL LETTER A WITH DOT BELOW
+ 0x1001ea1: 0x1EA1, // XK_abelowdot: LATIN SMALL LETTER A WITH DOT BELOW
+ 0x1001ea2: 0x1EA2, // XK_Ahook: LATIN CAPITAL LETTER A WITH HOOK ABOVE
+ 0x1001ea3: 0x1EA3, // XK_ahook: LATIN SMALL LETTER A WITH HOOK ABOVE
+ 0x1001ea4: 0x1EA4, // XK_Acircumflexacute: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+ 0x1001ea5: 0x1EA5, // XK_acircumflexacute: LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE
+ 0x1001ea6: 0x1EA6, // XK_Acircumflexgrave: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+ 0x1001ea7: 0x1EA7, // XK_acircumflexgrave: LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE
+ 0x1001ea8: 0x1EA8, // XK_Acircumflexhook: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+ 0x1001ea9: 0x1EA9, // XK_acircumflexhook: LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+ 0x1001eaa: 0x1EAA, // XK_Acircumflextilde: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+ 0x1001eab: 0x1EAB, // XK_acircumflextilde: LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE
+ 0x1001eac: 0x1EAC, // XK_Acircumflexbelowdot: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+ 0x1001ead: 0x1EAD, // XK_acircumflexbelowdot: LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+ 0x1001eae: 0x1EAE, // XK_Abreveacute: LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+ 0x1001eaf: 0x1EAF, // XK_abreveacute: LATIN SMALL LETTER A WITH BREVE AND ACUTE
+ 0x1001eb0: 0x1EB0, // XK_Abrevegrave: LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+ 0x1001eb1: 0x1EB1, // XK_abrevegrave: LATIN SMALL LETTER A WITH BREVE AND GRAVE
+ 0x1001eb2: 0x1EB2, // XK_Abrevehook: LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+ 0x1001eb3: 0x1EB3, // XK_abrevehook: LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE
+ 0x1001eb4: 0x1EB4, // XK_Abrevetilde: LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+ 0x1001eb5: 0x1EB5, // XK_abrevetilde: LATIN SMALL LETTER A WITH BREVE AND TILDE
+ 0x1001eb6: 0x1EB6, // XK_Abrevebelowdot: LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+ 0x1001eb7: 0x1EB7, // XK_abrevebelowdot: LATIN SMALL LETTER A WITH BREVE AND DOT BELOW
+ 0x1001eb8: 0x1EB8, // XK_Ebelowdot: LATIN CAPITAL LETTER E WITH DOT BELOW
+ 0x1001eb9: 0x1EB9, // XK_ebelowdot: LATIN SMALL LETTER E WITH DOT BELOW
+ 0x1001eba: 0x1EBA, // XK_Ehook: LATIN CAPITAL LETTER E WITH HOOK ABOVE
+ 0x1001ebb: 0x1EBB, // XK_ehook: LATIN SMALL LETTER E WITH HOOK ABOVE
+ 0x1001ebc: 0x1EBC, // XK_Etilde: LATIN CAPITAL LETTER E WITH TILDE
+ 0x1001ebd: 0x1EBD, // XK_etilde: LATIN SMALL LETTER E WITH TILDE
+ 0x1001ebe: 0x1EBE, // XK_Ecircumflexacute: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+ 0x1001ebf: 0x1EBF, // XK_ecircumflexacute: LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE
+ 0x1001ec0: 0x1EC0, // XK_Ecircumflexgrave: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+ 0x1001ec1: 0x1EC1, // XK_ecircumflexgrave: LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE
+ 0x1001ec2: 0x1EC2, // XK_Ecircumflexhook: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+ 0x1001ec3: 0x1EC3, // XK_ecircumflexhook: LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+ 0x1001ec4: 0x1EC4, // XK_Ecircumflextilde: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+ 0x1001ec5: 0x1EC5, // XK_ecircumflextilde: LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE
+ 0x1001ec6: 0x1EC6, // XK_Ecircumflexbelowdot: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+ 0x1001ec7: 0x1EC7, // XK_ecircumflexbelowdot: LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+ 0x1001ec8: 0x1EC8, // XK_Ihook: LATIN CAPITAL LETTER I WITH HOOK ABOVE
+ 0x1001ec9: 0x1EC9, // XK_ihook: LATIN SMALL LETTER I WITH HOOK ABOVE
+ 0x1001eca: 0x1ECA, // XK_Ibelowdot: LATIN CAPITAL LETTER I WITH DOT BELOW
+ 0x1001ecb: 0x1ECB, // XK_ibelowdot: LATIN SMALL LETTER I WITH DOT BELOW
+ 0x1001ecc: 0x1ECC, // XK_Obelowdot: LATIN CAPITAL LETTER O WITH DOT BELOW
+ 0x1001ecd: 0x1ECD, // XK_obelowdot: LATIN SMALL LETTER O WITH DOT BELOW
+ 0x1001ece: 0x1ECE, // XK_Ohook: LATIN CAPITAL LETTER O WITH HOOK ABOVE
+ 0x1001ecf: 0x1ECF, // XK_ohook: LATIN SMALL LETTER O WITH HOOK ABOVE
+ 0x1001ed0: 0x1ED0, // XK_Ocircumflexacute: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+ 0x1001ed1: 0x1ED1, // XK_ocircumflexacute: LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE
+ 0x1001ed2: 0x1ED2, // XK_Ocircumflexgrave: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+ 0x1001ed3: 0x1ED3, // XK_ocircumflexgrave: LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE
+ 0x1001ed4: 0x1ED4, // XK_Ocircumflexhook: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+ 0x1001ed5: 0x1ED5, // XK_ocircumflexhook: LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+ 0x1001ed6: 0x1ED6, // XK_Ocircumflextilde: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+ 0x1001ed7: 0x1ED7, // XK_ocircumflextilde: LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE
+ 0x1001ed8: 0x1ED8, // XK_Ocircumflexbelowdot: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+ 0x1001ed9: 0x1ED9, // XK_ocircumflexbelowdot: LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+ 0x1001eda: 0x1EDA, // XK_Ohornacute: LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+ 0x1001edb: 0x1EDB, // XK_ohornacute: LATIN SMALL LETTER O WITH HORN AND ACUTE
+ 0x1001edc: 0x1EDC, // XK_Ohorngrave: LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+ 0x1001edd: 0x1EDD, // XK_ohorngrave: LATIN SMALL LETTER O WITH HORN AND GRAVE
+ 0x1001ede: 0x1EDE, // XK_Ohornhook: LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+ 0x1001edf: 0x1EDF, // XK_ohornhook: LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE
+ 0x1001ee0: 0x1EE0, // XK_Ohorntilde: LATIN CAPITAL LETTER O WITH HORN AND TILDE
+ 0x1001ee1: 0x1EE1, // XK_ohorntilde: LATIN SMALL LETTER O WITH HORN AND TILDE
+ 0x1001ee2: 0x1EE2, // XK_Ohornbelowdot: LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+ 0x1001ee3: 0x1EE3, // XK_ohornbelowdot: LATIN SMALL LETTER O WITH HORN AND DOT BELOW
+ 0x1001ee4: 0x1EE4, // XK_Ubelowdot: LATIN CAPITAL LETTER U WITH DOT BELOW
+ 0x1001ee5: 0x1EE5, // XK_ubelowdot: LATIN SMALL LETTER U WITH DOT BELOW
+ 0x1001ee6: 0x1EE6, // XK_Uhook: LATIN CAPITAL LETTER U WITH HOOK ABOVE
+ 0x1001ee7: 0x1EE7, // XK_uhook: LATIN SMALL LETTER U WITH HOOK ABOVE
+ 0x1001ee8: 0x1EE8, // XK_Uhornacute: LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+ 0x1001ee9: 0x1EE9, // XK_uhornacute: LATIN SMALL LETTER U WITH HORN AND ACUTE
+ 0x1001eea: 0x1EEA, // XK_Uhorngrave: LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+ 0x1001eeb: 0x1EEB, // XK_uhorngrave: LATIN SMALL LETTER U WITH HORN AND GRAVE
+ 0x1001eec: 0x1EEC, // XK_Uhornhook: LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+ 0x1001eed: 0x1EED, // XK_uhornhook: LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE
+ 0x1001eee: 0x1EEE, // XK_Uhorntilde: LATIN CAPITAL LETTER U WITH HORN AND TILDE
+ 0x1001eef: 0x1EEF, // XK_uhorntilde: LATIN SMALL LETTER U WITH HORN AND TILDE
+ 0x1001ef0: 0x1EF0, // XK_Uhornbelowdot: LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+ 0x1001ef1: 0x1EF1, // XK_uhornbelowdot: LATIN SMALL LETTER U WITH HORN AND DOT BELOW
+ 0x1001ef4: 0x1EF4, // XK_Ybelowdot: LATIN CAPITAL LETTER Y WITH DOT BELOW
+ 0x1001ef5: 0x1EF5, // XK_ybelowdot: LATIN SMALL LETTER Y WITH DOT BELOW
+ 0x1001ef6: 0x1EF6, // XK_Yhook: LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+ 0x1001ef7: 0x1EF7, // XK_yhook: LATIN SMALL LETTER Y WITH HOOK ABOVE
+ 0x1001ef8: 0x1EF8, // XK_Ytilde: LATIN CAPITAL LETTER Y WITH TILDE
+ 0x1001ef9: 0x1EF9, // XK_ytilde: LATIN SMALL LETTER Y WITH TILDE
+ 0x10001a0: 0x01A0, // XK_Ohorn: LATIN CAPITAL LETTER O WITH HORN
+ 0x10001a1: 0x01A1, // XK_ohorn: LATIN SMALL LETTER O WITH HORN
+ 0x10001af: 0x01AF, // XK_Uhorn: LATIN CAPITAL LETTER U WITH HORN
+ 0x10001b0: 0x01B0, // XK_uhorn: LATIN SMALL LETTER U WITH HORN
+ 0x10020a0: 0x20A0, // XK_EcuSign: EURO-CURRENCY SIGN
+ 0x10020a1: 0x20A1, // XK_ColonSign: COLON SIGN
+ 0x10020a2: 0x20A2, // XK_CruzeiroSign: CRUZEIRO SIGN
+ 0x10020a3: 0x20A3, // XK_FFrancSign: FRENCH FRANC SIGN
+ 0x10020a4: 0x20A4, // XK_LiraSign: LIRA SIGN
+ 0x10020a5: 0x20A5, // XK_MillSign: MILL SIGN
+ 0x10020a6: 0x20A6, // XK_NairaSign: NAIRA SIGN
+ 0x10020a7: 0x20A7, // XK_PesetaSign: PESETA SIGN
+ 0x10020a8: 0x20A8, // XK_RupeeSign: RUPEE SIGN
+ 0x10020a9: 0x20A9, // XK_WonSign: WON SIGN
+ 0x10020aa: 0x20AA, // XK_NewSheqelSign: NEW SHEQEL SIGN
+ 0x10020ab: 0x20AB, // XK_DongSign: DONG SIGN
+ 0x20ac: 0x20AC, // XK_EuroSign: EURO SIGN
+ 0x1002070: 0x2070, // XK_zerosuperior: SUPERSCRIPT ZERO
+ 0x1002074: 0x2074, // XK_foursuperior: SUPERSCRIPT FOUR
+ 0x1002075: 0x2075, // XK_fivesuperior: SUPERSCRIPT FIVE
+ 0x1002076: 0x2076, // XK_sixsuperior: SUPERSCRIPT SIX
+ 0x1002077: 0x2077, // XK_sevensuperior: SUPERSCRIPT SEVEN
+ 0x1002078: 0x2078, // XK_eightsuperior: SUPERSCRIPT EIGHT
+ 0x1002079: 0x2079, // XK_ninesuperior: SUPERSCRIPT NINE
+ 0x1002080: 0x2080, // XK_zerosubscript: SUBSCRIPT ZERO
+ 0x1002081: 0x2081, // XK_onesubscript: SUBSCRIPT ONE
+ 0x1002082: 0x2082, // XK_twosubscript: SUBSCRIPT TWO
+ 0x1002083: 0x2083, // XK_threesubscript: SUBSCRIPT THREE
+ 0x1002084: 0x2084, // XK_foursubscript: SUBSCRIPT FOUR
+ 0x1002085: 0x2085, // XK_fivesubscript: SUBSCRIPT FIVE
+ 0x1002086: 0x2086, // XK_sixsubscript: SUBSCRIPT SIX
+ 0x1002087: 0x2087, // XK_sevensubscript: SUBSCRIPT SEVEN
+ 0x1002088: 0x2088, // XK_eightsubscript: SUBSCRIPT EIGHT
+ 0x1002089: 0x2089, // XK_ninesubscript: SUBSCRIPT NINE
+ 0x1002202: 0x2202, // XK_partdifferential: PARTIAL DIFFERENTIAL
+ 0x1002205: 0x2205, // XK_emptyset: NULL SET
+ 0x1002208: 0x2208, // XK_elementof: ELEMENT OF
+ 0x1002209: 0x2209, // XK_notelementof: NOT AN ELEMENT OF
+ 0x100220B: 0x220B, // XK_containsas: CONTAINS AS MEMBER
+ 0x100221A: 0x221A, // XK_squareroot: SQUARE ROOT
+ 0x100221B: 0x221B, // XK_cuberoot: CUBE ROOT
+ 0x100221C: 0x221C, // XK_fourthroot: FOURTH ROOT
+ 0x100222C: 0x222C, // XK_dintegral: DOUBLE INTEGRAL
+ 0x100222D: 0x222D, // XK_tintegral: TRIPLE INTEGRAL
+ 0x1002235: 0x2235, // XK_because: BECAUSE
+ 0x1002248: 0x2245, // XK_approxeq: ALMOST EQUAL TO
+ 0x1002247: 0x2247, // XK_notapproxeq: NOT ALMOST EQUAL TO
+ 0x1002262: 0x2262, // XK_notidentical: NOT IDENTICAL TO
+ 0x1002263: 0x2263, // XK_stricteq: STRICTLY EQUIVALENT TO
+ 0x1002800: 0x2800, // XK_braille_blank: BRAILLE PATTERN BLANK
+ 0x1002801: 0x2801, // XK_braille_dots_1: BRAILLE PATTERN DOTS-1
+ 0x1002802: 0x2802, // XK_braille_dots_2: BRAILLE PATTERN DOTS-2
+ 0x1002803: 0x2803, // XK_braille_dots_12: BRAILLE PATTERN DOTS-12
+ 0x1002804: 0x2804, // XK_braille_dots_3: BRAILLE PATTERN DOTS-3
+ 0x1002805: 0x2805, // XK_braille_dots_13: BRAILLE PATTERN DOTS-13
+ 0x1002806: 0x2806, // XK_braille_dots_23: BRAILLE PATTERN DOTS-23
+ 0x1002807: 0x2807, // XK_braille_dots_123: BRAILLE PATTERN DOTS-123
+ 0x1002808: 0x2808, // XK_braille_dots_4: BRAILLE PATTERN DOTS-4
+ 0x1002809: 0x2809, // XK_braille_dots_14: BRAILLE PATTERN DOTS-14
+ 0x100280a: 0x280a, // XK_braille_dots_24: BRAILLE PATTERN DOTS-24
+ 0x100280b: 0x280b, // XK_braille_dots_124: BRAILLE PATTERN DOTS-124
+ 0x100280c: 0x280c, // XK_braille_dots_34: BRAILLE PATTERN DOTS-34
+ 0x100280d: 0x280d, // XK_braille_dots_134: BRAILLE PATTERN DOTS-134
+ 0x100280e: 0x280e, // XK_braille_dots_234: BRAILLE PATTERN DOTS-234
+ 0x100280f: 0x280f, // XK_braille_dots_1234: BRAILLE PATTERN DOTS-1234
+ 0x1002810: 0x2810, // XK_braille_dots_5: BRAILLE PATTERN DOTS-5
+ 0x1002811: 0x2811, // XK_braille_dots_15: BRAILLE PATTERN DOTS-15
+ 0x1002812: 0x2812, // XK_braille_dots_25: BRAILLE PATTERN DOTS-25
+ 0x1002813: 0x2813, // XK_braille_dots_125: BRAILLE PATTERN DOTS-125
+ 0x1002814: 0x2814, // XK_braille_dots_35: BRAILLE PATTERN DOTS-35
+ 0x1002815: 0x2815, // XK_braille_dots_135: BRAILLE PATTERN DOTS-135
+ 0x1002816: 0x2816, // XK_braille_dots_235: BRAILLE PATTERN DOTS-235
+ 0x1002817: 0x2817, // XK_braille_dots_1235: BRAILLE PATTERN DOTS-1235
+ 0x1002818: 0x2818, // XK_braille_dots_45: BRAILLE PATTERN DOTS-45
+ 0x1002819: 0x2819, // XK_braille_dots_145: BRAILLE PATTERN DOTS-145
+ 0x100281a: 0x281a, // XK_braille_dots_245: BRAILLE PATTERN DOTS-245
+ 0x100281b: 0x281b, // XK_braille_dots_1245: BRAILLE PATTERN DOTS-1245
+ 0x100281c: 0x281c, // XK_braille_dots_345: BRAILLE PATTERN DOTS-345
+ 0x100281d: 0x281d, // XK_braille_dots_1345: BRAILLE PATTERN DOTS-1345
+ 0x100281e: 0x281e, // XK_braille_dots_2345: BRAILLE PATTERN DOTS-2345
+ 0x100281f: 0x281f, // XK_braille_dots_12345: BRAILLE PATTERN DOTS-12345
+ 0x1002820: 0x2820, // XK_braille_dots_6: BRAILLE PATTERN DOTS-6
+ 0x1002821: 0x2821, // XK_braille_dots_16: BRAILLE PATTERN DOTS-16
+ 0x1002822: 0x2822, // XK_braille_dots_26: BRAILLE PATTERN DOTS-26
+ 0x1002823: 0x2823, // XK_braille_dots_126: BRAILLE PATTERN DOTS-126
+ 0x1002824: 0x2824, // XK_braille_dots_36: BRAILLE PATTERN DOTS-36
+ 0x1002825: 0x2825, // XK_braille_dots_136: BRAILLE PATTERN DOTS-136
+ 0x1002826: 0x2826, // XK_braille_dots_236: BRAILLE PATTERN DOTS-236
+ 0x1002827: 0x2827, // XK_braille_dots_1236: BRAILLE PATTERN DOTS-1236
+ 0x1002828: 0x2828, // XK_braille_dots_46: BRAILLE PATTERN DOTS-46
+ 0x1002829: 0x2829, // XK_braille_dots_146: BRAILLE PATTERN DOTS-146
+ 0x100282a: 0x282a, // XK_braille_dots_246: BRAILLE PATTERN DOTS-246
+ 0x100282b: 0x282b, // XK_braille_dots_1246: BRAILLE PATTERN DOTS-1246
+ 0x100282c: 0x282c, // XK_braille_dots_346: BRAILLE PATTERN DOTS-346
+ 0x100282d: 0x282d, // XK_braille_dots_1346: BRAILLE PATTERN DOTS-1346
+ 0x100282e: 0x282e, // XK_braille_dots_2346: BRAILLE PATTERN DOTS-2346
+ 0x100282f: 0x282f, // XK_braille_dots_12346: BRAILLE PATTERN DOTS-12346
+ 0x1002830: 0x2830, // XK_braille_dots_56: BRAILLE PATTERN DOTS-56
+ 0x1002831: 0x2831, // XK_braille_dots_156: BRAILLE PATTERN DOTS-156
+ 0x1002832: 0x2832, // XK_braille_dots_256: BRAILLE PATTERN DOTS-256
+ 0x1002833: 0x2833, // XK_braille_dots_1256: BRAILLE PATTERN DOTS-1256
+ 0x1002834: 0x2834, // XK_braille_dots_356: BRAILLE PATTERN DOTS-356
+ 0x1002835: 0x2835, // XK_braille_dots_1356: BRAILLE PATTERN DOTS-1356
+ 0x1002836: 0x2836, // XK_braille_dots_2356: BRAILLE PATTERN DOTS-2356
+ 0x1002837: 0x2837, // XK_braille_dots_12356: BRAILLE PATTERN DOTS-12356
+ 0x1002838: 0x2838, // XK_braille_dots_456: BRAILLE PATTERN DOTS-456
+ 0x1002839: 0x2839, // XK_braille_dots_1456: BRAILLE PATTERN DOTS-1456
+ 0x100283a: 0x283a, // XK_braille_dots_2456: BRAILLE PATTERN DOTS-2456
+ 0x100283b: 0x283b, // XK_braille_dots_12456: BRAILLE PATTERN DOTS-12456
+ 0x100283c: 0x283c, // XK_braille_dots_3456: BRAILLE PATTERN DOTS-3456
+ 0x100283d: 0x283d, // XK_braille_dots_13456: BRAILLE PATTERN DOTS-13456
+ 0x100283e: 0x283e, // XK_braille_dots_23456: BRAILLE PATTERN DOTS-23456
+ 0x100283f: 0x283f, // XK_braille_dots_123456: BRAILLE PATTERN DOTS-123456
+ 0x1002840: 0x2840, // XK_braille_dots_7: BRAILLE PATTERN DOTS-7
+ 0x1002841: 0x2841, // XK_braille_dots_17: BRAILLE PATTERN DOTS-17
+ 0x1002842: 0x2842, // XK_braille_dots_27: BRAILLE PATTERN DOTS-27
+ 0x1002843: 0x2843, // XK_braille_dots_127: BRAILLE PATTERN DOTS-127
+ 0x1002844: 0x2844, // XK_braille_dots_37: BRAILLE PATTERN DOTS-37
+ 0x1002845: 0x2845, // XK_braille_dots_137: BRAILLE PATTERN DOTS-137
+ 0x1002846: 0x2846, // XK_braille_dots_237: BRAILLE PATTERN DOTS-237
+ 0x1002847: 0x2847, // XK_braille_dots_1237: BRAILLE PATTERN DOTS-1237
+ 0x1002848: 0x2848, // XK_braille_dots_47: BRAILLE PATTERN DOTS-47
+ 0x1002849: 0x2849, // XK_braille_dots_147: BRAILLE PATTERN DOTS-147
+ 0x100284a: 0x284a, // XK_braille_dots_247: BRAILLE PATTERN DOTS-247
+ 0x100284b: 0x284b, // XK_braille_dots_1247: BRAILLE PATTERN DOTS-1247
+ 0x100284c: 0x284c, // XK_braille_dots_347: BRAILLE PATTERN DOTS-347
+ 0x100284d: 0x284d, // XK_braille_dots_1347: BRAILLE PATTERN DOTS-1347
+ 0x100284e: 0x284e, // XK_braille_dots_2347: BRAILLE PATTERN DOTS-2347
+ 0x100284f: 0x284f, // XK_braille_dots_12347: BRAILLE PATTERN DOTS-12347
+ 0x1002850: 0x2850, // XK_braille_dots_57: BRAILLE PATTERN DOTS-57
+ 0x1002851: 0x2851, // XK_braille_dots_157: BRAILLE PATTERN DOTS-157
+ 0x1002852: 0x2852, // XK_braille_dots_257: BRAILLE PATTERN DOTS-257
+ 0x1002853: 0x2853, // XK_braille_dots_1257: BRAILLE PATTERN DOTS-1257
+ 0x1002854: 0x2854, // XK_braille_dots_357: BRAILLE PATTERN DOTS-357
+ 0x1002855: 0x2855, // XK_braille_dots_1357: BRAILLE PATTERN DOTS-1357
+ 0x1002856: 0x2856, // XK_braille_dots_2357: BRAILLE PATTERN DOTS-2357
+ 0x1002857: 0x2857, // XK_braille_dots_12357: BRAILLE PATTERN DOTS-12357
+ 0x1002858: 0x2858, // XK_braille_dots_457: BRAILLE PATTERN DOTS-457
+ 0x1002859: 0x2859, // XK_braille_dots_1457: BRAILLE PATTERN DOTS-1457
+ 0x100285a: 0x285a, // XK_braille_dots_2457: BRAILLE PATTERN DOTS-2457
+ 0x100285b: 0x285b, // XK_braille_dots_12457: BRAILLE PATTERN DOTS-12457
+ 0x100285c: 0x285c, // XK_braille_dots_3457: BRAILLE PATTERN DOTS-3457
+ 0x100285d: 0x285d, // XK_braille_dots_13457: BRAILLE PATTERN DOTS-13457
+ 0x100285e: 0x285e, // XK_braille_dots_23457: BRAILLE PATTERN DOTS-23457
+ 0x100285f: 0x285f, // XK_braille_dots_123457: BRAILLE PATTERN DOTS-123457
+ 0x1002860: 0x2860, // XK_braille_dots_67: BRAILLE PATTERN DOTS-67
+ 0x1002861: 0x2861, // XK_braille_dots_167: BRAILLE PATTERN DOTS-167
+ 0x1002862: 0x2862, // XK_braille_dots_267: BRAILLE PATTERN DOTS-267
+ 0x1002863: 0x2863, // XK_braille_dots_1267: BRAILLE PATTERN DOTS-1267
+ 0x1002864: 0x2864, // XK_braille_dots_367: BRAILLE PATTERN DOTS-367
+ 0x1002865: 0x2865, // XK_braille_dots_1367: BRAILLE PATTERN DOTS-1367
+ 0x1002866: 0x2866, // XK_braille_dots_2367: BRAILLE PATTERN DOTS-2367
+ 0x1002867: 0x2867, // XK_braille_dots_12367: BRAILLE PATTERN DOTS-12367
+ 0x1002868: 0x2868, // XK_braille_dots_467: BRAILLE PATTERN DOTS-467
+ 0x1002869: 0x2869, // XK_braille_dots_1467: BRAILLE PATTERN DOTS-1467
+ 0x100286a: 0x286a, // XK_braille_dots_2467: BRAILLE PATTERN DOTS-2467
+ 0x100286b: 0x286b, // XK_braille_dots_12467: BRAILLE PATTERN DOTS-12467
+ 0x100286c: 0x286c, // XK_braille_dots_3467: BRAILLE PATTERN DOTS-3467
+ 0x100286d: 0x286d, // XK_braille_dots_13467: BRAILLE PATTERN DOTS-13467
+ 0x100286e: 0x286e, // XK_braille_dots_23467: BRAILLE PATTERN DOTS-23467
+ 0x100286f: 0x286f, // XK_braille_dots_123467: BRAILLE PATTERN DOTS-123467
+ 0x1002870: 0x2870, // XK_braille_dots_567: BRAILLE PATTERN DOTS-567
+ 0x1002871: 0x2871, // XK_braille_dots_1567: BRAILLE PATTERN DOTS-1567
+ 0x1002872: 0x2872, // XK_braille_dots_2567: BRAILLE PATTERN DOTS-2567
+ 0x1002873: 0x2873, // XK_braille_dots_12567: BRAILLE PATTERN DOTS-12567
+ 0x1002874: 0x2874, // XK_braille_dots_3567: BRAILLE PATTERN DOTS-3567
+ 0x1002875: 0x2875, // XK_braille_dots_13567: BRAILLE PATTERN DOTS-13567
+ 0x1002876: 0x2876, // XK_braille_dots_23567: BRAILLE PATTERN DOTS-23567
+ 0x1002877: 0x2877, // XK_braille_dots_123567: BRAILLE PATTERN DOTS-123567
+ 0x1002878: 0x2878, // XK_braille_dots_4567: BRAILLE PATTERN DOTS-4567
+ 0x1002879: 0x2879, // XK_braille_dots_14567: BRAILLE PATTERN DOTS-14567
+ 0x100287a: 0x287a, // XK_braille_dots_24567: BRAILLE PATTERN DOTS-24567
+ 0x100287b: 0x287b, // XK_braille_dots_124567: BRAILLE PATTERN DOTS-124567
+ 0x100287c: 0x287c, // XK_braille_dots_34567: BRAILLE PATTERN DOTS-34567
+ 0x100287d: 0x287d, // XK_braille_dots_134567: BRAILLE PATTERN DOTS-134567
+ 0x100287e: 0x287e, // XK_braille_dots_234567: BRAILLE PATTERN DOTS-234567
+ 0x100287f: 0x287f, // XK_braille_dots_1234567: BRAILLE PATTERN DOTS-1234567
+ 0x1002880: 0x2880, // XK_braille_dots_8: BRAILLE PATTERN DOTS-8
+ 0x1002881: 0x2881, // XK_braille_dots_18: BRAILLE PATTERN DOTS-18
+ 0x1002882: 0x2882, // XK_braille_dots_28: BRAILLE PATTERN DOTS-28
+ 0x1002883: 0x2883, // XK_braille_dots_128: BRAILLE PATTERN DOTS-128
+ 0x1002884: 0x2884, // XK_braille_dots_38: BRAILLE PATTERN DOTS-38
+ 0x1002885: 0x2885, // XK_braille_dots_138: BRAILLE PATTERN DOTS-138
+ 0x1002886: 0x2886, // XK_braille_dots_238: BRAILLE PATTERN DOTS-238
+ 0x1002887: 0x2887, // XK_braille_dots_1238: BRAILLE PATTERN DOTS-1238
+ 0x1002888: 0x2888, // XK_braille_dots_48: BRAILLE PATTERN DOTS-48
+ 0x1002889: 0x2889, // XK_braille_dots_148: BRAILLE PATTERN DOTS-148
+ 0x100288a: 0x288a, // XK_braille_dots_248: BRAILLE PATTERN DOTS-248
+ 0x100288b: 0x288b, // XK_braille_dots_1248: BRAILLE PATTERN DOTS-1248
+ 0x100288c: 0x288c, // XK_braille_dots_348: BRAILLE PATTERN DOTS-348
+ 0x100288d: 0x288d, // XK_braille_dots_1348: BRAILLE PATTERN DOTS-1348
+ 0x100288e: 0x288e, // XK_braille_dots_2348: BRAILLE PATTERN DOTS-2348
+ 0x100288f: 0x288f, // XK_braille_dots_12348: BRAILLE PATTERN DOTS-12348
+ 0x1002890: 0x2890, // XK_braille_dots_58: BRAILLE PATTERN DOTS-58
+ 0x1002891: 0x2891, // XK_braille_dots_158: BRAILLE PATTERN DOTS-158
+ 0x1002892: 0x2892, // XK_braille_dots_258: BRAILLE PATTERN DOTS-258
+ 0x1002893: 0x2893, // XK_braille_dots_1258: BRAILLE PATTERN DOTS-1258
+ 0x1002894: 0x2894, // XK_braille_dots_358: BRAILLE PATTERN DOTS-358
+ 0x1002895: 0x2895, // XK_braille_dots_1358: BRAILLE PATTERN DOTS-1358
+ 0x1002896: 0x2896, // XK_braille_dots_2358: BRAILLE PATTERN DOTS-2358
+ 0x1002897: 0x2897, // XK_braille_dots_12358: BRAILLE PATTERN DOTS-12358
+ 0x1002898: 0x2898, // XK_braille_dots_458: BRAILLE PATTERN DOTS-458
+ 0x1002899: 0x2899, // XK_braille_dots_1458: BRAILLE PATTERN DOTS-1458
+ 0x100289a: 0x289a, // XK_braille_dots_2458: BRAILLE PATTERN DOTS-2458
+ 0x100289b: 0x289b, // XK_braille_dots_12458: BRAILLE PATTERN DOTS-12458
+ 0x100289c: 0x289c, // XK_braille_dots_3458: BRAILLE PATTERN DOTS-3458
+ 0x100289d: 0x289d, // XK_braille_dots_13458: BRAILLE PATTERN DOTS-13458
+ 0x100289e: 0x289e, // XK_braille_dots_23458: BRAILLE PATTERN DOTS-23458
+ 0x100289f: 0x289f, // XK_braille_dots_123458: BRAILLE PATTERN DOTS-123458
+ 0x10028a0: 0x28a0, // XK_braille_dots_68: BRAILLE PATTERN DOTS-68
+ 0x10028a1: 0x28a1, // XK_braille_dots_168: BRAILLE PATTERN DOTS-168
+ 0x10028a2: 0x28a2, // XK_braille_dots_268: BRAILLE PATTERN DOTS-268
+ 0x10028a3: 0x28a3, // XK_braille_dots_1268: BRAILLE PATTERN DOTS-1268
+ 0x10028a4: 0x28a4, // XK_braille_dots_368: BRAILLE PATTERN DOTS-368
+ 0x10028a5: 0x28a5, // XK_braille_dots_1368: BRAILLE PATTERN DOTS-1368
+ 0x10028a6: 0x28a6, // XK_braille_dots_2368: BRAILLE PATTERN DOTS-2368
+ 0x10028a7: 0x28a7, // XK_braille_dots_12368: BRAILLE PATTERN DOTS-12368
+ 0x10028a8: 0x28a8, // XK_braille_dots_468: BRAILLE PATTERN DOTS-468
+ 0x10028a9: 0x28a9, // XK_braille_dots_1468: BRAILLE PATTERN DOTS-1468
+ 0x10028aa: 0x28aa, // XK_braille_dots_2468: BRAILLE PATTERN DOTS-2468
+ 0x10028ab: 0x28ab, // XK_braille_dots_12468: BRAILLE PATTERN DOTS-12468
+ 0x10028ac: 0x28ac, // XK_braille_dots_3468: BRAILLE PATTERN DOTS-3468
+ 0x10028ad: 0x28ad, // XK_braille_dots_13468: BRAILLE PATTERN DOTS-13468
+ 0x10028ae: 0x28ae, // XK_braille_dots_23468: BRAILLE PATTERN DOTS-23468
+ 0x10028af: 0x28af, // XK_braille_dots_123468: BRAILLE PATTERN DOTS-123468
+ 0x10028b0: 0x28b0, // XK_braille_dots_568: BRAILLE PATTERN DOTS-568
+ 0x10028b1: 0x28b1, // XK_braille_dots_1568: BRAILLE PATTERN DOTS-1568
+ 0x10028b2: 0x28b2, // XK_braille_dots_2568: BRAILLE PATTERN DOTS-2568
+ 0x10028b3: 0x28b3, // XK_braille_dots_12568: BRAILLE PATTERN DOTS-12568
+ 0x10028b4: 0x28b4, // XK_braille_dots_3568: BRAILLE PATTERN DOTS-3568
+ 0x10028b5: 0x28b5, // XK_braille_dots_13568: BRAILLE PATTERN DOTS-13568
+ 0x10028b6: 0x28b6, // XK_braille_dots_23568: BRAILLE PATTERN DOTS-23568
+ 0x10028b7: 0x28b7, // XK_braille_dots_123568: BRAILLE PATTERN DOTS-123568
+ 0x10028b8: 0x28b8, // XK_braille_dots_4568: BRAILLE PATTERN DOTS-4568
+ 0x10028b9: 0x28b9, // XK_braille_dots_14568: BRAILLE PATTERN DOTS-14568
+ 0x10028ba: 0x28ba, // XK_braille_dots_24568: BRAILLE PATTERN DOTS-24568
+ 0x10028bb: 0x28bb, // XK_braille_dots_124568: BRAILLE PATTERN DOTS-124568
+ 0x10028bc: 0x28bc, // XK_braille_dots_34568: BRAILLE PATTERN DOTS-34568
+ 0x10028bd: 0x28bd, // XK_braille_dots_134568: BRAILLE PATTERN DOTS-134568
+ 0x10028be: 0x28be, // XK_braille_dots_234568: BRAILLE PATTERN DOTS-234568
+ 0x10028bf: 0x28bf, // XK_braille_dots_1234568: BRAILLE PATTERN DOTS-1234568
+ 0x10028c0: 0x28c0, // XK_braille_dots_78: BRAILLE PATTERN DOTS-78
+ 0x10028c1: 0x28c1, // XK_braille_dots_178: BRAILLE PATTERN DOTS-178
+ 0x10028c2: 0x28c2, // XK_braille_dots_278: BRAILLE PATTERN DOTS-278
+ 0x10028c3: 0x28c3, // XK_braille_dots_1278: BRAILLE PATTERN DOTS-1278
+ 0x10028c4: 0x28c4, // XK_braille_dots_378: BRAILLE PATTERN DOTS-378
+ 0x10028c5: 0x28c5, // XK_braille_dots_1378: BRAILLE PATTERN DOTS-1378
+ 0x10028c6: 0x28c6, // XK_braille_dots_2378: BRAILLE PATTERN DOTS-2378
+ 0x10028c7: 0x28c7, // XK_braille_dots_12378: BRAILLE PATTERN DOTS-12378
+ 0x10028c8: 0x28c8, // XK_braille_dots_478: BRAILLE PATTERN DOTS-478
+ 0x10028c9: 0x28c9, // XK_braille_dots_1478: BRAILLE PATTERN DOTS-1478
+ 0x10028ca: 0x28ca, // XK_braille_dots_2478: BRAILLE PATTERN DOTS-2478
+ 0x10028cb: 0x28cb, // XK_braille_dots_12478: BRAILLE PATTERN DOTS-12478
+ 0x10028cc: 0x28cc, // XK_braille_dots_3478: BRAILLE PATTERN DOTS-3478
+ 0x10028cd: 0x28cd, // XK_braille_dots_13478: BRAILLE PATTERN DOTS-13478
+ 0x10028ce: 0x28ce, // XK_braille_dots_23478: BRAILLE PATTERN DOTS-23478
+ 0x10028cf: 0x28cf, // XK_braille_dots_123478: BRAILLE PATTERN DOTS-123478
+ 0x10028d0: 0x28d0, // XK_braille_dots_578: BRAILLE PATTERN DOTS-578
+ 0x10028d1: 0x28d1, // XK_braille_dots_1578: BRAILLE PATTERN DOTS-1578
+ 0x10028d2: 0x28d2, // XK_braille_dots_2578: BRAILLE PATTERN DOTS-2578
+ 0x10028d3: 0x28d3, // XK_braille_dots_12578: BRAILLE PATTERN DOTS-12578
+ 0x10028d4: 0x28d4, // XK_braille_dots_3578: BRAILLE PATTERN DOTS-3578
+ 0x10028d5: 0x28d5, // XK_braille_dots_13578: BRAILLE PATTERN DOTS-13578
+ 0x10028d6: 0x28d6, // XK_braille_dots_23578: BRAILLE PATTERN DOTS-23578
+ 0x10028d7: 0x28d7, // XK_braille_dots_123578: BRAILLE PATTERN DOTS-123578
+ 0x10028d8: 0x28d8, // XK_braille_dots_4578: BRAILLE PATTERN DOTS-4578
+ 0x10028d9: 0x28d9, // XK_braille_dots_14578: BRAILLE PATTERN DOTS-14578
+ 0x10028da: 0x28da, // XK_braille_dots_24578: BRAILLE PATTERN DOTS-24578
+ 0x10028db: 0x28db, // XK_braille_dots_124578: BRAILLE PATTERN DOTS-124578
+ 0x10028dc: 0x28dc, // XK_braille_dots_34578: BRAILLE PATTERN DOTS-34578
+ 0x10028dd: 0x28dd, // XK_braille_dots_134578: BRAILLE PATTERN DOTS-134578
+ 0x10028de: 0x28de, // XK_braille_dots_234578: BRAILLE PATTERN DOTS-234578
+ 0x10028df: 0x28df, // XK_braille_dots_1234578: BRAILLE PATTERN DOTS-1234578
+ 0x10028e0: 0x28e0, // XK_braille_dots_678: BRAILLE PATTERN DOTS-678
+ 0x10028e1: 0x28e1, // XK_braille_dots_1678: BRAILLE PATTERN DOTS-1678
+ 0x10028e2: 0x28e2, // XK_braille_dots_2678: BRAILLE PATTERN DOTS-2678
+ 0x10028e3: 0x28e3, // XK_braille_dots_12678: BRAILLE PATTERN DOTS-12678
+ 0x10028e4: 0x28e4, // XK_braille_dots_3678: BRAILLE PATTERN DOTS-3678
+ 0x10028e5: 0x28e5, // XK_braille_dots_13678: BRAILLE PATTERN DOTS-13678
+ 0x10028e6: 0x28e6, // XK_braille_dots_23678: BRAILLE PATTERN DOTS-23678
+ 0x10028e7: 0x28e7, // XK_braille_dots_123678: BRAILLE PATTERN DOTS-123678
+ 0x10028e8: 0x28e8, // XK_braille_dots_4678: BRAILLE PATTERN DOTS-4678
+ 0x10028e9: 0x28e9, // XK_braille_dots_14678: BRAILLE PATTERN DOTS-14678
+ 0x10028ea: 0x28ea, // XK_braille_dots_24678: BRAILLE PATTERN DOTS-24678
+ 0x10028eb: 0x28eb, // XK_braille_dots_124678: BRAILLE PATTERN DOTS-124678
+ 0x10028ec: 0x28ec, // XK_braille_dots_34678: BRAILLE PATTERN DOTS-34678
+ 0x10028ed: 0x28ed, // XK_braille_dots_134678: BRAILLE PATTERN DOTS-134678
+ 0x10028ee: 0x28ee, // XK_braille_dots_234678: BRAILLE PATTERN DOTS-234678
+ 0x10028ef: 0x28ef, // XK_braille_dots_1234678: BRAILLE PATTERN DOTS-1234678
+ 0x10028f0: 0x28f0, // XK_braille_dots_5678: BRAILLE PATTERN DOTS-5678
+ 0x10028f1: 0x28f1, // XK_braille_dots_15678: BRAILLE PATTERN DOTS-15678
+ 0x10028f2: 0x28f2, // XK_braille_dots_25678: BRAILLE PATTERN DOTS-25678
+ 0x10028f3: 0x28f3, // XK_braille_dots_125678: BRAILLE PATTERN DOTS-125678
+ 0x10028f4: 0x28f4, // XK_braille_dots_35678: BRAILLE PATTERN DOTS-35678
+ 0x10028f5: 0x28f5, // XK_braille_dots_135678: BRAILLE PATTERN DOTS-135678
+ 0x10028f6: 0x28f6, // XK_braille_dots_235678: BRAILLE PATTERN DOTS-235678
+ 0x10028f7: 0x28f7, // XK_braille_dots_1235678: BRAILLE PATTERN DOTS-1235678
+ 0x10028f8: 0x28f8, // XK_braille_dots_45678: BRAILLE PATTERN DOTS-45678
+ 0x10028f9: 0x28f9, // XK_braille_dots_145678: BRAILLE PATTERN DOTS-145678
+ 0x10028fa: 0x28fa, // XK_braille_dots_245678: BRAILLE PATTERN DOTS-245678
+ 0x10028fb: 0x28fb, // XK_braille_dots_1245678: BRAILLE PATTERN DOTS-1245678
+ 0x10028fc: 0x28fc, // XK_braille_dots_345678: BRAILLE PATTERN DOTS-345678
+ 0x10028fd: 0x28fd, // XK_braille_dots_1345678: BRAILLE PATTERN DOTS-1345678
+ 0x10028fe: 0x28fe, // XK_braille_dots_2345678: BRAILLE PATTERN DOTS-2345678
+ 0x10028ff: 0x28ff, // XK_braille_dots_12345678: BRAILLE PATTERN DOTS-12345678
+ 0x1000d82: 0x0D82, // XK_Sinh_ng: SINHALA ANUSVARAYA
+ 0x1000d83: 0x0D83, // XK_Sinh_h2: SINHALA VISARGAYA
+ 0x1000d85: 0x0D85, // XK_Sinh_a: SINHALA AYANNA
+ 0x1000d86: 0x0D86, // XK_Sinh_aa: SINHALA AAYANNA
+ 0x1000d87: 0x0D87, // XK_Sinh_ae: SINHALA AEYANNA
+ 0x1000d88: 0x0D88, // XK_Sinh_aee: SINHALA AEEYANNA
+ 0x1000d89: 0x0D89, // XK_Sinh_i: SINHALA IYANNA
+ 0x1000d8a: 0x0D8A, // XK_Sinh_ii: SINHALA IIYANNA
+ 0x1000d8b: 0x0D8B, // XK_Sinh_u: SINHALA UYANNA
+ 0x1000d8c: 0x0D8C, // XK_Sinh_uu: SINHALA UUYANNA
+ 0x1000d8d: 0x0D8D, // XK_Sinh_ri: SINHALA IRUYANNA
+ 0x1000d8e: 0x0D8E, // XK_Sinh_rii: SINHALA IRUUYANNA
+ 0x1000d8f: 0x0D8F, // XK_Sinh_lu: SINHALA ILUYANNA
+ 0x1000d90: 0x0D90, // XK_Sinh_luu: SINHALA ILUUYANNA
+ 0x1000d91: 0x0D91, // XK_Sinh_e: SINHALA EYANNA
+ 0x1000d92: 0x0D92, // XK_Sinh_ee: SINHALA EEYANNA
+ 0x1000d93: 0x0D93, // XK_Sinh_ai: SINHALA AIYANNA
+ 0x1000d94: 0x0D94, // XK_Sinh_o: SINHALA OYANNA
+ 0x1000d95: 0x0D95, // XK_Sinh_oo: SINHALA OOYANNA
+ 0x1000d96: 0x0D96, // XK_Sinh_au: SINHALA AUYANNA
+ 0x1000d9a: 0x0D9A, // XK_Sinh_ka: SINHALA KAYANNA
+ 0x1000d9b: 0x0D9B, // XK_Sinh_kha: SINHALA MAHA. KAYANNA
+ 0x1000d9c: 0x0D9C, // XK_Sinh_ga: SINHALA GAYANNA
+ 0x1000d9d: 0x0D9D, // XK_Sinh_gha: SINHALA MAHA. GAYANNA
+ 0x1000d9e: 0x0D9E, // XK_Sinh_ng2: SINHALA KANTAJA NAASIKYAYA
+ 0x1000d9f: 0x0D9F, // XK_Sinh_nga: SINHALA SANYAKA GAYANNA
+ 0x1000da0: 0x0DA0, // XK_Sinh_ca: SINHALA CAYANNA
+ 0x1000da1: 0x0DA1, // XK_Sinh_cha: SINHALA MAHA. CAYANNA
+ 0x1000da2: 0x0DA2, // XK_Sinh_ja: SINHALA JAYANNA
+ 0x1000da3: 0x0DA3, // XK_Sinh_jha: SINHALA MAHA. JAYANNA
+ 0x1000da4: 0x0DA4, // XK_Sinh_nya: SINHALA TAALUJA NAASIKYAYA
+ 0x1000da5: 0x0DA5, // XK_Sinh_jnya: SINHALA TAALUJA SANYOOGA NAASIKYAYA
+ 0x1000da6: 0x0DA6, // XK_Sinh_nja: SINHALA SANYAKA JAYANNA
+ 0x1000da7: 0x0DA7, // XK_Sinh_tta: SINHALA TTAYANNA
+ 0x1000da8: 0x0DA8, // XK_Sinh_ttha: SINHALA MAHA. TTAYANNA
+ 0x1000da9: 0x0DA9, // XK_Sinh_dda: SINHALA DDAYANNA
+ 0x1000daa: 0x0DAA, // XK_Sinh_ddha: SINHALA MAHA. DDAYANNA
+ 0x1000dab: 0x0DAB, // XK_Sinh_nna: SINHALA MUURDHAJA NAYANNA
+ 0x1000dac: 0x0DAC, // XK_Sinh_ndda: SINHALA SANYAKA DDAYANNA
+ 0x1000dad: 0x0DAD, // XK_Sinh_tha: SINHALA TAYANNA
+ 0x1000dae: 0x0DAE, // XK_Sinh_thha: SINHALA MAHA. TAYANNA
+ 0x1000daf: 0x0DAF, // XK_Sinh_dha: SINHALA DAYANNA
+ 0x1000db0: 0x0DB0, // XK_Sinh_dhha: SINHALA MAHA. DAYANNA
+ 0x1000db1: 0x0DB1, // XK_Sinh_na: SINHALA DANTAJA NAYANNA
+ 0x1000db3: 0x0DB3, // XK_Sinh_ndha: SINHALA SANYAKA DAYANNA
+ 0x1000db4: 0x0DB4, // XK_Sinh_pa: SINHALA PAYANNA
+ 0x1000db5: 0x0DB5, // XK_Sinh_pha: SINHALA MAHA. PAYANNA
+ 0x1000db6: 0x0DB6, // XK_Sinh_ba: SINHALA BAYANNA
+ 0x1000db7: 0x0DB7, // XK_Sinh_bha: SINHALA MAHA. BAYANNA
+ 0x1000db8: 0x0DB8, // XK_Sinh_ma: SINHALA MAYANNA
+ 0x1000db9: 0x0DB9, // XK_Sinh_mba: SINHALA AMBA BAYANNA
+ 0x1000dba: 0x0DBA, // XK_Sinh_ya: SINHALA YAYANNA
+ 0x1000dbb: 0x0DBB, // XK_Sinh_ra: SINHALA RAYANNA
+ 0x1000dbd: 0x0DBD, // XK_Sinh_la: SINHALA DANTAJA LAYANNA
+ 0x1000dc0: 0x0DC0, // XK_Sinh_va: SINHALA VAYANNA
+ 0x1000dc1: 0x0DC1, // XK_Sinh_sha: SINHALA TAALUJA SAYANNA
+ 0x1000dc2: 0x0DC2, // XK_Sinh_ssha: SINHALA MUURDHAJA SAYANNA
+ 0x1000dc3: 0x0DC3, // XK_Sinh_sa: SINHALA DANTAJA SAYANNA
+ 0x1000dc4: 0x0DC4, // XK_Sinh_ha: SINHALA HAYANNA
+ 0x1000dc5: 0x0DC5, // XK_Sinh_lla: SINHALA MUURDHAJA LAYANNA
+ 0x1000dc6: 0x0DC6, // XK_Sinh_fa: SINHALA FAYANNA
+ 0x1000dca: 0x0DCA, // XK_Sinh_al: SINHALA AL-LAKUNA
+ 0x1000dcf: 0x0DCF, // XK_Sinh_aa2: SINHALA AELA-PILLA
+ 0x1000dd0: 0x0DD0, // XK_Sinh_ae2: SINHALA AEDA-PILLA
+ 0x1000dd1: 0x0DD1, // XK_Sinh_aee2: SINHALA DIGA AEDA-PILLA
+ 0x1000dd2: 0x0DD2, // XK_Sinh_i2: SINHALA IS-PILLA
+ 0x1000dd3: 0x0DD3, // XK_Sinh_ii2: SINHALA DIGA IS-PILLA
+ 0x1000dd4: 0x0DD4, // XK_Sinh_u2: SINHALA PAA-PILLA
+ 0x1000dd6: 0x0DD6, // XK_Sinh_uu2: SINHALA DIGA PAA-PILLA
+ 0x1000dd8: 0x0DD8, // XK_Sinh_ru2: SINHALA GAETTA-PILLA
+ 0x1000dd9: 0x0DD9, // XK_Sinh_e2: SINHALA KOMBUVA
+ 0x1000dda: 0x0DDA, // XK_Sinh_ee2: SINHALA DIGA KOMBUVA
+ 0x1000ddb: 0x0DDB, // XK_Sinh_ai2: SINHALA KOMBU DEKA
+ 0x1000dde: 0x0DDE, // XK_Sinh_au2: SINHALA KOMBUVA HAA GAYANUKITTA
+ 0x1000ddf: 0x0DDF, // XK_Sinh_lu2: SINHALA GAYANUKITTA
+ 0x1000df2: 0x0DF2, // XK_Sinh_ruu2: SINHALA DIGA GAETTA-PILLA
+ 0x1000df3: 0x0DF3, // XK_Sinh_luu2: SINHALA DIGA GAYANUKITTA
+ 0x1000df4: 0x0DF4, // XK_Sinh_kunddaliya: SINHALA KUNDDALIYA
+}
diff --git a/shiny/driver/internal/x11key/x11key.go b/shiny/driver/internal/x11key/x11key.go
new file mode 100644
index 0000000..546d463
--- /dev/null
+++ b/shiny/driver/internal/x11key/x11key.go
@@ -0,0 +1,323 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run gen.go
+
+// x11key contains X11 numeric codes for the keyboard and mouse.
+package x11key // import "golang.org/x/exp/shiny/driver/internal/x11key"
+
+import (
+ "unicode"
+
+ "golang.org/x/mobile/event/key"
+)
+
+// These constants come from /usr/include/X11/X.h
+const (
+ ShiftMask = 1 << 0
+ LockMask = 1 << 1
+ ControlMask = 1 << 2
+ Mod1Mask = 1 << 3
+ Mod2Mask = 1 << 4
+ Mod3Mask = 1 << 5
+ Mod4Mask = 1 << 6
+ Mod5Mask = 1 << 7
+ Button1Mask = 1 << 8
+ Button2Mask = 1 << 9
+ Button3Mask = 1 << 10
+ Button4Mask = 1 << 11
+ Button5Mask = 1 << 12
+)
+
+type KeysymTable struct {
+ Table [256][6]uint32
+
+ NumLockMod, ModeSwitchMod, ISOLevel3ShiftMod uint16
+}
+
+func (t *KeysymTable) Lookup(detail uint8, state uint16) (rune, key.Code) {
+ te := t.Table[detail][0:2]
+ if state&t.ModeSwitchMod != 0 {
+ te = t.Table[detail][2:4]
+ }
+ if state&t.ISOLevel3ShiftMod != 0 {
+ te = t.Table[detail][4:6]
+ }
+
+ // The key event's rune depends on whether the shift key is down.
+ unshifted := rune(te[0])
+ r := unshifted
+ if state&t.NumLockMod != 0 && isKeypad(te[1]) {
+ if state&ShiftMask == 0 {
+ r = rune(te[1])
+ }
+ } else if state&ShiftMask != 0 {
+ r = rune(te[1])
+ // In X11, a zero keysym when shift is down means to use what the
+ // keysym is when shift is up.
+ if r == 0 {
+ r = unshifted
+ }
+ }
+
+ // The key event's code is independent of whether the shift key is down.
+ var c key.Code
+ if 0 <= unshifted && unshifted < 0x80 {
+ c = asciiKeycodes[unshifted]
+ if state&LockMask != 0 {
+ r = unicode.ToUpper(r)
+ }
+ } else if kk, isKeypad := keypadKeysyms[r]; isKeypad {
+ r, c = kk.rune, kk.code
+ } else if nuk := nonUnicodeKeycodes[unshifted]; nuk != key.CodeUnknown {
+ r, c = -1, nuk
+ } else {
+ r = keysymCodePoints[r]
+ if state&LockMask != 0 {
+ r = unicode.ToUpper(r)
+ }
+ }
+
+ return r, c
+}
+
+func isKeypad(keysym uint32) bool {
+ return keysym >= 0xff80 && keysym <= 0xffbd
+}
+
+func KeyModifiers(state uint16) (m key.Modifiers) {
+ if state&ShiftMask != 0 {
+ m |= key.ModShift
+ }
+ if state&ControlMask != 0 {
+ m |= key.ModControl
+ }
+ if state&Mod1Mask != 0 {
+ m |= key.ModAlt
+ }
+ if state&Mod4Mask != 0 {
+ m |= key.ModMeta
+ }
+ return m
+}
+
+// These constants come from /usr/include/X11/{keysymdef,XF86keysym}.h
+const (
+ xkISOLeftTab = 0xfe20
+ xkBackSpace = 0xff08
+ xkTab = 0xff09
+ xkReturn = 0xff0d
+ xkEscape = 0xff1b
+ xkMultiKey = 0xff20
+ xkHome = 0xff50
+ xkLeft = 0xff51
+ xkUp = 0xff52
+ xkRight = 0xff53
+ xkDown = 0xff54
+ xkPageUp = 0xff55
+ xkPageDown = 0xff56
+ xkEnd = 0xff57
+ xkInsert = 0xff63
+ xkMenu = 0xff67
+ xkHelp = 0xff6a
+
+ xkNumLock = 0xff7f
+ xkKeypadEnter = 0xff8d
+ xkKeypadHome = 0xff95
+ xkKeypadLeft = 0xff96
+ xkKeypadUp = 0xff97
+ xkKeypadRight = 0xff98
+ xkKeypadDown = 0xff99
+ xkKeypadPageUp = 0xff9a
+ xkKeypadPageDown = 0xff9b
+ xkKeypadEnd = 0xff9c
+ xkKeypadInsert = 0xff9e
+ xkKeypadDelete = 0xff9f
+ xkKeypadEqual = 0xffbd
+ xkKeypadMultiply = 0xffaa
+ xkKeypadAdd = 0xffab
+ xkKeypadSubtract = 0xffad
+ xkKeypadDecimal = 0xffae
+ xkKeypadDivide = 0xffaf
+ xkKeypad0 = 0xffb0
+ xkKeypad1 = 0xffb1
+ xkKeypad2 = 0xffb2
+ xkKeypad3 = 0xffb3
+ xkKeypad4 = 0xffb4
+ xkKeypad5 = 0xffb5
+ xkKeypad6 = 0xffb6
+ xkKeypad7 = 0xffb7
+ xkKeypad8 = 0xffb8
+ xkKeypad9 = 0xffb9
+
+ xkF1 = 0xffbe
+ xkF2 = 0xffbf
+ xkF3 = 0xffc0
+ xkF4 = 0xffc1
+ xkF5 = 0xffc2
+ xkF6 = 0xffc3
+ xkF7 = 0xffc4
+ xkF8 = 0xffc5
+ xkF9 = 0xffc6
+ xkF10 = 0xffc7
+ xkF11 = 0xffc8
+ xkF12 = 0xffc9
+ xkShiftL = 0xffe1
+ xkShiftR = 0xffe2
+ xkControlL = 0xffe3
+ xkControlR = 0xffe4
+ xkCapsLock = 0xffe5
+ xkAltL = 0xffe9
+ xkAltR = 0xffea
+ xkSuperL = 0xffeb
+ xkSuperR = 0xffec
+ xkDelete = 0xffff
+
+ xf86xkAudioLowerVolume = 0x1008ff11
+ xf86xkAudioMute = 0x1008ff12
+ xf86xkAudioRaiseVolume = 0x1008ff13
+)
+
+// nonUnicodeKeycodes maps from those xproto.Keysym values (converted to runes)
+// that do not correspond to a Unicode code point, such as "Page Up", "F1" or
+// "Left Shift", to key.Code values.
+var nonUnicodeKeycodes = map[rune]key.Code{
+ xkISOLeftTab: key.CodeTab,
+ xkBackSpace: key.CodeDeleteBackspace,
+ xkTab: key.CodeTab,
+ xkReturn: key.CodeReturnEnter,
+ xkEscape: key.CodeEscape,
+ xkHome: key.CodeHome,
+ xkLeft: key.CodeLeftArrow,
+ xkUp: key.CodeUpArrow,
+ xkRight: key.CodeRightArrow,
+ xkDown: key.CodeDownArrow,
+ xkPageUp: key.CodePageUp,
+ xkPageDown: key.CodePageDown,
+ xkEnd: key.CodeEnd,
+ xkInsert: key.CodeInsert,
+ xkMenu: key.CodeRightGUI, // TODO: CodeRightGUI or CodeMenu??
+ xkHelp: key.CodeHelp,
+ xkNumLock: key.CodeKeypadNumLock,
+ xkMultiKey: key.CodeCompose,
+
+ xkKeypadEnter: key.CodeKeypadEnter,
+ xkKeypadHome: key.CodeHome,
+ xkKeypadLeft: key.CodeLeftArrow,
+ xkKeypadUp: key.CodeUpArrow,
+ xkKeypadRight: key.CodeRightArrow,
+ xkKeypadDown: key.CodeDownArrow,
+ xkKeypadPageUp: key.CodePageUp,
+ xkKeypadPageDown: key.CodePageDown,
+ xkKeypadEnd: key.CodeEnd,
+ xkKeypadInsert: key.CodeInsert,
+ xkKeypadDelete: key.CodeDeleteForward,
+
+ xkF1: key.CodeF1,
+ xkF2: key.CodeF2,
+ xkF3: key.CodeF3,
+ xkF4: key.CodeF4,
+ xkF5: key.CodeF5,
+ xkF6: key.CodeF6,
+ xkF7: key.CodeF7,
+ xkF8: key.CodeF8,
+ xkF9: key.CodeF9,
+ xkF10: key.CodeF10,
+ xkF11: key.CodeF11,
+ xkF12: key.CodeF12,
+
+ xkShiftL: key.CodeLeftShift,
+ xkShiftR: key.CodeRightShift,
+ xkControlL: key.CodeLeftControl,
+ xkControlR: key.CodeRightControl,
+ xkCapsLock: key.CodeCapsLock,
+ xkAltL: key.CodeLeftAlt,
+ xkAltR: key.CodeRightAlt,
+ xkSuperL: key.CodeLeftGUI,
+ xkSuperR: key.CodeRightGUI,
+
+ xkDelete: key.CodeDeleteForward,
+
+ xf86xkAudioRaiseVolume: key.CodeVolumeUp,
+ xf86xkAudioLowerVolume: key.CodeVolumeDown,
+ xf86xkAudioMute: key.CodeMute,
+}
+
+// asciiKeycodes maps lower-case ASCII runes to key.Code values.
+var asciiKeycodes = [0x80]key.Code{
+ 'a': key.CodeA,
+ 'b': key.CodeB,
+ 'c': key.CodeC,
+ 'd': key.CodeD,
+ 'e': key.CodeE,
+ 'f': key.CodeF,
+ 'g': key.CodeG,
+ 'h': key.CodeH,
+ 'i': key.CodeI,
+ 'j': key.CodeJ,
+ 'k': key.CodeK,
+ 'l': key.CodeL,
+ 'm': key.CodeM,
+ 'n': key.CodeN,
+ 'o': key.CodeO,
+ 'p': key.CodeP,
+ 'q': key.CodeQ,
+ 'r': key.CodeR,
+ 's': key.CodeS,
+ 't': key.CodeT,
+ 'u': key.CodeU,
+ 'v': key.CodeV,
+ 'w': key.CodeW,
+ 'x': key.CodeX,
+ 'y': key.CodeY,
+ 'z': key.CodeZ,
+
+ '1': key.Code1,
+ '2': key.Code2,
+ '3': key.Code3,
+ '4': key.Code4,
+ '5': key.Code5,
+ '6': key.Code6,
+ '7': key.Code7,
+ '8': key.Code8,
+ '9': key.Code9,
+ '0': key.Code0,
+
+ ' ': key.CodeSpacebar,
+ '-': key.CodeHyphenMinus,
+ '=': key.CodeEqualSign,
+ '[': key.CodeLeftSquareBracket,
+ ']': key.CodeRightSquareBracket,
+ '\\': key.CodeBackslash,
+ ';': key.CodeSemicolon,
+ '\'': key.CodeApostrophe,
+ '`': key.CodeGraveAccent,
+ ',': key.CodeComma,
+ '.': key.CodeFullStop,
+ '/': key.CodeSlash,
+}
+
+type keypadKeysym struct {
+ rune rune
+ code key.Code
+}
+
+var keypadKeysyms = map[rune]keypadKeysym{
+ xkKeypadEqual: {'=', key.CodeKeypadEqualSign},
+ xkKeypadMultiply: {'*', key.CodeKeypadAsterisk},
+ xkKeypadAdd: {'+', key.CodeKeypadPlusSign},
+ xkKeypadSubtract: {'-', key.CodeKeypadHyphenMinus},
+ xkKeypadDecimal: {'.', key.CodeKeypadFullStop},
+ xkKeypadDivide: {'/', key.CodeKeypadSlash},
+ xkKeypad0: {'0', key.CodeKeypad0},
+ xkKeypad1: {'1', key.CodeKeypad1},
+ xkKeypad2: {'2', key.CodeKeypad2},
+ xkKeypad3: {'3', key.CodeKeypad3},
+ xkKeypad4: {'4', key.CodeKeypad4},
+ xkKeypad5: {'5', key.CodeKeypad5},
+ xkKeypad6: {'6', key.CodeKeypad6},
+ xkKeypad7: {'7', key.CodeKeypad7},
+ xkKeypad8: {'8', key.CodeKeypad8},
+ xkKeypad9: {'9', key.CodeKeypad9},
+}
diff --git a/shiny/driver/mtldriver/buffer.go b/shiny/driver/mtldriver/buffer.go
new file mode 100644
index 0000000..fdd775a
--- /dev/null
+++ b/shiny/driver/mtldriver/buffer.go
@@ -0,0 +1,19 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && cgo
+
+package mtldriver
+
+import "image"
+
+// bufferImpl implements screen.Buffer.
+type bufferImpl struct {
+ rgba *image.RGBA
+}
+
+func (*bufferImpl) Release() {}
+func (b *bufferImpl) Size() image.Point { return b.rgba.Rect.Max }
+func (b *bufferImpl) Bounds() image.Rectangle { return b.rgba.Rect }
+func (b *bufferImpl) RGBA() *image.RGBA { return b.rgba }
diff --git a/shiny/driver/mtldriver/internal/appkit/appkit.go b/shiny/driver/mtldriver/internal/appkit/appkit.go
new file mode 100644
index 0000000..cf1ed5e
--- /dev/null
+++ b/shiny/driver/mtldriver/internal/appkit/appkit.go
@@ -0,0 +1,68 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin
+
+// Package appkit provides access to Apple's AppKit API
+// (https://developer.apple.com/documentation/appkit).
+//
+// This package is in very early stages of development.
+// It's a minimal implementation with scope limited to
+// supporting mtldriver.
+//
+// It was copied from dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/appkit.
+package appkit
+
+import (
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/mtldriver/internal/coreanim"
+)
+
+/*
+#include <stdbool.h>
+#include "appkit.h"
+*/
+import "C"
+
+// Window is a window that an app displays on the screen.
+//
+// Reference: https://developer.apple.com/documentation/appkit/nswindow.
+type Window struct {
+ window unsafe.Pointer
+}
+
+// NewWindow returns a Window that wraps an existing NSWindow * pointer.
+func NewWindow(window unsafe.Pointer) Window {
+ return Window{window}
+}
+
+// ContentView returns the window's content view, the highest accessible View
+// in the window's view hierarchy.
+//
+// Reference: https://developer.apple.com/documentation/appkit/nswindow/1419160-contentview.
+func (w Window) ContentView() View {
+ return View{C.Window_ContentView(w.window)}
+}
+
+// View is the infrastructure for drawing, printing, and handling events in an app.
+//
+// Reference: https://developer.apple.com/documentation/appkit/nsview.
+type View struct {
+ view unsafe.Pointer
+}
+
+// SetLayer sets v.layer to l.
+//
+// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer.
+func (v View) SetLayer(l coreanim.Layer) {
+ C.View_SetLayer(v.view, l.Layer())
+}
+
+// SetWantsLayer sets v.wantsLayer to wantsLayer.
+//
+// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer.
+func (v View) SetWantsLayer(wantsLayer bool) {
+ C.View_SetWantsLayer(v.view, C.bool(wantsLayer))
+}
diff --git a/shiny/driver/mtldriver/internal/appkit/appkit.h b/shiny/driver/mtldriver/internal/appkit/appkit.h
new file mode 100644
index 0000000..3a3e705
--- /dev/null
+++ b/shiny/driver/mtldriver/internal/appkit/appkit.h
@@ -0,0 +1,10 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+void * Window_ContentView(void * window);
+
+void View_SetLayer(void * view, void * layer);
+void View_SetWantsLayer(void * view, bool wantsLayer);
diff --git a/shiny/driver/mtldriver/internal/appkit/appkit.m b/shiny/driver/mtldriver/internal/appkit/appkit.m
new file mode 100644
index 0000000..4dec9db
--- /dev/null
+++ b/shiny/driver/mtldriver/internal/appkit/appkit.m
@@ -0,0 +1,20 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+#import <Cocoa/Cocoa.h>
+#include "appkit.h"
+
+void * Window_ContentView(void * window) {
+ return ((NSWindow *)window).contentView;
+}
+
+void View_SetLayer(void * view, void * layer) {
+ ((NSView *)view).layer = (CALayer *)layer;
+}
+
+void View_SetWantsLayer(void * view, bool wantsLayer) {
+ ((NSView *)view).wantsLayer = wantsLayer;
+}
diff --git a/shiny/driver/mtldriver/internal/coreanim/coreanim.go b/shiny/driver/mtldriver/internal/coreanim/coreanim.go
new file mode 100644
index 0000000..19ee694
--- /dev/null
+++ b/shiny/driver/mtldriver/internal/coreanim/coreanim.go
@@ -0,0 +1,140 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin
+
+// Package coreanim provides access to Apple's Core Animation API
+// (https://developer.apple.com/documentation/quartzcore).
+//
+// This package is in very early stages of development.
+// It's a minimal implementation with scope limited to
+// supporting mtldriver.
+//
+// It was copied from dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/coreanim.
+package coreanim
+
+import (
+ "errors"
+ "unsafe"
+
+ "dmitri.shuralyov.com/gpu/mtl"
+)
+
+/*
+#cgo LDFLAGS: -framework QuartzCore -framework Foundation
+#include <stdbool.h>
+#include "coreanim.h"
+*/
+import "C"
+
+// Layer is an object that manages image-based content and
+// allows you to perform animations on that content.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/calayer.
+type Layer interface {
+ // Layer returns the underlying CALayer * pointer.
+ Layer() unsafe.Pointer
+}
+
+// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
+type MetalLayer struct {
+ metalLayer unsafe.Pointer
+}
+
+// MakeMetalLayer creates a new Core Animation Metal layer.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
+func MakeMetalLayer() MetalLayer {
+ return MetalLayer{C.MakeMetalLayer()}
+}
+
+// Layer implements the Layer interface.
+func (ml MetalLayer) Layer() unsafe.Pointer { return ml.metalLayer }
+
+// PixelFormat returns the pixel format of textures for rendering layer content.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
+func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
+ return mtl.PixelFormat(C.MetalLayer_PixelFormat(ml.metalLayer))
+}
+
+// SetDevice sets the Metal device responsible for the layer's drawable resources.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
+func (ml MetalLayer) SetDevice(device mtl.Device) {
+ C.MetalLayer_SetDevice(ml.metalLayer, device.Device())
+}
+
+// SetPixelFormat controls the pixel format of textures for rendering layer content.
+//
+// The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB,
+// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
+// SetPixelFormat panics for other values.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
+func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
+ e := C.MetalLayer_SetPixelFormat(ml.metalLayer, C.uint16_t(pf))
+ if e != nil {
+ panic(errors.New(C.GoString(e)))
+ }
+}
+
+// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
+// managed by Core Animation.
+//
+// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
+func (ml MetalLayer) SetMaximumDrawableCount(count int) {
+ e := C.MetalLayer_SetMaximumDrawableCount(ml.metalLayer, C.uint_t(count))
+ if e != nil {
+ panic(errors.New(C.GoString(e)))
+ }
+}
+
+// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
+// are synchronized with the display's refresh rate.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled.
+func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
+ C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, C.bool(enabled))
+}
+
+// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
+func (ml MetalLayer) SetDrawableSize(width, height int) {
+ C.MetalLayer_SetDrawableSize(ml.metalLayer, C.double(width), C.double(height))
+}
+
+// NextDrawable returns a Metal drawable.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
+func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
+ md := C.MetalLayer_NextDrawable(ml.metalLayer)
+ if md == nil {
+ return MetalDrawable{}, errors.New("nextDrawable returned nil")
+ }
+
+ return MetalDrawable{md}, nil
+}
+
+// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
+type MetalDrawable struct {
+ metalDrawable unsafe.Pointer
+}
+
+// Drawable implements the mtl.Drawable interface.
+func (md MetalDrawable) Drawable() unsafe.Pointer { return md.metalDrawable }
+
+// Texture returns a Metal texture object representing the drawable object's content.
+//
+// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
+func (md MetalDrawable) Texture() mtl.Texture {
+ return mtl.NewTexture(C.MetalDrawable_Texture(md.metalDrawable))
+}
diff --git a/shiny/driver/mtldriver/internal/coreanim/coreanim.h b/shiny/driver/mtldriver/internal/coreanim/coreanim.h
new file mode 100644
index 0000000..6e1ee2b
--- /dev/null
+++ b/shiny/driver/mtldriver/internal/coreanim/coreanim.h
@@ -0,0 +1,20 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+typedef unsigned long uint_t;
+typedef unsigned short uint16_t;
+
+void * MakeMetalLayer();
+
+uint16_t MetalLayer_PixelFormat(void * metalLayer);
+void MetalLayer_SetDevice(void * metalLayer, void * device);
+const char * MetalLayer_SetPixelFormat(void * metalLayer, uint16_t pixelFormat);
+const char * MetalLayer_SetMaximumDrawableCount(void * metalLayer, uint_t maximumDrawableCount);
+void MetalLayer_SetDisplaySyncEnabled(void * metalLayer, bool displaySyncEnabled);
+void MetalLayer_SetDrawableSize(void * metalLayer, double width, double height);
+void * MetalLayer_NextDrawable(void * metalLayer);
+
+void * MetalDrawable_Texture(void * drawable);
diff --git a/shiny/driver/mtldriver/internal/coreanim/coreanim.m b/shiny/driver/mtldriver/internal/coreanim/coreanim.m
new file mode 100644
index 0000000..7848a84
--- /dev/null
+++ b/shiny/driver/mtldriver/internal/coreanim/coreanim.m
@@ -0,0 +1,58 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+#import <QuartzCore/QuartzCore.h>
+#include "coreanim.h"
+
+void * MakeMetalLayer() {
+ return [[CAMetalLayer alloc] init];
+}
+
+uint16_t MetalLayer_PixelFormat(void * metalLayer) {
+ return ((CAMetalLayer *)metalLayer).pixelFormat;
+}
+
+void MetalLayer_SetDevice(void * metalLayer, void * device) {
+ ((CAMetalLayer *)metalLayer).device = (id<MTLDevice>)device;
+}
+
+const char * MetalLayer_SetPixelFormat(void * metalLayer, uint16_t pixelFormat) {
+ @try {
+ ((CAMetalLayer *)metalLayer).pixelFormat = (MTLPixelFormat)pixelFormat;
+ }
+ @catch (NSException * exception) {
+ return exception.reason.UTF8String;
+ }
+ return NULL;
+}
+
+const char * MetalLayer_SetMaximumDrawableCount(void * metalLayer, uint_t maximumDrawableCount) {
+ if (@available(macOS 10.13.2, *)) {
+ @try {
+ ((CAMetalLayer *)metalLayer).maximumDrawableCount = (NSUInteger)maximumDrawableCount;
+ }
+ @catch (NSException * exception) {
+ return exception.reason.UTF8String;
+ }
+ }
+ return NULL;
+}
+
+void MetalLayer_SetDisplaySyncEnabled(void * metalLayer, bool displaySyncEnabled) {
+ ((CAMetalLayer *)metalLayer).displaySyncEnabled = displaySyncEnabled;
+}
+
+void MetalLayer_SetDrawableSize(void * metalLayer, double width, double height) {
+ ((CAMetalLayer *)metalLayer).drawableSize = (CGSize){width, height};
+}
+
+void * MetalLayer_NextDrawable(void * metalLayer) {
+ return [(CAMetalLayer *)metalLayer nextDrawable];
+}
+
+void * MetalDrawable_Texture(void * metalDrawable) {
+ return ((id<CAMetalDrawable>)metalDrawable).texture;
+}
diff --git a/shiny/driver/mtldriver/mtldriver.go b/shiny/driver/mtldriver/mtldriver.go
new file mode 100644
index 0000000..b705be1
--- /dev/null
+++ b/shiny/driver/mtldriver/mtldriver.go
@@ -0,0 +1,252 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && cgo
+
+// Package mtldriver provides a Metal driver for accessing a screen.
+//
+// At this time, the Metal API is used only to present the final pixels
+// to the screen. All rendering is performed on the CPU via the image/draw
+// algorithms. Future work is to use mtl.Buffer, mtl.Texture, etc., and
+// do more of the rendering work on the GPU.
+package mtldriver
+
+import (
+ "runtime"
+ "unsafe"
+
+ "dmitri.shuralyov.com/gpu/mtl"
+ "github.com/go-gl/glfw/v3.3/glfw"
+ "golang.org/x/exp/shiny/driver/internal/errscreen"
+ "golang.org/x/exp/shiny/driver/mtldriver/internal/appkit"
+ "golang.org/x/exp/shiny/driver/mtldriver/internal/coreanim"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+func init() {
+ runtime.LockOSThread()
+}
+
+// Main is called by the program's main function to run the graphical
+// application.
+//
+// It calls f on the Screen, possibly in a separate goroutine, as some OS-
+// specific libraries require being on 'the main thread'. It returns when f
+// returns.
+func Main(f func(screen.Screen)) {
+ if err := main(f); err != nil {
+ f(errscreen.Stub(err))
+ }
+}
+
+func main(f func(screen.Screen)) error {
+ device, err := mtl.CreateSystemDefaultDevice()
+ if err != nil {
+ return err
+ }
+ err = glfw.Init()
+ if err != nil {
+ return err
+ }
+ defer glfw.Terminate()
+ glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
+ {
+ // TODO(dmitshur): Delete this when https://github.com/go-gl/glfw/issues/272 is resolved.
+ // Post an empty event from the main thread before it can happen in a non-main thread,
+ // to work around https://github.com/glfw/glfw/issues/1649.
+ glfw.PostEmptyEvent()
+ }
+ var (
+ done = make(chan struct{})
+ newWindowCh = make(chan newWindowReq, 1)
+ releaseWindowCh = make(chan releaseWindowReq, 1)
+ )
+ go func() {
+ f(&screenImpl{
+ newWindowCh: newWindowCh,
+ })
+ close(done)
+ glfw.PostEmptyEvent() // Break main loop out of glfw.WaitEvents so it can receive on done.
+ }()
+ for {
+ select {
+ case <-done:
+ return nil
+ case req := <-newWindowCh:
+ w, err := newWindow(device, releaseWindowCh, req.opts)
+ req.respCh <- newWindowResp{w, err}
+ case req := <-releaseWindowCh:
+ req.window.Destroy()
+ req.respCh <- struct{}{}
+ default:
+ glfw.WaitEvents()
+ }
+ }
+}
+
+type newWindowReq struct {
+ opts *screen.NewWindowOptions
+ respCh chan newWindowResp
+}
+
+type newWindowResp struct {
+ w screen.Window
+ err error
+}
+
+type releaseWindowReq struct {
+ window *glfw.Window
+ respCh chan struct{}
+}
+
+// newWindow creates a new GLFW window.
+// It must be called on the main thread.
+func newWindow(device mtl.Device, releaseWindowCh chan releaseWindowReq, opts *screen.NewWindowOptions) (screen.Window, error) {
+ width, height := optsSize(opts)
+ window, err := glfw.CreateWindow(width, height, opts.GetTitle(), nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ ml := coreanim.MakeMetalLayer()
+ ml.SetDevice(device)
+ ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
+ ml.SetMaximumDrawableCount(3)
+ ml.SetDisplaySyncEnabled(true)
+ cv := appkit.NewWindow(unsafe.Pointer(window.GetCocoaWindow())).ContentView()
+ cv.SetLayer(ml)
+ cv.SetWantsLayer(true)
+
+ w := &windowImpl{
+ device: device,
+ window: window,
+ releaseWindowCh: releaseWindowCh,
+ ml: ml,
+ cq: device.MakeCommandQueue(),
+ }
+
+ // Set callbacks.
+ framebufferSizeCallback := func(_ *glfw.Window, width, height int) {
+ w.Send(size.Event{
+ WidthPx: width,
+ HeightPx: height,
+ // TODO(dmitshur): ppp,
+ })
+ w.Send(paint.Event{External: true})
+ }
+ window.SetFramebufferSizeCallback(framebufferSizeCallback)
+ window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) {
+ const scale = 2 // TODO(dmitshur): compute dynamically
+ w.Send(mouse.Event{X: float32(x * scale), Y: float32(y * scale)})
+ })
+ window.SetMouseButtonCallback(func(_ *glfw.Window, b glfw.MouseButton, a glfw.Action, mods glfw.ModifierKey) {
+ btn := glfwMouseButton(b)
+ if btn == mouse.ButtonNone {
+ return
+ }
+ const scale = 2 // TODO(dmitshur): compute dynamically
+ x, y := window.GetCursorPos()
+ w.Send(mouse.Event{
+ X: float32(x * scale), Y: float32(y * scale),
+ Button: btn,
+ Direction: glfwMouseDirection(a),
+ // TODO(dmitshur): set Modifiers
+ })
+ })
+ window.SetKeyCallback(func(_ *glfw.Window, k glfw.Key, _ int, a glfw.Action, mods glfw.ModifierKey) {
+ code := glfwKeyCode(k)
+ if code == key.CodeUnknown {
+ return
+ }
+ w.Send(key.Event{
+ Code: code,
+ Direction: glfwKeyDirection(a),
+ // TODO(dmitshur): set Modifiers
+ })
+ })
+ // TODO(dmitshur): set CharModsCallback to catch text (runes) that are typed,
+ // and perhaps try to unify key pressed + character typed into single event
+ window.SetCloseCallback(func(*glfw.Window) {
+ w.lifecycler.SetDead(true)
+ w.lifecycler.SendEvent(w, nil)
+ })
+
+ // TODO(dmitshur): more fine-grained tracking of whether window is visible and/or focused
+ w.lifecycler.SetDead(false)
+ w.lifecycler.SetVisible(true)
+ w.lifecycler.SetFocused(true)
+ w.lifecycler.SendEvent(w, nil)
+
+ // Send the initial size and paint events.
+ width, height = window.GetFramebufferSize()
+ framebufferSizeCallback(window, width, height)
+
+ return w, nil
+}
+
+func optsSize(opts *screen.NewWindowOptions) (width, height int) {
+ width, height = 1024/2, 768/2
+ if opts != nil {
+ if opts.Width > 0 {
+ width = opts.Width
+ }
+ if opts.Height > 0 {
+ height = opts.Height
+ }
+ }
+ return width, height
+}
+
+func glfwMouseButton(button glfw.MouseButton) mouse.Button {
+ switch button {
+ case glfw.MouseButtonLeft:
+ return mouse.ButtonLeft
+ case glfw.MouseButtonRight:
+ return mouse.ButtonRight
+ case glfw.MouseButtonMiddle:
+ return mouse.ButtonMiddle
+ default:
+ return mouse.ButtonNone
+ }
+}
+
+func glfwMouseDirection(action glfw.Action) mouse.Direction {
+ switch action {
+ case glfw.Press:
+ return mouse.DirPress
+ case glfw.Release:
+ return mouse.DirRelease
+ default:
+ panic("unreachable")
+ }
+}
+
+func glfwKeyCode(k glfw.Key) key.Code {
+ // TODO(dmitshur): support more keys
+ switch k {
+ case glfw.KeyEnter:
+ return key.CodeReturnEnter
+ case glfw.KeyEscape:
+ return key.CodeEscape
+ default:
+ return key.CodeUnknown
+ }
+}
+
+func glfwKeyDirection(action glfw.Action) key.Direction {
+ switch action {
+ case glfw.Press:
+ return key.DirPress
+ case glfw.Release:
+ return key.DirRelease
+ case glfw.Repeat:
+ return key.DirNone
+ default:
+ panic("unreachable")
+ }
+}
diff --git a/shiny/driver/mtldriver/screen.go b/shiny/driver/mtldriver/screen.go
new file mode 100644
index 0000000..df6155c
--- /dev/null
+++ b/shiny/driver/mtldriver/screen.go
@@ -0,0 +1,42 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && cgo
+
+package mtldriver
+
+import (
+ "image"
+
+ "github.com/go-gl/glfw/v3.3/glfw"
+ "golang.org/x/exp/shiny/screen"
+)
+
+// screenImpl implements screen.Screen.
+type screenImpl struct {
+ newWindowCh chan newWindowReq
+}
+
+func (*screenImpl) NewBuffer(size image.Point) (screen.Buffer, error) {
+ return &bufferImpl{
+ rgba: image.NewRGBA(image.Rectangle{Max: size}),
+ }, nil
+}
+
+func (*screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
+ return &textureImpl{
+ rgba: image.NewRGBA(image.Rectangle{Max: size}),
+ }, nil
+}
+
+func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
+ respCh := make(chan newWindowResp)
+ s.newWindowCh <- newWindowReq{
+ opts: opts,
+ respCh: respCh,
+ }
+ glfw.PostEmptyEvent() // Break main loop out of glfw.WaitEvents so it can receive on newWindowCh.
+ resp := <-respCh
+ return resp.w, resp.err
+}
diff --git a/shiny/driver/mtldriver/texture.go b/shiny/driver/mtldriver/texture.go
new file mode 100644
index 0000000..a0277bb
--- /dev/null
+++ b/shiny/driver/mtldriver/texture.go
@@ -0,0 +1,32 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && cgo
+
+package mtldriver
+
+import (
+ "image"
+ "image/color"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/draw"
+)
+
+// textureImpl implements screen.Texture.
+type textureImpl struct {
+ rgba *image.RGBA
+}
+
+func (*textureImpl) Release() {}
+func (t *textureImpl) Size() image.Point { return t.rgba.Rect.Max }
+func (t *textureImpl) Bounds() image.Rectangle { return t.rgba.Rect }
+
+func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ draw.Draw(t.rgba, sr.Sub(sr.Min).Add(dp), src.RGBA(), sr.Min, draw.Src)
+}
+
+func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ draw.Draw(t.rgba, dr, &image.Uniform{src}, image.Point{}, op)
+}
diff --git a/shiny/driver/mtldriver/window.go b/shiny/driver/mtldriver/window.go
new file mode 100644
index 0000000..078a951
--- /dev/null
+++ b/shiny/driver/mtldriver/window.go
@@ -0,0 +1,119 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && cgo
+
+package mtldriver
+
+import (
+ "image"
+ "image/color"
+ "log"
+
+ "dmitri.shuralyov.com/gpu/mtl"
+ "github.com/go-gl/glfw/v3.3/glfw"
+ "golang.org/x/exp/shiny/driver/internal/drawer"
+ "golang.org/x/exp/shiny/driver/internal/event"
+ "golang.org/x/exp/shiny/driver/internal/lifecycler"
+ "golang.org/x/exp/shiny/driver/mtldriver/internal/coreanim"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/draw"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/size"
+)
+
+// windowImpl implements screen.Window.
+type windowImpl struct {
+ device mtl.Device
+ window *glfw.Window
+ releaseWindowCh chan releaseWindowReq
+ ml coreanim.MetalLayer
+ cq mtl.CommandQueue
+
+ event.Deque
+ lifecycler lifecycler.State
+
+ rgba *image.RGBA
+ texture mtl.Texture // Used in Publish.
+}
+
+func (w *windowImpl) Release() {
+ respCh := make(chan struct{})
+ w.releaseWindowCh <- releaseWindowReq{
+ window: w.window,
+ respCh: respCh,
+ }
+ glfw.PostEmptyEvent() // Break main loop out of glfw.WaitEvents so it can receive on releaseWindowCh.
+ <-respCh
+}
+
+func (w *windowImpl) NextEvent() interface{} {
+ e := w.Deque.NextEvent()
+ if sz, ok := e.(size.Event); ok {
+ // TODO(dmitshur): this is the best place/time/frequency to do this
+ // I've found so far, but see if it can be even better
+
+ // Set drawable size, create backing image and texture.
+ w.ml.SetDrawableSize(sz.WidthPx, sz.HeightPx)
+ w.rgba = image.NewRGBA(image.Rectangle{Max: image.Point{X: sz.WidthPx, Y: sz.HeightPx}})
+ w.texture = w.device.MakeTexture(mtl.TextureDescriptor{
+ PixelFormat: mtl.PixelFormatRGBA8UNorm,
+ Width: sz.WidthPx,
+ Height: sz.HeightPx,
+ StorageMode: mtl.StorageModeManaged,
+ })
+ }
+ return e
+}
+
+func (w *windowImpl) Publish() screen.PublishResult {
+ // Copy w.rgba pixels into a texture.
+ region := mtl.RegionMake2D(0, 0, w.texture.Width, w.texture.Height)
+ bytesPerRow := 4 * w.texture.Width
+ w.texture.ReplaceRegion(region, 0, &w.rgba.Pix[0], uintptr(bytesPerRow))
+
+ drawable, err := w.ml.NextDrawable()
+ if err != nil {
+ log.Println("Window.Publish: couldn't get the next drawable:", err)
+ return screen.PublishResult{}
+ }
+
+ cb := w.cq.MakeCommandBuffer()
+
+ // Copy the texture into the drawable.
+ bce := cb.MakeBlitCommandEncoder()
+ bce.CopyFromTexture(
+ w.texture, 0, 0, mtl.Origin{}, mtl.Size{w.texture.Width, w.texture.Height, 1},
+ drawable.Texture(), 0, 0, mtl.Origin{})
+ bce.EndEncoding()
+
+ cb.PresentDrawable(drawable)
+ cb.Commit()
+
+ return screen.PublishResult{}
+}
+
+func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ draw.Draw(w.rgba, sr.Sub(sr.Min).Add(dp), src.RGBA(), sr.Min, draw.Src)
+}
+
+func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ draw.Draw(w.rgba, dr, &image.Uniform{src}, image.Point{}, op)
+}
+
+func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, _ *screen.DrawOptions) {
+ draw.NearestNeighbor.Transform(w.rgba, src2dst, src.(*textureImpl).rgba, sr, op, nil)
+}
+
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, _ *screen.DrawOptions) {
+ draw.NearestNeighbor.Transform(w.rgba, src2dst, &image.Uniform{src}, sr, op, nil)
+}
+
+func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Copy(w, dp, src, sr, op, opts)
+}
+
+func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Scale(w, dr, src, sr, op, opts)
+}
diff --git a/shiny/driver/mtldriver_darwin.go b/shiny/driver/mtldriver_darwin.go
new file mode 100644
index 0000000..4e910c5
--- /dev/null
+++ b/shiny/driver/mtldriver_darwin.go
@@ -0,0 +1,16 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin && metal
+
+package driver
+
+import (
+ "golang.org/x/exp/shiny/driver/mtldriver"
+ "golang.org/x/exp/shiny/screen"
+)
+
+func main(f func(screen.Screen)) {
+ mtldriver.Main(f)
+}
diff --git a/shiny/driver/windriver/buffer.go b/shiny/driver/windriver/buffer.go
new file mode 100644
index 0000000..10e42c7
--- /dev/null
+++ b/shiny/driver/windriver/buffer.go
@@ -0,0 +1,101 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package windriver
+
+import (
+ "image"
+ "image/draw"
+ "sync"
+ "syscall"
+
+ "golang.org/x/exp/shiny/driver/internal/swizzle"
+)
+
+type bufferImpl struct {
+ hbitmap syscall.Handle
+ buf []byte
+ rgba image.RGBA
+ size image.Point
+
+ mu sync.Mutex
+ nUpload uint32
+ released bool
+ cleanedUp bool
+}
+
+func (b *bufferImpl) Size() image.Point { return b.size }
+func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
+func (b *bufferImpl) RGBA() *image.RGBA { return &b.rgba }
+
+func (b *bufferImpl) preUpload() {
+ // Check that the program hasn't tried to modify the rgba field via the
+ // pointer returned by the bufferImpl.RGBA method. This check doesn't catch
+ // 100% of all cases; it simply tries to detect some invalid uses of a
+ // screen.Buffer such as:
+ // *buffer.RGBA() = anotherImageRGBA
+ if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] {
+ panic("windriver: invalid Buffer.RGBA modification")
+ }
+
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ if b.released {
+ panic("windriver: Buffer.Upload called after Buffer.Release")
+ }
+ if b.nUpload == 0 {
+ swizzle.BGRA(b.buf)
+ }
+ b.nUpload++
+}
+
+func (b *bufferImpl) postUpload() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ b.nUpload--
+ if b.nUpload != 0 {
+ return
+ }
+
+ if b.released {
+ go b.cleanUp()
+ } else {
+ swizzle.BGRA(b.buf)
+ }
+}
+
+func (b *bufferImpl) Release() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ if !b.released && b.nUpload == 0 {
+ go b.cleanUp()
+ }
+ b.released = true
+}
+
+func (b *bufferImpl) cleanUp() {
+ b.mu.Lock()
+ if b.cleanedUp {
+ b.mu.Unlock()
+ panic("windriver: Buffer clean-up occurred twice")
+ }
+ b.cleanedUp = true
+ b.mu.Unlock()
+
+ b.rgba.Pix = nil
+ _DeleteObject(b.hbitmap)
+}
+
+func (b *bufferImpl) blitToDC(dc syscall.Handle, dp image.Point, sr image.Rectangle) error {
+ b.preUpload()
+ defer b.postUpload()
+
+ dr := sr.Add(dp.Sub(sr.Min))
+ return copyBitmapToDC(dc, dr, b.hbitmap, sr, draw.Src)
+}
diff --git a/shiny/driver/windriver/doc.go b/shiny/driver/windriver/doc.go
new file mode 100644
index 0000000..6ab6606
--- /dev/null
+++ b/shiny/driver/windriver/doc.go
@@ -0,0 +1,44 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package windriver provides the Windows driver for accessing a screen.
+package windriver // import "golang.org/x/exp/shiny/driver/windriver"
+
+/*
+Implementation Details
+
+On Windows, GUI is managed via user code and OS sending messages to
+a window. These messages include paint events, input events and others.
+Any thread that hosts GUI must handle incoming window messages through
+a "message loop".
+
+windriver designates the thread that calls Main as the GUI thread.
+It locks this thread, creates a special window to handle screen.Screen
+calls and runs message loop. All new windows are created by the
+same thread, so message loop above handles all their window messages.
+
+Some general Windows rules about thread affinity of GUI objects:
+
+part 1: Window handles
+https://blogs.msdn.microsoft.com/oldnewthing/20051010-09/?p=33843
+
+part 2: Device contexts
+https://blogs.msdn.microsoft.com/oldnewthing/20051011-10/?p=33823
+
+part 3: Menus, icons, cursors, and accelerator tables
+https://blogs.msdn.microsoft.com/oldnewthing/20051012-00/?p=33803
+
+part 4: GDI objects and other notes on affinity
+https://blogs.msdn.microsoft.com/oldnewthing/20051013-11/?p=33783
+
+part 5: Object clean-up
+https://blogs.msdn.microsoft.com/oldnewthing/20051014-19/?p=33763
+
+How to build Windows GUI articles:
+
+http://www.codeproject.com/Articles/1988/Guide-to-WIN-Paint-for-Beginners
+http://www.codeproject.com/Articles/2078/Guide-to-WIN-Paint-for-Intermediates
+http://www.codeproject.com/Articles/224754/Guide-to-Win-Memory-DC
+
+*/
diff --git a/shiny/driver/windriver/other.go b/shiny/driver/windriver/other.go
new file mode 100644
index 0000000..f533187
--- /dev/null
+++ b/shiny/driver/windriver/other.go
@@ -0,0 +1,26 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !windows
+
+package windriver
+
+import (
+ "fmt"
+ "runtime"
+
+ "golang.org/x/exp/shiny/driver/internal/errscreen"
+ "golang.org/x/exp/shiny/screen"
+)
+
+// Main is called by the program's main function to run the graphical
+// application.
+//
+// It calls f on the Screen, possibly in a separate goroutine, as some OS-
+// specific libraries require being on 'the main thread'. It returns when f
+// returns.
+func Main(f func(screen.Screen)) {
+ f(errscreen.Stub(fmt.Errorf(
+ "windriver: unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)))
+}
diff --git a/shiny/driver/windriver/screen.go b/shiny/driver/windriver/screen.go
new file mode 100644
index 0000000..bc64df0
--- /dev/null
+++ b/shiny/driver/windriver/screen.go
@@ -0,0 +1,84 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package windriver
+
+import (
+ "fmt"
+ "image"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/internal/win32"
+ "golang.org/x/exp/shiny/screen"
+)
+
+var theScreen = &screenImpl{
+ windows: make(map[syscall.Handle]*windowImpl),
+}
+
+type screenImpl struct {
+ mu sync.Mutex
+ windows map[syscall.Handle]*windowImpl
+}
+
+func (*screenImpl) NewBuffer(size image.Point) (screen.Buffer, error) {
+ // Buffer length must fit in BITMAPINFO.Header.SizeImage (uint32), as
+ // well as in Go slice length (int). It's easiest to be consistent
+ // between 32-bit and 64-bit, so we just use int32.
+ const (
+ maxInt32 = 0x7fffffff
+ maxBufLen = maxInt32
+ )
+ if size.X < 0 || size.X > maxInt32 || size.Y < 0 || size.Y > maxInt32 || int64(size.X)*int64(size.Y)*4 > maxBufLen {
+ return nil, fmt.Errorf("windriver: invalid buffer size %v", size)
+ }
+
+ hbitmap, bitvalues, err := mkbitmap(size)
+ if err != nil {
+ return nil, err
+ }
+ bufLen := 4 * size.X * size.Y
+ array := (*[maxBufLen]byte)(unsafe.Pointer(bitvalues))
+ buf := (*array)[:bufLen:bufLen]
+ return &bufferImpl{
+ hbitmap: hbitmap,
+ buf: buf,
+ rgba: image.RGBA{
+ Pix: buf,
+ Stride: 4 * size.X,
+ Rect: image.Rectangle{Max: size},
+ },
+ size: size,
+ }, nil
+}
+
+func (*screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
+ return newTexture(size)
+}
+
+func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
+ w := &windowImpl{}
+
+ var err error
+ w.hwnd, err = win32.NewWindow(opts)
+ if err != nil {
+ return nil, err
+ }
+
+ s.mu.Lock()
+ s.windows[w.hwnd] = w
+ s.mu.Unlock()
+
+ err = win32.ResizeClientRect(w.hwnd, opts)
+ if err != nil {
+ return nil, err
+ }
+
+ win32.Show(w.hwnd)
+ return w, nil
+}
diff --git a/shiny/driver/windriver/syscall.go b/shiny/driver/windriver/syscall.go
new file mode 100644
index 0000000..e479032
--- /dev/null
+++ b/shiny/driver/windriver/syscall.go
@@ -0,0 +1,7 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
+
+package windriver
diff --git a/shiny/driver/windriver/syscall_windows.go b/shiny/driver/windriver/syscall_windows.go
new file mode 100644
index 0000000..65a20ec
--- /dev/null
+++ b/shiny/driver/windriver/syscall_windows.go
@@ -0,0 +1,167 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package windriver
+
+import (
+ "unsafe"
+)
+
+type _COLORREF uint32
+
+func _RGB(r, g, b byte) _COLORREF {
+ return _COLORREF(r) | _COLORREF(g)<<8 | _COLORREF(b)<<16
+}
+
+type _POINT struct {
+ X int32
+ Y int32
+}
+
+type _RECT struct {
+ Left int32
+ Top int32
+ Right int32
+ Bottom int32
+}
+
+type _BITMAPINFOHEADER struct {
+ Size uint32
+ Width int32
+ Height int32
+ Planes uint16
+ BitCount uint16
+ Compression uint32
+ SizeImage uint32
+ XPelsPerMeter int32
+ YPelsPerMeter int32
+ ClrUsed uint32
+ ClrImportant uint32
+}
+
+type _RGBQUAD struct {
+ Blue byte
+ Green byte
+ Red byte
+ Reserved byte
+}
+
+type _BITMAPINFO struct {
+ Header _BITMAPINFOHEADER
+ Colors [1]_RGBQUAD
+}
+
+type _BLENDFUNCTION struct {
+ BlendOp byte
+ BlendFlags byte
+ SourceConstantAlpha byte
+ AlphaFormat byte
+}
+
+// ToUintptr helps to pass bf to syscall.Syscall.
+func (bf _BLENDFUNCTION) ToUintptr() uintptr {
+ return *((*uintptr)(unsafe.Pointer(&bf)))
+}
+
+type _XFORM struct {
+ eM11 float32
+ eM12 float32
+ eM21 float32
+ eM22 float32
+ eDx float32
+ eDy float32
+}
+
+const (
+ _WM_PAINT = 15
+ _WM_WINDOWPOSCHANGED = 71
+ _WM_KEYDOWN = 256
+ _WM_KEYUP = 257
+ _WM_SYSKEYDOWN = 260
+ _WM_SYSKEYUP = 261
+ _WM_MOUSEMOVE = 512
+ _WM_LBUTTONDOWN = 513
+ _WM_LBUTTONUP = 514
+ _WM_RBUTTONDOWN = 516
+ _WM_RBUTTONUP = 517
+ _WM_MBUTTONDOWN = 519
+ _WM_MBUTTONUP = 520
+ _WM_USER = 0x0400
+)
+
+const (
+ _WS_OVERLAPPED = 0x00000000
+ _WS_CAPTION = 0x00C00000
+ _WS_SYSMENU = 0x00080000
+ _WS_THICKFRAME = 0x00040000
+ _WS_MINIMIZEBOX = 0x00020000
+ _WS_MAXIMIZEBOX = 0x00010000
+ _WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME | _WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
+)
+
+const (
+ _COLOR_BTNFACE = 15
+)
+
+const (
+ _IDI_APPLICATION = 32512
+ _IDC_ARROW = 32512
+)
+
+const (
+ _BI_RGB = 0
+ _DIB_RGB_COLORS = 0
+
+ _AC_SRC_OVER = 0x00
+ _AC_SRC_ALPHA = 0x01
+
+ _SRCCOPY = 0x00cc0020
+
+ _SHADEBLENDCAPS = 120
+ _SB_NONE = 0
+)
+
+const (
+ _GM_COMPATIBLE = 1
+ _GM_ADVANCED = 2
+
+ _MWT_IDENTITY = 1
+)
+
+func _GET_X_LPARAM(lp uintptr) int32 {
+ return int32(_LOWORD(lp))
+}
+
+func _GET_Y_LPARAM(lp uintptr) int32 {
+ return int32(_HIWORD(lp))
+}
+
+func _LOWORD(l uintptr) uint16 {
+ return uint16(uint32(l))
+}
+
+func _HIWORD(l uintptr) uint16 {
+ return uint16(uint32(l >> 16))
+}
+
+// notes to self
+// UINT = uint32
+// callbacks = uintptr
+// strings = *uint16
+
+//sys _AlphaBlend(dcdest syscall.Handle, xoriginDest int32, yoriginDest int32, wDest int32, hDest int32, dcsrc syscall.Handle, xoriginSrc int32, yoriginSrc int32, wsrc int32, hsrc int32, ftn uintptr) (err error) = msimg32.AlphaBlend
+//sys _BitBlt(dcdest syscall.Handle, xdest int32, ydest int32, width int32, height int32, dcsrc syscall.Handle, xsrc int32, ysrc int32, rop uint32) (err error) = gdi32.BitBlt
+//sys _CreateCompatibleBitmap(dc syscall.Handle, width int32, height int32) (bitmap syscall.Handle, err error) = gdi32.CreateCompatibleBitmap
+//sys _CreateCompatibleDC(dc syscall.Handle) (newdc syscall.Handle, err error) = gdi32.CreateCompatibleDC
+//sys _CreateDIBSection(dc syscall.Handle, bmi *_BITMAPINFO, usage uint32, bits **byte, section syscall.Handle, offset uint32) (bitmap syscall.Handle, err error) = gdi32.CreateDIBSection
+//sys _CreateSolidBrush(color _COLORREF) (brush syscall.Handle, err error) = gdi32.CreateSolidBrush
+//sys _DeleteDC(dc syscall.Handle) (err error) = gdi32.DeleteDC
+//sys _DeleteObject(object syscall.Handle) (err error) = gdi32.DeleteObject
+//sys _FillRect(dc syscall.Handle, rc *_RECT, brush syscall.Handle) (err error) = user32.FillRect
+//sys _ModifyWorldTransform(dc syscall.Handle, x *_XFORM, mode uint32) (err error) = gdi32.ModifyWorldTransform
+//sys _SelectObject(dc syscall.Handle, gdiobj syscall.Handle) (newobj syscall.Handle, err error) = gdi32.SelectObject
+//sys _SetGraphicsMode(dc syscall.Handle, mode int32) (oldmode int32, err error) = gdi32.SetGraphicsMode
+//sys _SetWorldTransform(dc syscall.Handle, x *_XFORM) (err error) = gdi32.SetWorldTransform
+//sys _StretchBlt(dcdest syscall.Handle, xdest int32, ydest int32, wdest int32, hdest int32, dcsrc syscall.Handle, xsrc int32, ysrc int32, wsrc int32, hsrc int32, rop uint32) (err error) = gdi32.StretchBlt
+//sys _GetDeviceCaps(dc syscall.Handle, index int32) (ret int32) = gdi32.GetDeviceCaps
diff --git a/shiny/driver/windriver/texture.go b/shiny/driver/windriver/texture.go
new file mode 100644
index 0000000..5857686
--- /dev/null
+++ b/shiny/driver/windriver/texture.go
@@ -0,0 +1,161 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package windriver
+
+import (
+ "errors"
+ "image"
+ "image/color"
+ "image/draw"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/internal/win32"
+ "golang.org/x/exp/shiny/screen"
+)
+
+type textureImpl struct {
+ size image.Point
+ dc syscall.Handle
+ bitmap syscall.Handle
+
+ mu sync.Mutex
+ released bool
+}
+
+type handleCreateTextureParams struct {
+ size image.Point
+ dc syscall.Handle
+ bitmap syscall.Handle
+ err error
+}
+
+var msgCreateTexture = win32.AddScreenMsg(handleCreateTexture)
+
+func newTexture(size image.Point) (screen.Texture, error) {
+ p := handleCreateTextureParams{size: size}
+ win32.SendScreenMessage(msgCreateTexture, 0, uintptr(unsafe.Pointer(&p)))
+ if p.err != nil {
+ return nil, p.err
+ }
+ return &textureImpl{
+ size: size,
+ dc: p.dc,
+ bitmap: p.bitmap,
+ }, nil
+}
+
+func handleCreateTexture(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) {
+ // This code needs to run on Windows message pump thread.
+ // Firstly, it calls GetDC(nil) and, according to Windows documentation
+ // (https://msdn.microsoft.com/en-us/library/windows/desktop/dd144871(v=vs.85).aspx),
+ // has to be released on the same thread.
+ // Secondly, according to Windows documentation
+ // (https://msdn.microsoft.com/en-us/library/windows/desktop/dd183489(v=vs.85).aspx),
+ // ... thread that calls CreateCompatibleDC owns the HDC that is created.
+ // When this thread is destroyed, the HDC is no longer valid. ...
+ // So making Windows message pump thread own returned HDC makes DC
+ // live as long as we want to.
+ p := (*handleCreateTextureParams)(unsafe.Pointer(lParam))
+
+ screenDC, err := win32.GetDC(0)
+ if err != nil {
+ p.err = err
+ return
+ }
+ defer win32.ReleaseDC(0, screenDC)
+
+ dc, err := _CreateCompatibleDC(screenDC)
+ if err != nil {
+ p.err = err
+ return
+ }
+ bitmap, err := _CreateCompatibleBitmap(screenDC, int32(p.size.X), int32(p.size.Y))
+ if err != nil {
+ _DeleteDC(dc)
+ p.err = err
+ return
+ }
+ p.dc = dc
+ p.bitmap = bitmap
+}
+
+func (t *textureImpl) Bounds() image.Rectangle {
+ return image.Rectangle{Max: t.size}
+}
+
+func (t *textureImpl) Fill(r image.Rectangle, c color.Color, op draw.Op) {
+ err := t.update(func(dc syscall.Handle) error {
+ return fill(dc, r, c, op)
+ })
+ if err != nil {
+ panic(err) // TODO handle error
+ }
+}
+
+func (t *textureImpl) Release() {
+ if err := t.release(); err != nil {
+ panic(err) // TODO handle error
+ }
+}
+
+func (t *textureImpl) release() error {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ if t.released {
+ return nil
+ }
+ t.released = true
+
+ err := _DeleteObject(t.bitmap)
+ if err != nil {
+ return err
+ }
+ return _DeleteDC(t.dc)
+}
+
+func (t *textureImpl) Size() image.Point {
+ return t.size
+}
+
+func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ err := t.update(func(dc syscall.Handle) error {
+ return src.(*bufferImpl).blitToDC(dc, dp, sr)
+ })
+ if err != nil {
+ panic(err) // TODO handle error
+ }
+}
+
+// update prepares texture t for update and executes f over texture device
+// context dc in a safe manner.
+func (t *textureImpl) update(f func(dc syscall.Handle) error) (retErr error) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ if t.released {
+ return errors.New("windriver: Texture.Upload called after Texture.Release")
+ }
+
+ // Select t.bitmap into t.dc, so our drawing gets recorded
+ // into t.bitmap and not into 1x1 default bitmap created
+ // during CreateCompatibleDC call.
+ prev, err := _SelectObject(t.dc, t.bitmap)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _, err2 := _SelectObject(t.dc, prev)
+ if retErr == nil {
+ retErr = err2
+ }
+ }()
+
+ return f(t.dc)
+}
diff --git a/shiny/driver/windriver/window.go b/shiny/driver/windriver/window.go
new file mode 100644
index 0000000..3937f4b
--- /dev/null
+++ b/shiny/driver/windriver/window.go
@@ -0,0 +1,273 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package windriver
+
+// TODO: implement a back buffer.
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "math"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/exp/shiny/driver/internal/drawer"
+ "golang.org/x/exp/shiny/driver/internal/event"
+ "golang.org/x/exp/shiny/driver/internal/win32"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+type windowImpl struct {
+ hwnd syscall.Handle
+
+ event.Deque
+
+ sz size.Event
+ lifecycleStage lifecycle.Stage
+}
+
+func (w *windowImpl) Release() {
+ win32.Release(w.hwnd)
+}
+
+func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ src.(*bufferImpl).preUpload()
+ defer src.(*bufferImpl).postUpload()
+
+ w.execCmd(&cmd{
+ id: cmdUpload,
+ dp: dp,
+ buffer: src.(*bufferImpl),
+ sr: sr,
+ })
+}
+
+func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ w.execCmd(&cmd{
+ id: cmdFill,
+ dr: dr,
+ color: src,
+ op: op,
+ })
+}
+
+func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ if op != draw.Src && op != draw.Over {
+ // TODO:
+ return
+ }
+ w.execCmd(&cmd{
+ id: cmdDraw,
+ src2dst: src2dst,
+ texture: src.(*textureImpl).bitmap,
+ sr: sr,
+ op: op,
+ })
+}
+
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ if op != draw.Src && op != draw.Over {
+ // TODO:
+ return
+ }
+ w.execCmd(&cmd{
+ id: cmdDrawUniform,
+ src2dst: src2dst,
+ color: src,
+ sr: sr,
+ op: op,
+ })
+}
+
+func drawWindow(dc syscall.Handle, src2dst f64.Aff3, src interface{}, sr image.Rectangle, op draw.Op) (retErr error) {
+ var dr image.Rectangle
+ if src2dst[1] != 0 || src2dst[3] != 0 {
+ // general drawing
+ dr = sr.Sub(sr.Min)
+
+ prevmode, err := _SetGraphicsMode(dc, _GM_ADVANCED)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _, err := _SetGraphicsMode(dc, prevmode)
+ if retErr == nil {
+ retErr = err
+ }
+ }()
+
+ x := _XFORM{
+ eM11: +float32(src2dst[0]),
+ eM12: -float32(src2dst[1]),
+ eM21: -float32(src2dst[3]),
+ eM22: +float32(src2dst[4]),
+ eDx: +float32(src2dst[2]),
+ eDy: +float32(src2dst[5]),
+ }
+ err = _SetWorldTransform(dc, &x)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ err := _ModifyWorldTransform(dc, nil, _MWT_IDENTITY)
+ if retErr == nil {
+ retErr = err
+ }
+ }()
+ } else if src2dst[0] == 1 && src2dst[4] == 1 {
+ // copy bitmap
+ dr = sr.Add(image.Point{int(src2dst[2]), int(src2dst[5])})
+ } else {
+ // scale bitmap
+ dstXMin := float64(sr.Min.X)*src2dst[0] + src2dst[2]
+ dstXMax := float64(sr.Max.X)*src2dst[0] + src2dst[2]
+ if dstXMin > dstXMax {
+ // TODO: check if this (and below) works when src2dst[0] < 0.
+ dstXMin, dstXMax = dstXMax, dstXMin
+ }
+ dstYMin := float64(sr.Min.Y)*src2dst[4] + src2dst[5]
+ dstYMax := float64(sr.Max.Y)*src2dst[4] + src2dst[5]
+ if dstYMin > dstYMax {
+ // TODO: check if this (and below) works when src2dst[4] < 0.
+ dstYMin, dstYMax = dstYMax, dstYMin
+ }
+ dr = image.Rectangle{
+ image.Point{int(math.Floor(dstXMin)), int(math.Floor(dstYMin))},
+ image.Point{int(math.Ceil(dstXMax)), int(math.Ceil(dstYMax))},
+ }
+ }
+ switch s := src.(type) {
+ case syscall.Handle:
+ return copyBitmapToDC(dc, dr, s, sr, op)
+ case color.Color:
+ return fill(dc, dr, s, op)
+ }
+ return fmt.Errorf("unsupported type %T", src)
+}
+
+func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Copy(w, dp, src, sr, op, opts)
+}
+
+func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Scale(w, dr, src, sr, op, opts)
+}
+
+func (w *windowImpl) Publish() screen.PublishResult {
+ // TODO
+ return screen.PublishResult{}
+}
+
+func init() {
+ send := func(hwnd syscall.Handle, e interface{}) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[hwnd]
+ theScreen.mu.Unlock()
+
+ w.Send(e)
+ }
+ win32.MouseEvent = func(hwnd syscall.Handle, e mouse.Event) { send(hwnd, e) }
+ win32.PaintEvent = func(hwnd syscall.Handle, e paint.Event) { send(hwnd, e) }
+ win32.KeyEvent = func(hwnd syscall.Handle, e key.Event) { send(hwnd, e) }
+ win32.LifecycleEvent = lifecycleEvent
+ win32.SizeEvent = sizeEvent
+}
+
+func lifecycleEvent(hwnd syscall.Handle, to lifecycle.Stage) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[hwnd]
+ theScreen.mu.Unlock()
+
+ if w.lifecycleStage == to {
+ return
+ }
+ w.Send(lifecycle.Event{
+ From: w.lifecycleStage,
+ To: to,
+ })
+ w.lifecycleStage = to
+}
+
+func sizeEvent(hwnd syscall.Handle, e size.Event) {
+ theScreen.mu.Lock()
+ w := theScreen.windows[hwnd]
+ theScreen.mu.Unlock()
+
+ w.Send(e)
+
+ if e != w.sz {
+ w.sz = e
+ w.Send(paint.Event{})
+ }
+}
+
+// cmd is used to carry parameters between user code
+// and Windows message pump thread.
+type cmd struct {
+ id int
+ err error
+
+ src2dst f64.Aff3
+ sr image.Rectangle
+ dp image.Point
+ dr image.Rectangle
+ color color.Color
+ op draw.Op
+ texture syscall.Handle
+ buffer *bufferImpl
+}
+
+const (
+ cmdDraw = iota
+ cmdFill
+ cmdUpload
+ cmdDrawUniform
+)
+
+var msgCmd = win32.AddWindowMsg(handleCmd)
+
+func (w *windowImpl) execCmd(c *cmd) {
+ win32.SendMessage(w.hwnd, msgCmd, 0, uintptr(unsafe.Pointer(c)))
+ if c.err != nil {
+ panic(fmt.Sprintf("execCmd faild for cmd.id=%d: %v", c.id, c.err)) // TODO handle errors
+ }
+}
+
+func handleCmd(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) {
+ c := (*cmd)(unsafe.Pointer(lParam))
+
+ dc, err := win32.GetDC(hwnd)
+ if err != nil {
+ c.err = err
+ return
+ }
+ defer win32.ReleaseDC(hwnd, dc)
+
+ switch c.id {
+ case cmdDraw:
+ c.err = drawWindow(dc, c.src2dst, c.texture, c.sr, c.op)
+ case cmdDrawUniform:
+ c.err = drawWindow(dc, c.src2dst, c.color, c.sr, c.op)
+ case cmdFill:
+ c.err = fill(dc, c.dr, c.color, c.op)
+ case cmdUpload:
+ // TODO: adjust if dp is outside dst bounds, or sr is outside buffer bounds.
+ dr := c.sr.Add(c.dp.Sub(c.sr.Min))
+ c.err = copyBitmapToDC(dc, dr, c.buffer.hbitmap, c.sr, draw.Src)
+ default:
+ c.err = fmt.Errorf("unknown command id=%d", c.id)
+ }
+ return
+}
diff --git a/shiny/driver/windriver/windraw.go b/shiny/driver/windriver/windraw.go
new file mode 100644
index 0000000..4e3fd2f
--- /dev/null
+++ b/shiny/driver/windriver/windraw.go
@@ -0,0 +1,122 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package windriver
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "syscall"
+ "unsafe"
+)
+
+func mkbitmap(size image.Point) (syscall.Handle, *byte, error) {
+ bi := _BITMAPINFO{
+ Header: _BITMAPINFOHEADER{
+ Size: uint32(unsafe.Sizeof(_BITMAPINFOHEADER{})),
+ Width: int32(size.X),
+ Height: -int32(size.Y), // negative height to force top-down drawing
+ Planes: 1,
+ BitCount: 32,
+ Compression: _BI_RGB,
+ SizeImage: uint32(size.X * size.Y * 4),
+ },
+ }
+
+ var ppvBits *byte
+ bitmap, err := _CreateDIBSection(0, &bi, _DIB_RGB_COLORS, &ppvBits, 0, 0)
+ if err != nil {
+ return 0, nil, err
+ }
+ return bitmap, ppvBits, nil
+}
+
+var blendOverFunc = _BLENDFUNCTION{
+ BlendOp: _AC_SRC_OVER,
+ BlendFlags: 0,
+ SourceConstantAlpha: 255, // only use per-pixel alphas
+ AlphaFormat: _AC_SRC_ALPHA, // premultiplied
+}
+
+func copyBitmapToDC(dc syscall.Handle, dr image.Rectangle, src syscall.Handle, sr image.Rectangle, op draw.Op) (retErr error) {
+ memdc, err := _CreateCompatibleDC(dc)
+ if err != nil {
+ return err
+ }
+ defer _DeleteDC(memdc)
+
+ prev, err := _SelectObject(memdc, src)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _, err2 := _SelectObject(memdc, prev)
+ if retErr == nil {
+ retErr = err2
+ }
+ }()
+
+ if _GetDeviceCaps(dc, _SHADEBLENDCAPS) == _SB_NONE {
+ // This output device does not support blending capabilities,
+ // so the subsequent output is incorrect, but is the best we
+ // can do on systems that do not support AlphaBlend.
+ op = draw.Src
+ }
+
+ switch op {
+ case draw.Src:
+ return _StretchBlt(dc, int32(dr.Min.X), int32(dr.Min.Y), int32(dr.Dx()), int32(dr.Dy()),
+ memdc, int32(sr.Min.X), int32(sr.Min.Y), int32(sr.Dx()), int32(sr.Dy()), _SRCCOPY)
+ case draw.Over:
+ return _AlphaBlend(dc, int32(dr.Min.X), int32(dr.Min.Y), int32(dr.Dx()), int32(dr.Dy()),
+ memdc, int32(sr.Min.X), int32(sr.Min.Y), int32(sr.Dx()), int32(sr.Dy()), blendOverFunc.ToUintptr())
+ default:
+ return fmt.Errorf("windriver: invalid draw operation %v", op)
+ }
+}
+
+func fill(dc syscall.Handle, dr image.Rectangle, c color.Color, op draw.Op) error {
+ r, g, b, a := c.RGBA()
+ r >>= 8
+ g >>= 8
+ b >>= 8
+ a >>= 8
+
+ if op == draw.Src {
+ color := _RGB(byte(r), byte(g), byte(b))
+ brush, err := _CreateSolidBrush(color)
+ if err != nil {
+ return err
+ }
+ defer _DeleteObject(brush)
+
+ rect := _RECT{
+ Left: int32(dr.Min.X),
+ Top: int32(dr.Min.Y),
+ Right: int32(dr.Max.X),
+ Bottom: int32(dr.Max.Y),
+ }
+ return _FillRect(dc, &rect, brush)
+ }
+
+ // AlphaBlend will stretch the input image (using StretchBlt's
+ // COLORONCOLOR mode) to fill the output rectangle. Testing
+ // this shows that the result appears to be the same as if we had
+ // used a MxN bitmap instead.
+ sr := image.Rect(0, 0, 1, 1)
+ bitmap, bitvalues, err := mkbitmap(sr.Size())
+ if err != nil {
+ return err
+ }
+ defer _DeleteObject(bitmap) // TODO handle error?
+
+ color := _COLORREF((a << 24) | (r << 16) | (g << 8) | b)
+ *(*_COLORREF)(unsafe.Pointer(bitvalues)) = color
+
+ return copyBitmapToDC(dc, dr, bitmap, sr, draw.Over)
+}
diff --git a/shiny/driver/windriver/windriver.go b/shiny/driver/windriver/windriver.go
new file mode 100644
index 0000000..b6d0fb6
--- /dev/null
+++ b/shiny/driver/windriver/windriver.go
@@ -0,0 +1,25 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package windriver
+
+import (
+ "golang.org/x/exp/shiny/driver/internal/errscreen"
+ "golang.org/x/exp/shiny/driver/internal/win32"
+ "golang.org/x/exp/shiny/screen"
+)
+
+// Main is called by the program's main function to run the graphical
+// application.
+//
+// It calls f on the Screen, possibly in a separate goroutine, as some OS-
+// specific libraries require being on 'the main thread'. It returns when f
+// returns.
+func Main(f func(screen.Screen)) {
+ if err := win32.Main(func() { f(theScreen) }); err != nil {
+ f(errscreen.Stub(err))
+ }
+}
diff --git a/shiny/driver/windriver/zsyscall_windows.go b/shiny/driver/windriver/zsyscall_windows.go
new file mode 100644
index 0000000..18047b1
--- /dev/null
+++ b/shiny/driver/windriver/zsyscall_windows.go
@@ -0,0 +1,214 @@
+// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
+
+package windriver
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var _ unsafe.Pointer
+
+var (
+ modmsimg32 = windows.NewLazySystemDLL("msimg32.dll")
+ modgdi32 = windows.NewLazySystemDLL("gdi32.dll")
+ moduser32 = windows.NewLazySystemDLL("user32.dll")
+
+ procAlphaBlend = modmsimg32.NewProc("AlphaBlend")
+ procBitBlt = modgdi32.NewProc("BitBlt")
+ procCreateCompatibleBitmap = modgdi32.NewProc("CreateCompatibleBitmap")
+ procCreateCompatibleDC = modgdi32.NewProc("CreateCompatibleDC")
+ procCreateDIBSection = modgdi32.NewProc("CreateDIBSection")
+ procCreateSolidBrush = modgdi32.NewProc("CreateSolidBrush")
+ procDeleteDC = modgdi32.NewProc("DeleteDC")
+ procDeleteObject = modgdi32.NewProc("DeleteObject")
+ procFillRect = moduser32.NewProc("FillRect")
+ procModifyWorldTransform = modgdi32.NewProc("ModifyWorldTransform")
+ procSelectObject = modgdi32.NewProc("SelectObject")
+ procSetGraphicsMode = modgdi32.NewProc("SetGraphicsMode")
+ procSetWorldTransform = modgdi32.NewProc("SetWorldTransform")
+ procStretchBlt = modgdi32.NewProc("StretchBlt")
+ procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps")
+)
+
+func _AlphaBlend(dcdest syscall.Handle, xoriginDest int32, yoriginDest int32, wDest int32, hDest int32, dcsrc syscall.Handle, xoriginSrc int32, yoriginSrc int32, wsrc int32, hsrc int32, ftn uintptr) (err error) {
+ r1, _, e1 := syscall.Syscall12(procAlphaBlend.Addr(), 11, uintptr(dcdest), uintptr(xoriginDest), uintptr(yoriginDest), uintptr(wDest), uintptr(hDest), uintptr(dcsrc), uintptr(xoriginSrc), uintptr(yoriginSrc), uintptr(wsrc), uintptr(hsrc), uintptr(ftn), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _BitBlt(dcdest syscall.Handle, xdest int32, ydest int32, width int32, height int32, dcsrc syscall.Handle, xsrc int32, ysrc int32, rop uint32) (err error) {
+ r1, _, e1 := syscall.Syscall9(procBitBlt.Addr(), 9, uintptr(dcdest), uintptr(xdest), uintptr(ydest), uintptr(width), uintptr(height), uintptr(dcsrc), uintptr(xsrc), uintptr(ysrc), uintptr(rop))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _CreateCompatibleBitmap(dc syscall.Handle, width int32, height int32) (bitmap syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procCreateCompatibleBitmap.Addr(), 3, uintptr(dc), uintptr(width), uintptr(height))
+ bitmap = syscall.Handle(r0)
+ if bitmap == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _CreateCompatibleDC(dc syscall.Handle) (newdc syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procCreateCompatibleDC.Addr(), 1, uintptr(dc), 0, 0)
+ newdc = syscall.Handle(r0)
+ if newdc == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _CreateDIBSection(dc syscall.Handle, bmi *_BITMAPINFO, usage uint32, bits **byte, section syscall.Handle, offset uint32) (bitmap syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall6(procCreateDIBSection.Addr(), 6, uintptr(dc), uintptr(unsafe.Pointer(bmi)), uintptr(usage), uintptr(unsafe.Pointer(bits)), uintptr(section), uintptr(offset))
+ bitmap = syscall.Handle(r0)
+ if bitmap == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _CreateSolidBrush(color _COLORREF) (brush syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procCreateSolidBrush.Addr(), 1, uintptr(color), 0, 0)
+ brush = syscall.Handle(r0)
+ if brush == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _DeleteDC(dc syscall.Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procDeleteDC.Addr(), 1, uintptr(dc), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _DeleteObject(object syscall.Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procDeleteObject.Addr(), 1, uintptr(object), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _FillRect(dc syscall.Handle, rc *_RECT, brush syscall.Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procFillRect.Addr(), 3, uintptr(dc), uintptr(unsafe.Pointer(rc)), uintptr(brush))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _ModifyWorldTransform(dc syscall.Handle, x *_XFORM, mode uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procModifyWorldTransform.Addr(), 3, uintptr(dc), uintptr(unsafe.Pointer(x)), uintptr(mode))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _SelectObject(dc syscall.Handle, gdiobj syscall.Handle) (newobj syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procSelectObject.Addr(), 2, uintptr(dc), uintptr(gdiobj), 0)
+ newobj = syscall.Handle(r0)
+ if newobj == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _SetGraphicsMode(dc syscall.Handle, mode int32) (oldmode int32, err error) {
+ r0, _, e1 := syscall.Syscall(procSetGraphicsMode.Addr(), 2, uintptr(dc), uintptr(mode), 0)
+ oldmode = int32(r0)
+ if oldmode == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _SetWorldTransform(dc syscall.Handle, x *_XFORM) (err error) {
+ r1, _, e1 := syscall.Syscall(procSetWorldTransform.Addr(), 2, uintptr(dc), uintptr(unsafe.Pointer(x)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _StretchBlt(dcdest syscall.Handle, xdest int32, ydest int32, wdest int32, hdest int32, dcsrc syscall.Handle, xsrc int32, ysrc int32, wsrc int32, hsrc int32, rop uint32) (err error) {
+ r1, _, e1 := syscall.Syscall12(procStretchBlt.Addr(), 11, uintptr(dcdest), uintptr(xdest), uintptr(ydest), uintptr(wdest), uintptr(hdest), uintptr(dcsrc), uintptr(xsrc), uintptr(ysrc), uintptr(wsrc), uintptr(hsrc), uintptr(rop), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func _GetDeviceCaps(dc syscall.Handle, index int32) (ret int32) {
+ r0, _, _ := syscall.Syscall(procGetDeviceCaps.Addr(), 2, uintptr(dc), uintptr(index), 0)
+ ret = int32(r0)
+ return
+}
diff --git a/shiny/driver/x11driver/buffer.go b/shiny/driver/x11driver/buffer.go
new file mode 100644
index 0000000..c2ae264
--- /dev/null
+++ b/shiny/driver/x11driver/buffer.go
@@ -0,0 +1,175 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x11driver
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "log"
+ "sync"
+ "unsafe"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/render"
+ "github.com/jezek/xgb/shm"
+ "github.com/jezek/xgb/xproto"
+
+ "golang.org/x/exp/shiny/driver/internal/swizzle"
+)
+
+type bufferUploader interface {
+ upload(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle)
+}
+
+type bufferImpl struct {
+ s *screenImpl
+
+ addr unsafe.Pointer
+ buf []byte
+ rgba image.RGBA
+ size image.Point
+ xs shm.Seg
+
+ mu sync.Mutex
+ nUpload uint32
+ released bool
+ cleanedUp bool
+}
+
+func (b *bufferImpl) degenerate() bool { return b.size.X == 0 || b.size.Y == 0 }
+func (b *bufferImpl) Size() image.Point { return b.size }
+func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
+func (b *bufferImpl) RGBA() *image.RGBA { return &b.rgba }
+
+func (b *bufferImpl) preUpload() {
+ // Check that the program hasn't tried to modify the rgba field via the
+ // pointer returned by the bufferImpl.RGBA method. This check doesn't catch
+ // 100% of all cases; it simply tries to detect some invalid uses of a
+ // screen.Buffer such as:
+ // *buffer.RGBA() = anotherImageRGBA
+ if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] {
+ panic("x11driver: invalid Buffer.RGBA modification")
+ }
+
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ if b.released {
+ panic("x11driver: Buffer.Upload called after Buffer.Release")
+ }
+ if b.nUpload == 0 {
+ swizzle.BGRA(b.buf)
+ }
+ b.nUpload++
+}
+
+func (b *bufferImpl) postUpload() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ b.nUpload--
+ if b.nUpload != 0 {
+ return
+ }
+
+ if b.released {
+ go b.cleanUp()
+ } else {
+ swizzle.BGRA(b.buf)
+ }
+}
+
+func (b *bufferImpl) Release() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ if !b.released && b.nUpload == 0 {
+ go b.cleanUp()
+ }
+ b.released = true
+}
+
+func (b *bufferImpl) cleanUp() {
+ b.mu.Lock()
+ if b.cleanedUp {
+ b.mu.Unlock()
+ panic("x11driver: Buffer clean-up occurred twice")
+ }
+ b.cleanedUp = true
+ b.mu.Unlock()
+
+ b.s.mu.Lock()
+ delete(b.s.buffers, b.xs)
+ b.s.mu.Unlock()
+
+ if b.degenerate() {
+ return
+ }
+ shm.Detach(b.s.xc, b.xs)
+ if err := shmClose(b.addr); err != nil {
+ log.Printf("x11driver: shmClose: %v", err)
+ }
+}
+
+func (b *bufferImpl) upload(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle) {
+ originalSRMin := sr.Min
+ sr = sr.Intersect(b.Bounds())
+ if sr.Empty() {
+ return
+ }
+ dp = dp.Add(sr.Min.Sub(originalSRMin))
+ b.preUpload()
+
+ b.s.mu.Lock()
+ b.s.nPendingUploads++
+ b.s.mu.Unlock()
+
+ cookie := shm.PutImage(
+ b.s.xc, xd, xg,
+ uint16(b.size.X), uint16(b.size.Y), // TotalWidth, TotalHeight,
+ uint16(sr.Min.X), uint16(sr.Min.Y), // SrcX, SrcY,
+ uint16(sr.Dx()), uint16(sr.Dy()), // SrcWidth, SrcHeight,
+ int16(dp.X), int16(dp.Y), // DstX, DstY,
+ depth, xproto.ImageFormatZPixmap,
+ 1, b.xs, 0, // 1 means send a completion event, 0 means a zero offset.
+ )
+
+ completion := make(chan struct{})
+
+ b.s.mu.Lock()
+ b.s.uploads[cookie.Sequence] = completion
+ b.s.nPendingUploads--
+ b.s.handleCompletions()
+ b.s.mu.Unlock()
+
+ <-completion
+
+ b.postUpload()
+}
+
+func fill(xc *xgb.Conn, xp render.Picture, dr image.Rectangle, src color.Color, op draw.Op) {
+ r, g, b, a := src.RGBA()
+ c := render.Color{
+ Red: uint16(r),
+ Green: uint16(g),
+ Blue: uint16(b),
+ Alpha: uint16(a),
+ }
+ x, y := dr.Min.X, dr.Min.Y
+ if x < -0x8000 || 0x7fff < x || y < -0x8000 || 0x7fff < y {
+ return
+ }
+ dx, dy := dr.Dx(), dr.Dy()
+ if dx < 0 || 0xffff < dx || dy < 0 || 0xffff < dy {
+ return
+ }
+ render.FillRectangles(xc, renderOp(op), xp, c, []xproto.Rectangle{{
+ X: int16(x),
+ Y: int16(y),
+ Width: uint16(dx),
+ Height: uint16(dy),
+ }})
+}
diff --git a/shiny/driver/x11driver/buffer_fallback.go b/shiny/driver/x11driver/buffer_fallback.go
new file mode 100644
index 0000000..fbf9483
--- /dev/null
+++ b/shiny/driver/x11driver/buffer_fallback.go
@@ -0,0 +1,124 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x11driver
+
+import (
+ "image"
+ "sync"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/xproto"
+
+ "golang.org/x/exp/shiny/driver/internal/swizzle"
+)
+
+const (
+ xPutImageReqSizeMax = (1 << 16) * 4
+ xPutImageReqSizeFixed = 28
+ xPutImageReqDataSize = xPutImageReqSizeMax - xPutImageReqSizeFixed
+)
+
+type bufferFallbackImpl struct {
+ xc *xgb.Conn
+
+ buf []byte
+ rgba image.RGBA
+ size image.Point
+
+ mu sync.Mutex
+ nUpload uint32
+ released bool
+}
+
+func (b *bufferFallbackImpl) Size() image.Point { return b.size }
+func (b *bufferFallbackImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
+func (b *bufferFallbackImpl) RGBA() *image.RGBA { return &b.rgba }
+
+func (b *bufferFallbackImpl) preUpload() {
+ // Check that the program hasn't tried to modify the rgba field via the
+ // pointer returned by the bufferFallbackImpl.RGBA method. This check doesn't catch
+ // 100% of all cases; it simply tries to detect some invalid uses of a
+ // screen.Buffer such as:
+ // *buffer.RGBA() = anotherImageRGBA
+ if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] {
+ panic("x11driver: invalid Buffer.RGBA modification")
+ }
+
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ if b.released {
+ panic("x11driver: Buffer.Upload called after Buffer.Release")
+ }
+ if b.nUpload == 0 {
+ swizzle.BGRA(b.buf)
+ }
+ b.nUpload++
+}
+
+func (b *bufferFallbackImpl) postUpload() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ b.nUpload--
+ if b.nUpload != 0 {
+ return
+ }
+
+ if !b.released {
+ swizzle.BGRA(b.buf)
+ }
+}
+
+func (b *bufferFallbackImpl) Release() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ b.released = true
+}
+
+func (b *bufferFallbackImpl) upload(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle) {
+ originalSRMin := sr.Min
+ sr = sr.Intersect(b.Bounds())
+ if sr.Empty() {
+ return
+ }
+ dp = dp.Add(sr.Min.Sub(originalSRMin))
+ b.preUpload()
+
+ b.putImage(xd, xg, depth, dp, sr)
+
+ b.postUpload()
+}
+
+// putImage issues xproto.PutImage requests in batches.
+func (b *bufferFallbackImpl) putImage(xd xproto.Drawable, xg xproto.Gcontext, depth uint8, dp image.Point, sr image.Rectangle) {
+ widthPerReq := b.size.X
+ rowPerReq := xPutImageReqDataSize / (widthPerReq * 4)
+ dataPerReq := rowPerReq * widthPerReq * 4
+ dstX := dp.X
+ dstY := dp.Y
+ start := 0
+ end := 0
+
+ for end < len(b.buf) {
+ end = start + dataPerReq
+ if end > len(b.buf) {
+ end = len(b.buf)
+ }
+
+ data := b.buf[start:end]
+ heightPerReq := len(data) / (widthPerReq * 4)
+
+ xproto.PutImage(
+ b.xc, xproto.ImageFormatZPixmap, xd, xg,
+ uint16(widthPerReq), uint16(heightPerReq),
+ int16(dstX), int16(dstY),
+ 0, depth, data)
+
+ start = end
+ dstY += rowPerReq
+ }
+}
diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go
new file mode 100644
index 0000000..c0dc9a4
--- /dev/null
+++ b/shiny/driver/x11driver/screen.go
@@ -0,0 +1,690 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x11driver
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "log"
+ "sync"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/render"
+ "github.com/jezek/xgb/shm"
+ "github.com/jezek/xgb/xproto"
+
+ "golang.org/x/exp/shiny/driver/internal/x11key"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/mouse"
+)
+
+// TODO: check that xgb is safe to use concurrently from multiple goroutines.
+// For example, its Conn.WaitForEvent concept is a method, not a channel, so
+// it's not obvious how to interrupt it to service a NewWindow request.
+
+type screenImpl struct {
+ xc *xgb.Conn
+ xsi *xproto.ScreenInfo
+ keysyms x11key.KeysymTable
+
+ atomNETWMName xproto.Atom
+ atomUTF8String xproto.Atom
+ atomWMDeleteWindow xproto.Atom
+ atomWMProtocols xproto.Atom
+ atomWMTakeFocus xproto.Atom
+
+ pixelsPerPt float32
+ pictformat24 render.Pictformat
+ pictformat32 render.Pictformat
+
+ // window32 and its related X11 resources is an unmapped window so that we
+ // have a depth-32 window to create depth-32 pixmaps from, i.e. pixmaps
+ // with an alpha channel. The root window isn't guaranteed to be depth-32.
+ gcontext32 xproto.Gcontext
+ window32 xproto.Window
+
+ // opaqueP is a fully opaque, solid fill picture.
+ opaqueP render.Picture
+ useShm bool
+
+ uniformMu sync.Mutex
+ uniformC render.Color
+ uniformP render.Picture
+
+ mu sync.Mutex
+ buffers map[shm.Seg]*bufferImpl
+ uploads map[uint16]chan struct{}
+ windows map[xproto.Window]*windowImpl
+ nPendingUploads int
+ completionKeys []uint16
+}
+
+func newScreenImpl(xc *xgb.Conn, useShm bool) (*screenImpl, error) {
+ s := &screenImpl{
+ xc: xc,
+ xsi: xproto.Setup(xc).DefaultScreen(xc),
+ buffers: map[shm.Seg]*bufferImpl{},
+ uploads: map[uint16]chan struct{}{},
+ windows: map[xproto.Window]*windowImpl{},
+ useShm: useShm,
+ }
+ if err := s.initAtoms(); err != nil {
+ return nil, err
+ }
+ if err := s.initKeyboardMapping(); err != nil {
+ return nil, err
+ }
+ const (
+ mmPerInch = 25.4
+ ptPerInch = 72
+ )
+ pixelsPerMM := float32(s.xsi.WidthInPixels) / float32(s.xsi.WidthInMillimeters)
+ s.pixelsPerPt = pixelsPerMM * mmPerInch / ptPerInch
+ if err := s.initPictformats(); err != nil {
+ return nil, err
+ }
+ if err := s.initWindow32(); err != nil {
+ return nil, err
+ }
+
+ var err error
+ s.opaqueP, err = render.NewPictureId(xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
+ }
+ s.uniformP, err = render.NewPictureId(xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
+ }
+ render.CreateSolidFill(s.xc, s.opaqueP, render.Color{
+ Red: 0xffff,
+ Green: 0xffff,
+ Blue: 0xffff,
+ Alpha: 0xffff,
+ })
+ render.CreateSolidFill(s.xc, s.uniformP, render.Color{})
+
+ go s.run()
+ return s, nil
+}
+
+func (s *screenImpl) run() {
+ keyboardChanged := false
+ for {
+ ev, err := s.xc.WaitForEvent()
+ if err != nil {
+ log.Printf("x11driver: xproto.WaitForEvent: %v", err)
+ continue
+ }
+
+ noWindowFound := false
+ switch ev := ev.(type) {
+ case xproto.DestroyNotifyEvent:
+ s.mu.Lock()
+ delete(s.windows, ev.Window)
+ s.mu.Unlock()
+
+ case shm.CompletionEvent:
+ s.mu.Lock()
+ s.completionKeys = append(s.completionKeys, ev.Sequence)
+ s.handleCompletions()
+ s.mu.Unlock()
+
+ case xproto.ClientMessageEvent:
+ if ev.Type != s.atomWMProtocols || ev.Format != 32 {
+ break
+ }
+ switch xproto.Atom(ev.Data.Data32[0]) {
+ case s.atomWMDeleteWindow:
+ if w := s.findWindow(ev.Window); w != nil {
+ w.lifecycler.SetDead(true)
+ w.lifecycler.SendEvent(w, nil)
+ } else {
+ noWindowFound = true
+ }
+ case s.atomWMTakeFocus:
+ xproto.SetInputFocus(s.xc, xproto.InputFocusParent, ev.Window, xproto.Timestamp(ev.Data.Data32[1]))
+ }
+
+ case xproto.ConfigureNotifyEvent:
+ if w := s.findWindow(ev.Window); w != nil {
+ w.handleConfigureNotify(ev)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.ExposeEvent:
+ if w := s.findWindow(ev.Window); w != nil {
+ // A non-zero Count means that there are more expose events
+ // coming. For example, a non-rectangular exposure (e.g. from a
+ // partially overlapped window) will result in multiple expose
+ // events whose dirty rectangles combine to define the dirty
+ // region. Go's paint events do not provide dirty regions, so
+ // we only pass on the final X11 expose event.
+ if ev.Count == 0 {
+ w.handleExpose()
+ }
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.FocusInEvent:
+ if w := s.findWindow(ev.Event); w != nil {
+ w.lifecycler.SetFocused(true)
+ w.lifecycler.SendEvent(w, nil)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.FocusOutEvent:
+ if w := s.findWindow(ev.Event); w != nil {
+ w.lifecycler.SetFocused(false)
+ w.lifecycler.SendEvent(w, nil)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.KeyPressEvent:
+ if keyboardChanged {
+ keyboardChanged = false
+ s.initKeyboardMapping()
+ }
+ if w := s.findWindow(ev.Event); w != nil {
+ w.handleKey(ev.Detail, ev.State, key.DirPress)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.KeyReleaseEvent:
+ if keyboardChanged {
+ keyboardChanged = false
+ s.initKeyboardMapping()
+ }
+ if w := s.findWindow(ev.Event); w != nil {
+ w.handleKey(ev.Detail, ev.State, key.DirRelease)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.ButtonPressEvent:
+ if w := s.findWindow(ev.Event); w != nil {
+ w.handleMouse(ev.EventX, ev.EventY, ev.Detail, ev.State, mouse.DirPress)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.ButtonReleaseEvent:
+ if w := s.findWindow(ev.Event); w != nil {
+ w.handleMouse(ev.EventX, ev.EventY, ev.Detail, ev.State, mouse.DirRelease)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.MotionNotifyEvent:
+ if w := s.findWindow(ev.Event); w != nil {
+ w.handleMouse(ev.EventX, ev.EventY, 0, ev.State, mouse.DirNone)
+ } else {
+ noWindowFound = true
+ }
+
+ case xproto.MappingNotifyEvent:
+ if ev.Request == xproto.MappingModifier || ev.Request == xproto.MappingKeyboard {
+ keyboardChanged = true
+ }
+ }
+
+ if noWindowFound {
+ log.Printf("x11driver: no window found for event %T", ev)
+ }
+ }
+}
+
+// TODO: is findBuffer and the s.buffers field unused? Delete?
+
+func (s *screenImpl) findBuffer(key shm.Seg) *bufferImpl {
+ s.mu.Lock()
+ b := s.buffers[key]
+ s.mu.Unlock()
+ return b
+}
+
+func (s *screenImpl) findWindow(key xproto.Window) *windowImpl {
+ s.mu.Lock()
+ w := s.windows[key]
+ s.mu.Unlock()
+ return w
+}
+
+// handleCompletions must only be called while holding s.mu.
+func (s *screenImpl) handleCompletions() {
+ if s.nPendingUploads != 0 {
+ return
+ }
+ for _, ck := range s.completionKeys {
+ completion, ok := s.uploads[ck]
+ if !ok {
+ log.Printf("x11driver: no matching upload for a SHM completion event")
+ continue
+ }
+ delete(s.uploads, ck)
+ close(completion)
+ }
+ s.completionKeys = s.completionKeys[:0]
+}
+
+const (
+ maxShmSide = 0x00007fff // 32,767 pixels.
+ maxShmSize = 0x10000000 // 268,435,456 bytes.
+)
+
+func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
+
+ w, h := int64(size.X), int64(size.Y)
+ if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
+ return nil, fmt.Errorf("x11driver: invalid buffer size %v", size)
+ }
+
+ // If the X11 server or connection cannot support SHM pixmaps,
+ // fall back to regular pixmaps.
+ if !s.useShm {
+ b := &bufferFallbackImpl{
+ xc: s.xc,
+ size: size,
+ rgba: image.RGBA{
+ Stride: 4 * size.X,
+ Rect: image.Rectangle{Max: size},
+ Pix: make([]uint8, 4*size.X*size.Y),
+ },
+ }
+ b.buf = b.rgba.Pix
+ return b, nil
+ }
+
+ b := &bufferImpl{
+ s: s,
+ rgba: image.RGBA{
+ Stride: 4 * size.X,
+ Rect: image.Rectangle{Max: size},
+ },
+ size: size,
+ }
+
+ if size.X == 0 || size.Y == 0 {
+ // No-op, but we can't take the else path because the minimum shmget
+ // size is 1.
+ } else {
+ xs, err := shm.NewSegId(s.xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: shm.NewSegId: %v", err)
+ }
+
+ bufLen := 4 * size.X * size.Y
+ shmid, addr, err := shmOpen(bufLen)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: shmOpen: %v", err)
+ }
+ defer func() {
+ if retErr != nil {
+ shmClose(addr)
+ }
+ }()
+ a := (*[maxShmSize]byte)(addr)
+ b.buf = (*a)[:bufLen:bufLen]
+ b.rgba.Pix = b.buf
+ b.addr = addr
+
+ // readOnly is whether the shared memory is read-only from the X11 server's
+ // point of view. We need false to use SHM pixmaps.
+ const readOnly = false
+ shm.Attach(s.xc, xs, uint32(shmid), readOnly)
+ b.xs = xs
+ }
+
+ s.mu.Lock()
+ s.buffers[b.xs] = b
+ s.mu.Unlock()
+
+ return b, nil
+}
+
+func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
+ w, h := int64(size.X), int64(size.Y)
+ if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
+ return nil, fmt.Errorf("x11driver: invalid texture size %v", size)
+ }
+ if w == 0 || h == 0 {
+ return &textureImpl{
+ s: s,
+ size: size,
+ }, nil
+ }
+
+ xm, err := xproto.NewPixmapId(s.xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewPixmapId failed: %v", err)
+ }
+ xp, err := render.NewPictureId(s.xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
+ }
+ xproto.CreatePixmap(s.xc, textureDepth, xm, xproto.Drawable(s.window32), uint16(w), uint16(h))
+ render.CreatePicture(s.xc, xp, xproto.Drawable(xm), s.pictformat32, render.CpRepeat, []uint32{render.RepeatPad})
+ render.SetPictureFilter(s.xc, xp, uint16(len("bilinear")), "bilinear", nil)
+ // The X11 server doesn't zero-initialize the pixmap. We do it ourselves.
+ render.FillRectangles(s.xc, render.PictOpSrc, xp, render.Color{}, []xproto.Rectangle{{
+ Width: uint16(w),
+ Height: uint16(h),
+ }})
+
+ return &textureImpl{
+ s: s,
+ size: size,
+ xm: xm,
+ xp: xp,
+ }, nil
+}
+
+func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
+ width, height := 1024, 768
+ if opts != nil {
+ if opts.Width > 0 {
+ width = opts.Width
+ }
+ if opts.Height > 0 {
+ height = opts.Height
+ }
+ }
+
+ xw, err := xproto.NewWindowId(s.xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewWindowId failed: %v", err)
+ }
+ xg, err := xproto.NewGcontextId(s.xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: xproto.NewGcontextId failed: %v", err)
+ }
+ xp, err := render.NewPictureId(s.xc)
+ if err != nil {
+ return nil, fmt.Errorf("x11driver: render.NewPictureId failed: %v", err)
+ }
+ pictformat := render.Pictformat(0)
+ switch s.xsi.RootDepth {
+ default:
+ return nil, fmt.Errorf("x11driver: unsupported root depth %d", s.xsi.RootDepth)
+ case 24:
+ pictformat = s.pictformat24
+ case 32:
+ pictformat = s.pictformat32
+ }
+
+ w := &windowImpl{
+ s: s,
+ xw: xw,
+ xg: xg,
+ xp: xp,
+ xevents: make(chan xgb.Event),
+ }
+
+ s.mu.Lock()
+ s.windows[xw] = w
+ s.mu.Unlock()
+
+ w.lifecycler.SendEvent(w, nil)
+
+ xproto.CreateWindow(s.xc, s.xsi.RootDepth, xw, s.xsi.Root,
+ 0, 0, uint16(width), uint16(height), 0,
+ xproto.WindowClassInputOutput, s.xsi.RootVisual,
+ xproto.CwEventMask,
+ []uint32{0 |
+ xproto.EventMaskKeyPress |
+ xproto.EventMaskKeyRelease |
+ xproto.EventMaskButtonPress |
+ xproto.EventMaskButtonRelease |
+ xproto.EventMaskPointerMotion |
+ xproto.EventMaskExposure |
+ xproto.EventMaskStructureNotify |
+ xproto.EventMaskFocusChange,
+ },
+ )
+ s.setProperty(xw, s.atomWMProtocols, s.atomWMDeleteWindow, s.atomWMTakeFocus)
+
+ title := []byte(opts.GetTitle())
+ xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, s.atomNETWMName, s.atomUTF8String, 8, uint32(len(title)), title)
+
+ xproto.CreateGC(s.xc, xg, xproto.Drawable(xw), 0, nil)
+ render.CreatePicture(s.xc, xp, xproto.Drawable(xw), pictformat, 0, nil)
+ xproto.MapWindow(s.xc, xw)
+
+ return w, nil
+}
+
+func (s *screenImpl) initAtoms() (err error) {
+ s.atomNETWMName, err = s.internAtom("_NET_WM_NAME")
+ if err != nil {
+ return err
+ }
+ s.atomUTF8String, err = s.internAtom("UTF8_STRING")
+ if err != nil {
+ return err
+ }
+ s.atomWMDeleteWindow, err = s.internAtom("WM_DELETE_WINDOW")
+ if err != nil {
+ return err
+ }
+ s.atomWMProtocols, err = s.internAtom("WM_PROTOCOLS")
+ if err != nil {
+ return err
+ }
+ s.atomWMTakeFocus, err = s.internAtom("WM_TAKE_FOCUS")
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *screenImpl) internAtom(name string) (xproto.Atom, error) {
+ r, err := xproto.InternAtom(s.xc, false, uint16(len(name)), name).Reply()
+ if err != nil {
+ return 0, fmt.Errorf("x11driver: xproto.InternAtom failed: %v", err)
+ }
+ if r == nil {
+ return 0, fmt.Errorf("x11driver: xproto.InternAtom failed")
+ }
+ return r.Atom, nil
+}
+
+func (s *screenImpl) initKeyboardMapping() error {
+ const keyLo, keyHi = 8, 255
+ km, err := xproto.GetKeyboardMapping(s.xc, keyLo, keyHi-keyLo+1).Reply()
+ if err != nil {
+ return err
+ }
+ n := int(km.KeysymsPerKeycode)
+ if n < 2 {
+ return fmt.Errorf("x11driver: too few keysyms per keycode: %d", n)
+ }
+ for i := keyLo; i <= keyHi; i++ {
+ for j := 0; j < 6; j++ {
+ if j < n {
+ s.keysyms.Table[i][j] = uint32(km.Keysyms[(i-keyLo)*n+j])
+ } else {
+ s.keysyms.Table[i][j] = 0
+ }
+ }
+ }
+
+ // Figure out which modifier is the numlock modifier (see chapter 12.7 of the XLib Manual).
+ mm, err := xproto.GetModifierMapping(s.xc).Reply()
+ if err != nil {
+ return err
+ }
+ s.keysyms.NumLockMod, s.keysyms.ModeSwitchMod, s.keysyms.ISOLevel3ShiftMod = 0, 0, 0
+ numLockFound, modeSwitchFound, isoLevel3ShiftFound := false, false, false
+modifierSearchLoop:
+ for modifier := 0; modifier < 8; modifier++ {
+ for i := 0; i < int(mm.KeycodesPerModifier); i++ {
+ const (
+ // XK_Num_Lock, XK_Mode_switch and XK_ISO_Level3_Shift from /usr/include/X11/keysymdef.h.
+ xkNumLock = 0xff7f
+ xkModeSwitch = 0xff7e
+ xkISOLevel3Shift = 0xfe03
+ )
+ switch s.keysyms.Table[mm.Keycodes[modifier*int(mm.KeycodesPerModifier)+i]][0] {
+ case xkNumLock:
+ s.keysyms.NumLockMod = 1 << uint(modifier)
+ numLockFound = true
+ case xkModeSwitch:
+ s.keysyms.ModeSwitchMod = 1 << uint(modifier)
+ modeSwitchFound = true
+ case xkISOLevel3Shift:
+ s.keysyms.ISOLevel3ShiftMod = 1 << uint(modifier)
+ isoLevel3ShiftFound = true
+ }
+ if numLockFound && modeSwitchFound && isoLevel3ShiftFound {
+ break modifierSearchLoop
+ }
+ }
+ }
+ return nil
+}
+
+func (s *screenImpl) initPictformats() error {
+ pformats, err := render.QueryPictFormats(s.xc).Reply()
+ if err != nil {
+ return fmt.Errorf("x11driver: render.QueryPictFormats failed: %v", err)
+ }
+ s.pictformat24, err = findPictformat(pformats.Formats, 24)
+ if err != nil {
+ return err
+ }
+ s.pictformat32, err = findPictformat(pformats.Formats, 32)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func findPictformat(fs []render.Pictforminfo, depth byte) (render.Pictformat, error) {
+ // This presumes little-endian BGRA.
+ want := render.Directformat{
+ RedShift: 16,
+ RedMask: 0xff,
+ GreenShift: 8,
+ GreenMask: 0xff,
+ BlueShift: 0,
+ BlueMask: 0xff,
+ AlphaShift: 24,
+ AlphaMask: 0xff,
+ }
+ if depth == 24 {
+ want.AlphaShift = 0
+ want.AlphaMask = 0x00
+ }
+ for _, f := range fs {
+ if f.Type == render.PictTypeDirect && f.Depth == depth && f.Direct == want {
+ return f.Id, nil
+ }
+ }
+ return 0, fmt.Errorf("x11driver: no matching Pictformat for depth %d", depth)
+}
+
+func (s *screenImpl) initWindow32() error {
+ visualid, err := findVisual(s.xsi, 32)
+ if err != nil {
+ return err
+ }
+ colormap, err := xproto.NewColormapId(s.xc)
+ if err != nil {
+ return fmt.Errorf("x11driver: xproto.NewColormapId failed: %v", err)
+ }
+ if err := xproto.CreateColormapChecked(
+ s.xc, xproto.ColormapAllocNone, colormap, s.xsi.Root, visualid).Check(); err != nil {
+ return fmt.Errorf("x11driver: xproto.CreateColormap failed: %v", err)
+ }
+ s.window32, err = xproto.NewWindowId(s.xc)
+ if err != nil {
+ return fmt.Errorf("x11driver: xproto.NewWindowId failed: %v", err)
+ }
+ s.gcontext32, err = xproto.NewGcontextId(s.xc)
+ if err != nil {
+ return fmt.Errorf("x11driver: xproto.NewGcontextId failed: %v", err)
+ }
+ const depth = 32
+ xproto.CreateWindow(s.xc, depth, s.window32, s.xsi.Root,
+ 0, 0, 1, 1, 0,
+ xproto.WindowClassInputOutput, visualid,
+ // The CwBorderPixel attribute seems necessary for depth == 32. See
+ // http://stackoverflow.com/questions/3645632/how-to-create-a-window-with-a-bit-depth-of-32
+ xproto.CwBorderPixel|xproto.CwColormap,
+ []uint32{0, uint32(colormap)},
+ )
+ xproto.CreateGC(s.xc, s.gcontext32, xproto.Drawable(s.window32), 0, nil)
+ return nil
+}
+
+func findVisual(xsi *xproto.ScreenInfo, depth byte) (xproto.Visualid, error) {
+ for _, d := range xsi.AllowedDepths {
+ if d.Depth != depth {
+ continue
+ }
+ for _, v := range d.Visuals {
+ if v.RedMask == 0xff0000 && v.GreenMask == 0xff00 && v.BlueMask == 0xff {
+ return v.VisualId, nil
+ }
+ }
+ }
+ return 0, fmt.Errorf("x11driver: no matching Visualid")
+}
+
+func (s *screenImpl) setProperty(xw xproto.Window, prop xproto.Atom, values ...xproto.Atom) {
+ b := make([]byte, len(values)*4)
+ for i, v := range values {
+ b[4*i+0] = uint8(v >> 0)
+ b[4*i+1] = uint8(v >> 8)
+ b[4*i+2] = uint8(v >> 16)
+ b[4*i+3] = uint8(v >> 24)
+ }
+ xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, prop, xproto.AtomAtom, 32, uint32(len(values)), b)
+}
+
+func (s *screenImpl) drawUniform(xp render.Picture, src2dst *f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ if sr.Empty() {
+ return
+ }
+
+ if opts == nil && *src2dst == (f64.Aff3{1, 0, 0, 0, 1, 0}) {
+ fill(s.xc, xp, sr, src, op)
+ return
+ }
+
+ r, g, b, a := src.RGBA()
+ c := render.Color{
+ Red: uint16(r),
+ Green: uint16(g),
+ Blue: uint16(b),
+ Alpha: uint16(a),
+ }
+ points := trifanPoints(src2dst, sr)
+
+ s.uniformMu.Lock()
+ defer s.uniformMu.Unlock()
+
+ if s.uniformC != c {
+ s.uniformC = c
+ render.FreePicture(s.xc, s.uniformP)
+ render.CreateSolidFill(s.xc, s.uniformP, c)
+ }
+
+ if op == draw.Src {
+ // We implement draw.Src as render.PictOpOutReverse followed by
+ // render.PictOpOver, for the same reason as in textureImpl.draw.
+ render.TriFan(s.xc, render.PictOpOutReverse, s.opaqueP, xp, 0, 0, 0, points[:])
+ }
+ render.TriFan(s.xc, render.PictOpOver, s.uniformP, xp, 0, 0, 0, points[:])
+}
diff --git a/shiny/driver/x11driver/shm_linux_ipc.go b/shiny/driver/x11driver/shm_linux_ipc.go
new file mode 100644
index 0000000..dcb61ac
--- /dev/null
+++ b/shiny/driver/x11driver/shm_linux_ipc.go
@@ -0,0 +1,48 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux && (386 || ppc64 || ppc64le || s390x)
+
+package x11driver
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+// These constants are from /usr/include/linux/ipc.h
+const (
+ ipcPrivate = 0
+ ipcRmID = 0
+
+ shmAt = 21
+ shmDt = 22
+ shmGet = 23
+ shmCtl = 24
+)
+
+func shmOpen(size int) (shmid uintptr, addr unsafe.Pointer, err error) {
+ shmid, _, errno0 := syscall.RawSyscall6(syscall.SYS_IPC, shmGet, ipcPrivate, uintptr(size), 0600, 0, 0)
+ if errno0 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmget: %v", errno0)
+ }
+ _, _, errno1 := syscall.RawSyscall6(syscall.SYS_IPC, shmAt, shmid, 0, uintptr(unsafe.Pointer(&addr)), 0, 0)
+ _, _, errno2 := syscall.RawSyscall6(syscall.SYS_IPC, shmCtl, shmid, ipcRmID, 0, 0, 0)
+ if errno1 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmat: %v", errno1)
+ }
+ if errno2 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmctl: %v", errno2)
+ }
+ return shmid, addr, nil
+}
+
+func shmClose(p unsafe.Pointer) error {
+ _, _, errno := syscall.RawSyscall6(syscall.SYS_IPC, shmDt, 0, 0, 0, uintptr(p), 0)
+ if errno != 0 {
+ return fmt.Errorf("shmdt: %v", errno)
+ }
+ return nil
+}
diff --git a/shiny/driver/x11driver/shm_openbsd_syscall.go b/shiny/driver/x11driver/shm_openbsd_syscall.go
new file mode 100644
index 0000000..d8dc2c1
--- /dev/null
+++ b/shiny/driver/x11driver/shm_openbsd_syscall.go
@@ -0,0 +1,43 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build openbsd && (i386 || amd64)
+
+package x11driver
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+// These constants are from /usr/include/sys/ipc.h
+const (
+ ipcPrivate = 0
+ ipcRmID = 0
+)
+
+func shmOpen(size int) (shmid uintptr, addr unsafe.Pointer, err error) {
+ shmid, _, errno0 := syscall.RawSyscall(syscall.SYS_SHMGET, ipcPrivate, uintptr(size), 0666)
+ if errno0 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmget: %v", errno0)
+ }
+ p, _, errno1 := syscall.RawSyscall(syscall.SYS_SHMAT, shmid, 0, 0)
+ _, _, errno2 := syscall.RawSyscall(syscall.SYS_SHMCTL, shmid, ipcRmID, 0)
+ if errno1 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmat: %v", errno1)
+ }
+ if errno2 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmctl: %v", errno2)
+ }
+ return shmid, unsafe.Pointer(p), nil
+}
+
+func shmClose(p unsafe.Pointer) error {
+ _, _, errno := syscall.RawSyscall(syscall.SYS_SHMDT, uintptr(p), 0, 0)
+ if errno != 0 {
+ return fmt.Errorf("shmdt: %v", errno)
+ }
+ return nil
+}
diff --git a/shiny/driver/x11driver/shm_other.go b/shiny/driver/x11driver/shm_other.go
new file mode 100644
index 0000000..8003f14
--- /dev/null
+++ b/shiny/driver/x11driver/shm_other.go
@@ -0,0 +1,22 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !linux && !dragonfly && !openbsd
+
+package x11driver
+
+import (
+ "fmt"
+ "runtime"
+ "unsafe"
+)
+
+func shmOpen(size int) (shmid uintptr, addr unsafe.Pointer, err error) {
+ return 0, unsafe.Pointer(uintptr(0)),
+ fmt.Errorf("unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func shmClose(p unsafe.Pointer) error {
+ return fmt.Errorf("unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)
+}
diff --git a/shiny/driver/x11driver/shm_shmopen_syscall.go b/shiny/driver/x11driver/shm_shmopen_syscall.go
new file mode 100644
index 0000000..74f571b
--- /dev/null
+++ b/shiny/driver/x11driver/shm_shmopen_syscall.go
@@ -0,0 +1,43 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (linux || dragonfly) && (amd64 || arm || arm64 || mips64 || mips64le)
+
+package x11driver
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+// These constants are from /usr/include/linux/ipc.h
+const (
+ ipcPrivate = 0
+ ipcRmID = 0
+)
+
+func shmOpen(size int) (shmid uintptr, addr unsafe.Pointer, err error) {
+ shmid, _, errno0 := syscall.RawSyscall(syscall.SYS_SHMGET, ipcPrivate, uintptr(size), 0600)
+ if errno0 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmget: %v", errno0)
+ }
+ p, _, errno1 := syscall.RawSyscall(syscall.SYS_SHMAT, shmid, 0, 0)
+ _, _, errno2 := syscall.RawSyscall(syscall.SYS_SHMCTL, shmid, ipcRmID, 0)
+ if errno1 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmat: %v", errno1)
+ }
+ if errno2 != 0 {
+ return 0, unsafe.Pointer(uintptr(0)), fmt.Errorf("shmctl: %v", errno2)
+ }
+ return shmid, unsafe.Pointer(p), nil
+}
+
+func shmClose(p unsafe.Pointer) error {
+ _, _, errno := syscall.RawSyscall(syscall.SYS_SHMDT, uintptr(p), 0, 0)
+ if errno != 0 {
+ return fmt.Errorf("shmdt: %v", errno)
+ }
+ return nil
+}
diff --git a/shiny/driver/x11driver/texture.go b/shiny/driver/x11driver/texture.go
new file mode 100644
index 0000000..ac4cd01
--- /dev/null
+++ b/shiny/driver/x11driver/texture.go
@@ -0,0 +1,200 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x11driver
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "math"
+ "sync"
+
+ "github.com/jezek/xgb/render"
+ "github.com/jezek/xgb/xproto"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+)
+
+const textureDepth = 32
+
+type textureImpl struct {
+ s *screenImpl
+
+ size image.Point
+ xm xproto.Pixmap
+ xp render.Picture
+
+ // renderMu is a mutex that enforces the atomicity of methods like
+ // Window.Draw that are conceptually one operation but are implemented by
+ // multiple X11/Render calls. X11/Render is a stateful API, so interleaving
+ // X11/Render calls from separate higher-level operations causes
+ // inconsistencies.
+ renderMu sync.Mutex
+
+ releasedMu sync.Mutex
+ released bool
+}
+
+func (t *textureImpl) degenerate() bool { return t.size.X == 0 || t.size.Y == 0 }
+func (t *textureImpl) Size() image.Point { return t.size }
+func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} }
+
+func (t *textureImpl) Release() {
+ t.releasedMu.Lock()
+ released := t.released
+ t.released = true
+ t.releasedMu.Unlock()
+
+ if released || t.degenerate() {
+ return
+ }
+ render.FreePicture(t.s.xc, t.xp)
+ xproto.FreePixmap(t.s.xc, t.xm)
+}
+
+func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ if t.degenerate() {
+ return
+ }
+ src.(bufferUploader).upload(xproto.Drawable(t.xm), t.s.gcontext32, textureDepth, dp, sr)
+}
+
+func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ if t.degenerate() {
+ return
+ }
+ fill(t.s.xc, t.xp, dr, src, op)
+}
+
+// f64ToFixed converts from float64 to X11/Render's 16.16 fixed point.
+func f64ToFixed(x float64) render.Fixed {
+ return render.Fixed(x * 65536)
+}
+
+func inv(x *f64.Aff3) f64.Aff3 {
+ invDet := 1 / (x[0]*x[4] - x[1]*x[3])
+ return f64.Aff3{
+ +x[4] * invDet,
+ -x[1] * invDet,
+ (x[1]*x[5] - x[2]*x[4]) * invDet,
+ -x[3] * invDet,
+ +x[0] * invDet,
+ (x[2]*x[3] - x[0]*x[5]) * invDet,
+ }
+}
+
+func (t *textureImpl) draw(xp render.Picture, src2dst *f64.Aff3, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ sr = sr.Intersect(t.Bounds())
+ if sr.Empty() {
+ return
+ }
+
+ t.renderMu.Lock()
+ defer t.renderMu.Unlock()
+
+ // For simple copies and scales, the inverse matrix is trivial to compute,
+ // and we do not need the "Src becomes OutReverse plus Over" dance (see
+ // below). Thus, draw can be one render.SetPictureTransform call and then
+ // one render.Composite call, regardless of whether or not op is Src.
+ if src2dst[1] == 0 && src2dst[3] == 0 {
+ dstXMin := float64(sr.Min.X)*src2dst[0] + src2dst[2]
+ dstXMax := float64(sr.Max.X)*src2dst[0] + src2dst[2]
+ if dstXMin > dstXMax {
+ // TODO: check if this (and below) works when src2dst[0] < 0.
+ dstXMin, dstXMax = dstXMax, dstXMin
+ }
+ dXMin := int(math.Floor(dstXMin))
+ dXMax := int(math.Ceil(dstXMax))
+
+ dstYMin := float64(sr.Min.Y)*src2dst[4] + src2dst[5]
+ dstYMax := float64(sr.Max.Y)*src2dst[4] + src2dst[5]
+ if dstYMin > dstYMax {
+ // TODO: check if this (and below) works when src2dst[4] < 0.
+ dstYMin, dstYMax = dstYMax, dstYMin
+ }
+ dYMin := int(math.Floor(dstYMin))
+ dYMax := int(math.Ceil(dstYMax))
+
+ render.SetPictureTransform(t.s.xc, t.xp, render.Transform{
+ f64ToFixed(1 / src2dst[0]), 0, 0,
+ 0, f64ToFixed(1 / src2dst[4]), 0,
+ 0, 0, 1 << 16,
+ })
+ render.Composite(t.s.xc, renderOp(op), t.xp, 0, xp,
+ int16(sr.Min.X), int16(sr.Min.Y), // SrcX, SrcY,
+ 0, 0, // MaskX, MaskY,
+ int16(dXMin), int16(dYMin), // DstX, DstY,
+ uint16(dXMax-dXMin), uint16(dYMax-dYMin), // Width, Height,
+ )
+ return
+ }
+
+ // The X11/Render transform matrix maps from destination pixels to source
+ // pixels, so we invert src2dst.
+ dst2src := inv(src2dst)
+ render.SetPictureTransform(t.s.xc, t.xp, render.Transform{
+ f64ToFixed(dst2src[0]), f64ToFixed(dst2src[1]), render.Fixed(sr.Min.X << 16),
+ f64ToFixed(dst2src[3]), f64ToFixed(dst2src[4]), render.Fixed(sr.Min.Y << 16),
+ 0, 0, 1 << 16,
+ })
+
+ points := trifanPoints(src2dst, sr)
+ if op == draw.Src {
+ // render.TriFan visits every dst-space pixel in the axis-aligned
+ // bounding box (AABB) containing the transformation of the sr
+ // rectangle in src-space to a quad in dst-space.
+ //
+ // render.TriFan is like render.Composite, except that the AABB is
+ // defined implicitly by the transformed triangle vertices instead of
+ // being passed explicitly as arguments. It implies the minimal AABB.
+ //
+ // In any case, for arbitrary src2dst affine transformations, which
+ // include rotations, this means that a naive render.TriFan call will
+ // affect those pixels inside the AABB but outside the quad. For the
+ // draw.Src operator, this means that pixels in that AABB can be
+ // incorrectly set to zero.
+ //
+ // Instead, we implement the draw.Src operator as two render.TriFan
+ // calls. The first one (using the PictOpOutReverse operator and a
+ // fully opaque source) clears the dst-space quad but leaves pixels
+ // outside that quad (but inside the AABB) untouched. The second one
+ // (using the PictOpOver operator and the texture t as source) fills in
+ // the quad and again does not touch the pixels outside.
+ //
+ // What X11/Render calls PictOpOutReverse is also known as dst-out. See
+ // http://www.w3.org/TR/SVGCompositing/examples/compop-porterduff-examples.png
+ // for a visualization.
+ render.TriFan(t.s.xc, render.PictOpOutReverse, t.s.opaqueP, xp, 0, 0, 0, points[:])
+ }
+ render.TriFan(t.s.xc, render.PictOpOver, t.xp, xp, 0, 0, 0, points[:])
+}
+
+func trifanPoints(src2dst *f64.Aff3, sr image.Rectangle) [4]render.Pointfix {
+ minX := float64(sr.Min.X)
+ maxX := float64(sr.Max.X)
+ minY := float64(sr.Min.Y)
+ maxY := float64(sr.Max.Y)
+ return [4]render.Pointfix{{
+ f64ToFixed(src2dst[0]*minX + src2dst[1]*minY + src2dst[2]),
+ f64ToFixed(src2dst[3]*minX + src2dst[4]*minY + src2dst[5]),
+ }, {
+ f64ToFixed(src2dst[0]*maxX + src2dst[1]*minY + src2dst[2]),
+ f64ToFixed(src2dst[3]*maxX + src2dst[4]*minY + src2dst[5]),
+ }, {
+ f64ToFixed(src2dst[0]*maxX + src2dst[1]*maxY + src2dst[2]),
+ f64ToFixed(src2dst[3]*maxX + src2dst[4]*maxY + src2dst[5]),
+ }, {
+ f64ToFixed(src2dst[0]*minX + src2dst[1]*maxY + src2dst[2]),
+ f64ToFixed(src2dst[3]*minX + src2dst[4]*maxY + src2dst[5]),
+ }}
+}
+
+func renderOp(op draw.Op) byte {
+ if op == draw.Src {
+ return render.PictOpSrc
+ }
+ return render.PictOpOver
+}
diff --git a/shiny/driver/x11driver/window.go b/shiny/driver/x11driver/window.go
new file mode 100644
index 0000000..68b6d99
--- /dev/null
+++ b/shiny/driver/x11driver/window.go
@@ -0,0 +1,170 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package x11driver
+
+// TODO: implement a back buffer.
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "sync"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/render"
+ "github.com/jezek/xgb/xproto"
+
+ "golang.org/x/exp/shiny/driver/internal/drawer"
+ "golang.org/x/exp/shiny/driver/internal/event"
+ "golang.org/x/exp/shiny/driver/internal/lifecycler"
+ "golang.org/x/exp/shiny/driver/internal/x11key"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+ "golang.org/x/mobile/geom"
+)
+
+type windowImpl struct {
+ s *screenImpl
+
+ xw xproto.Window
+ xg xproto.Gcontext
+ xp render.Picture
+
+ event.Deque
+ xevents chan xgb.Event
+
+ // This next group of variables are mutable, but are only modified in the
+ // screenImpl.run goroutine.
+ width, height int
+
+ lifecycler lifecycler.State
+
+ mu sync.Mutex
+ released bool
+}
+
+func (w *windowImpl) Release() {
+ w.mu.Lock()
+ released := w.released
+ w.released = true
+ w.mu.Unlock()
+
+ // TODO: call w.lifecycler.SetDead and w.lifecycler.SendEvent, a la
+ // handling atomWMDeleteWindow?
+
+ if released {
+ return
+ }
+ render.FreePicture(w.s.xc, w.xp)
+ xproto.FreeGC(w.s.xc, w.xg)
+ xproto.DestroyWindow(w.s.xc, w.xw)
+}
+
+func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
+ src.(bufferUploader).upload(xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr)
+}
+
+func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
+ fill(w.s.xc, w.xp, dr, src, op)
+}
+
+func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ w.s.drawUniform(w.xp, &src2dst, src, sr, op, opts)
+}
+
+func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ src.(*textureImpl).draw(w.xp, &src2dst, sr, op, opts)
+}
+
+func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Copy(w, dp, src, sr, op, opts)
+}
+
+func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
+ drawer.Scale(w, dr, src, sr, op, opts)
+}
+
+func (w *windowImpl) Publish() screen.PublishResult {
+ // TODO: implement a back buffer, and copy or flip that here to the front
+ // buffer.
+
+ // This sync isn't needed to flush the outgoing X11 requests. Instead, it
+ // acts as a form of flow control. Outgoing requests can be quite small on
+ // the wire, e.g. draw this texture ID (an integer) to this rectangle (four
+ // more integers), but much more expensive on the server (blending a
+ // million source and destination pixels). Without this sync, the Go X11
+ // client could easily end up sending work at a faster rate than the X11
+ // server can serve.
+ w.s.xc.Sync()
+
+ return screen.PublishResult{}
+}
+
+func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) {
+ // TODO: does the order of these lifecycle and size events matter? Should
+ // they really be a single, atomic event?
+ w.lifecycler.SetVisible((int(ev.X)+int(ev.Width)) > 0 && (int(ev.Y)+int(ev.Height)) > 0)
+ w.lifecycler.SendEvent(w, nil)
+
+ newWidth, newHeight := int(ev.Width), int(ev.Height)
+ if w.width == newWidth && w.height == newHeight {
+ return
+ }
+ w.width, w.height = newWidth, newHeight
+ w.Send(size.Event{
+ WidthPx: newWidth,
+ HeightPx: newHeight,
+ WidthPt: geom.Pt(newWidth),
+ HeightPt: geom.Pt(newHeight),
+ PixelsPerPt: w.s.pixelsPerPt,
+ })
+}
+
+func (w *windowImpl) handleExpose() {
+ w.Send(paint.Event{})
+}
+
+func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) {
+ r, c := w.s.keysyms.Lookup(uint8(detail), state)
+ w.Send(key.Event{
+ Rune: r,
+ Code: c,
+ Modifiers: x11key.KeyModifiers(state),
+ Direction: dir,
+ })
+}
+
+func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) {
+ // TODO: should a mouse.Event have a separate MouseModifiers field, for
+ // which buttons are pressed during a mouse move?
+ btn := mouse.Button(b)
+ switch btn {
+ case 4:
+ btn = mouse.ButtonWheelUp
+ case 5:
+ btn = mouse.ButtonWheelDown
+ case 6:
+ btn = mouse.ButtonWheelLeft
+ case 7:
+ btn = mouse.ButtonWheelRight
+ }
+ if btn.IsWheel() {
+ if dir != mouse.DirPress {
+ return
+ }
+ dir = mouse.DirStep
+ }
+ w.Send(mouse.Event{
+ X: float32(x),
+ Y: float32(y),
+ Button: btn,
+ Modifiers: x11key.KeyModifiers(state),
+ Direction: dir,
+ })
+}
diff --git a/shiny/driver/x11driver/x11driver.go b/shiny/driver/x11driver/x11driver.go
new file mode 100644
index 0000000..f0f6d48
--- /dev/null
+++ b/shiny/driver/x11driver/x11driver.go
@@ -0,0 +1,64 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package x11driver provides the X11 driver for accessing a screen.
+package x11driver // import "golang.org/x/exp/shiny/driver/x11driver"
+
+// TODO: figure out what to say about the responsibility for users of this
+// package to check any implicit dependencies' LICENSEs. For example, the
+// driver might use third party software outside of golang.org/x, like an X11
+// or OpenGL library.
+
+import (
+ "fmt"
+
+ "github.com/jezek/xgb"
+ "github.com/jezek/xgb/render"
+ "github.com/jezek/xgb/shm"
+
+ "golang.org/x/exp/shiny/driver/internal/errscreen"
+ "golang.org/x/exp/shiny/screen"
+)
+
+// Main is called by the program's main function to run the graphical
+// application.
+//
+// It calls f on the Screen, possibly in a separate goroutine, as some OS-
+// specific libraries require being on 'the main thread'. It returns when f
+// returns.
+func Main(f func(screen.Screen)) {
+ if err := main(f); err != nil {
+ f(errscreen.Stub(err))
+ }
+}
+
+func main(f func(screen.Screen)) (retErr error) {
+ xc, err := xgb.NewConn()
+ if err != nil {
+ return fmt.Errorf("x11driver: xgb.NewConn failed: %v", err)
+ }
+ defer func() {
+ if retErr != nil {
+ xc.Close()
+ }
+ }()
+
+ if err := render.Init(xc); err != nil {
+ return fmt.Errorf("x11driver: render.Init failed: %v", err)
+ }
+
+ useShm := true
+ if err := shm.Init(xc); err != nil {
+ useShm = false
+ }
+
+ s, err := newScreenImpl(xc, useShm)
+ if err != nil {
+ return err
+ }
+ f(s)
+ // TODO: tear down the s.run goroutine? It's probably not worth the
+ // complexity of doing it cleanly, if the app is about to exit anyway.
+ return nil
+}
diff --git a/shiny/example/basic/main.go b/shiny/example/basic/main.go
new file mode 100644
index 0000000..8a35268
--- /dev/null
+++ b/shiny/example/basic/main.go
@@ -0,0 +1,187 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Basic is a basic example of a graphical application.
+package main
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "log"
+ "math"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/imageutil"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+var (
+ blue0 = color.RGBA{0x00, 0x00, 0x1f, 0xff}
+ blue1 = color.RGBA{0x00, 0x00, 0x3f, 0xff}
+ darkGray = color.RGBA{0x3f, 0x3f, 0x3f, 0xff}
+ green = color.RGBA{0x00, 0x7f, 0x00, 0x7f}
+ red = color.RGBA{0x7f, 0x00, 0x00, 0x7f}
+ yellow = color.RGBA{0x3f, 0x3f, 0x00, 0x3f}
+
+ cos30 = math.Cos(math.Pi / 6)
+ sin30 = math.Sin(math.Pi / 6)
+)
+
+func main() {
+ driver.Main(func(s screen.Screen) {
+ w, err := s.NewWindow(&screen.NewWindowOptions{
+ Title: "Basic Shiny Example",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer w.Release()
+
+ size0 := image.Point{256, 256}
+ b, err := s.NewBuffer(size0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer b.Release()
+ drawGradient(b.RGBA())
+
+ t0, err := s.NewTexture(size0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer t0.Release()
+ t0.Upload(image.Point{}, b, b.Bounds())
+
+ size1 := image.Point{32, 20}
+ t1, err := s.NewTexture(size1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer t1.Release()
+ t1.Fill(t1.Bounds(), green, screen.Src)
+ t1.Fill(t1.Bounds().Inset(2), red, screen.Over)
+ t1.Fill(t1.Bounds().Inset(4), red, screen.Src)
+
+ var sz size.Event
+ for {
+ e := w.NextEvent()
+
+ // This print message is to help programmers learn what events this
+ // example program generates. A real program shouldn't print such
+ // messages; they're not important to end users.
+ format := "got %#v\n"
+ if _, ok := e.(fmt.Stringer); ok {
+ format = "got %v\n"
+ }
+ fmt.Printf(format, e)
+
+ switch e := e.(type) {
+ case lifecycle.Event:
+ if e.To == lifecycle.StageDead {
+ return
+ }
+
+ case key.Event:
+ if e.Code == key.CodeEscape {
+ return
+ }
+
+ case paint.Event:
+ const inset = 10
+ for _, r := range imageutil.Border(sz.Bounds(), inset) {
+ w.Fill(r, blue0, screen.Src)
+ }
+ w.Fill(sz.Bounds().Inset(inset), blue1, screen.Src)
+ w.Upload(image.Point{20, 0}, b, b.Bounds())
+ w.Fill(image.Rect(50, 50, 350, 120), red, screen.Over)
+
+ // By default, draw the entirety of the texture using the Over
+ // operator. Uncomment one or both of the lines below to see
+ // their different effects.
+ op := screen.Over
+ // op = screen.Src
+ t0Rect := t0.Bounds()
+ // t0Rect = image.Rect(16, 0, 240, 100)
+
+ // Draw the texture t0 twice, as a 1:1 copy and under the
+ // transform src2dst.
+ w.Copy(image.Point{150, 100}, t0, t0Rect, op, nil)
+ src2dst := f64.Aff3{
+ +0.5 * cos30, -1.0 * sin30, 100,
+ +0.5 * sin30, +1.0 * cos30, 200,
+ }
+ w.Draw(src2dst, t0, t0Rect, op, nil)
+ w.DrawUniform(src2dst, yellow, t0Rect.Inset(30), screen.Over, nil)
+
+ // Draw crosses at the transformed corners of t0Rect.
+ for _, sx := range []int{t0Rect.Min.X, t0Rect.Max.X} {
+ for _, sy := range []int{t0Rect.Min.Y, t0Rect.Max.Y} {
+ dx := int(src2dst[0]*float64(sx) + src2dst[1]*float64(sy) + src2dst[2])
+ dy := int(src2dst[3]*float64(sx) + src2dst[4]*float64(sy) + src2dst[5])
+ w.Fill(image.Rect(dx-0, dy-1, dx+1, dy+2), darkGray, screen.Src)
+ w.Fill(image.Rect(dx-1, dy-0, dx+2, dy+1), darkGray, screen.Src)
+ }
+ }
+
+ // Draw t1.
+ w.Copy(image.Point{400, 50}, t1, t1.Bounds(), screen.Src, nil)
+
+ w.Publish()
+
+ case size.Event:
+ sz = e
+
+ case error:
+ log.Print(e)
+ }
+ }
+ })
+}
+
+func drawGradient(m *image.RGBA) {
+ b := m.Bounds()
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if x%64 == 0 || y%64 == 0 {
+ m.SetRGBA(x, y, color.RGBA{0xff, 0xff, 0xff, 0xff})
+ } else if x%64 == 63 || y%64 == 63 {
+ m.SetRGBA(x, y, color.RGBA{0x00, 0x00, 0xff, 0xff})
+ } else {
+ m.SetRGBA(x, y, color.RGBA{uint8(x), uint8(y), 0x00, 0xff})
+ }
+ }
+ }
+
+ // Round off the corners.
+ const radius = 64
+ lox := b.Min.X + radius - 1
+ loy := b.Min.Y + radius - 1
+ hix := b.Max.X - radius
+ hiy := b.Max.Y - radius
+ for y := 0; y < radius; y++ {
+ for x := 0; x < radius; x++ {
+ if x*x+y*y <= radius*radius {
+ continue
+ }
+ m.SetRGBA(lox-x, loy-y, color.RGBA{})
+ m.SetRGBA(hix+x, loy-y, color.RGBA{})
+ m.SetRGBA(lox-x, hiy+y, color.RGBA{})
+ m.SetRGBA(hix+x, hiy+y, color.RGBA{})
+ }
+ }
+}
diff --git a/shiny/example/basicgl/main.go b/shiny/example/basicgl/main.go
new file mode 100644
index 0000000..b097180
--- /dev/null
+++ b/shiny/example/basicgl/main.go
@@ -0,0 +1,232 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Basicgl demonstrates the use of Shiny's glwidget.
+package main
+
+import (
+ "encoding/binary"
+ "fmt"
+ "image"
+ "image/color"
+ "log"
+ "math"
+
+ "golang.org/x/exp/shiny/driver/gldriver"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/flex"
+ "golang.org/x/exp/shiny/widget/glwidget"
+ "golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/colornames"
+ "golang.org/x/mobile/gl"
+)
+
+func colorPatch(c color.Color, w, h unit.Value) *widget.Sizer {
+ return widget.NewSizer(w, h, widget.NewUniform(theme.StaticColor(c), nil))
+}
+
+func main() {
+ gldriver.Main(func(s screen.Screen) {
+ t1, t2 := newTriangleGL(), newTriangleGL()
+ defer t1.cleanup()
+ defer t2.cleanup()
+
+ body := widget.NewSheet(flex.NewFlex(
+ colorPatch(colornames.Green, unit.Pixels(50), unit.Pixels(50)),
+ widget.WithLayoutData(t1.w, flex.LayoutData{Grow: 1, Align: flex.AlignItemStretch}),
+ colorPatch(colornames.Blue, unit.Pixels(50), unit.Pixels(50)),
+ widget.WithLayoutData(t2.w, flex.LayoutData{MinSize: image.Point{80, 80}}),
+ colorPatch(colornames.Green, unit.Pixels(50), unit.Pixels(50)),
+ ))
+
+ if err := widget.RunWindow(s, body, &widget.RunWindowOptions{
+ NewWindowOptions: screen.NewWindowOptions{
+ Title: "BasicGL Shiny Example",
+ },
+ }); err != nil {
+ log.Fatal(err)
+ }
+ })
+}
+
+func newTriangleGL() *triangleGL {
+ t := new(triangleGL)
+ t.w = glwidget.NewGL(t.draw)
+ t.init()
+ return t
+}
+
+type triangleGL struct {
+ w *glwidget.GL
+
+ program gl.Program
+ position gl.Attrib
+ offset gl.Uniform
+ color gl.Uniform
+ buf gl.Buffer
+
+ green float32
+}
+
+func (t *triangleGL) init() {
+ glctx := t.w.Ctx
+ var err error
+ t.program, err = createProgram(glctx, vertexShader, fragmentShader)
+ if err != nil {
+ log.Fatalf("error creating GL program: %v", err)
+ }
+
+ t.buf = glctx.CreateBuffer()
+ glctx.BindBuffer(gl.ARRAY_BUFFER, t.buf)
+ glctx.BufferData(gl.ARRAY_BUFFER, triangleData, gl.STATIC_DRAW)
+
+ t.position = glctx.GetAttribLocation(t.program, "position")
+ t.color = glctx.GetUniformLocation(t.program, "color")
+ t.offset = glctx.GetUniformLocation(t.program, "offset")
+
+ glctx.UseProgram(t.program)
+ glctx.ClearColor(1, 0, 0, 1)
+}
+
+func (t *triangleGL) cleanup() {
+ glctx := t.w.Ctx
+ glctx.DeleteProgram(t.program)
+ glctx.DeleteBuffer(t.buf)
+}
+
+func (t *triangleGL) draw(w *glwidget.GL) {
+ glctx := t.w.Ctx
+
+ glctx.Viewport(0, 0, w.Rect.Dx(), w.Rect.Dy())
+ glctx.Clear(gl.COLOR_BUFFER_BIT)
+
+ t.green += 0.01
+ if t.green > 1 {
+ t.green = 0
+ }
+ glctx.Uniform4f(t.color, 0, t.green, 0, 1)
+ glctx.Uniform2f(t.offset, 0.2, 0.9)
+
+ glctx.BindBuffer(gl.ARRAY_BUFFER, t.buf)
+ glctx.EnableVertexAttribArray(t.position)
+ glctx.VertexAttribPointer(t.position, coordsPerVertex, gl.FLOAT, false, 0, 0)
+ glctx.DrawArrays(gl.TRIANGLES, 0, vertexCount)
+ glctx.DisableVertexAttribArray(t.position)
+ w.Publish()
+}
+
+// asBytes returns the byte representation of float32 values in the given byte
+// order. byteOrder must be either binary.BigEndian or binary.LittleEndian.
+func asBytes(byteOrder binary.ByteOrder, values ...float32) []byte {
+ le := false
+ switch byteOrder {
+ case binary.BigEndian:
+ case binary.LittleEndian:
+ le = true
+ default:
+ panic(fmt.Sprintf("invalid byte order %v", byteOrder))
+ }
+
+ b := make([]byte, 4*len(values))
+ for i, v := range values {
+ u := math.Float32bits(v)
+ if le {
+ b[4*i+0] = byte(u >> 0)
+ b[4*i+1] = byte(u >> 8)
+ b[4*i+2] = byte(u >> 16)
+ b[4*i+3] = byte(u >> 24)
+ } else {
+ b[4*i+0] = byte(u >> 24)
+ b[4*i+1] = byte(u >> 16)
+ b[4*i+2] = byte(u >> 8)
+ b[4*i+3] = byte(u >> 0)
+ }
+ }
+ return b
+}
+
+// createProgram creates, compiles, and links a gl.Program.
+func createProgram(glctx gl.Context, vertexSrc, fragmentSrc string) (gl.Program, error) {
+ program := glctx.CreateProgram()
+ if program.Value == 0 {
+ return gl.Program{}, fmt.Errorf("basicgl: no programs available")
+ }
+
+ vertexShader, err := loadShader(glctx, gl.VERTEX_SHADER, vertexSrc)
+ if err != nil {
+ return gl.Program{}, err
+ }
+ fragmentShader, err := loadShader(glctx, gl.FRAGMENT_SHADER, fragmentSrc)
+ if err != nil {
+ glctx.DeleteShader(vertexShader)
+ return gl.Program{}, err
+ }
+
+ glctx.AttachShader(program, vertexShader)
+ glctx.AttachShader(program, fragmentShader)
+ glctx.LinkProgram(program)
+
+ // Flag shaders for deletion when program is unlinked.
+ glctx.DeleteShader(vertexShader)
+ glctx.DeleteShader(fragmentShader)
+
+ if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 {
+ defer glctx.DeleteProgram(program)
+ return gl.Program{}, fmt.Errorf("basicgl: %s", glctx.GetProgramInfoLog(program))
+ }
+ return program, nil
+}
+
+func loadShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) {
+ shader := glctx.CreateShader(shaderType)
+ if shader.Value == 0 {
+ return gl.Shader{}, fmt.Errorf("basicgl: could not create shader (type %v)", shaderType)
+ }
+ glctx.ShaderSource(shader, src)
+ glctx.CompileShader(shader)
+ if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
+ defer glctx.DeleteShader(shader)
+ return gl.Shader{}, fmt.Errorf("basicgl: shader compile: %s", glctx.GetShaderInfoLog(shader))
+ }
+ return shader, nil
+}
+
+var triangleData = asBytes(binary.LittleEndian,
+ 0.0, 0.4, 0.0, // top left
+ 0.0, 0.0, 0.0, // bottom left
+ 0.4, 0.0, 0.0, // bottom right
+)
+
+const (
+ coordsPerVertex = 3
+ vertexCount = 3
+)
+
+const vertexShader = `#version 100
+uniform vec2 offset;
+
+attribute vec4 position;
+void main() {
+ // offset comes in with x/y values between 0 and 1.
+ // position bounds are -1 to 1.
+ vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0);
+ gl_Position = position + offset4;
+}`
+
+const fragmentShader = `#version 100
+precision mediump float;
+uniform vec4 color;
+void main() {
+ gl_FragColor = color;
+}`
diff --git a/shiny/example/fluid/main.go b/shiny/example/fluid/main.go
new file mode 100644
index 0000000..e410b42
--- /dev/null
+++ b/shiny/example/fluid/main.go
@@ -0,0 +1,396 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Fluid is a fluid dynamics simulator. It is based on Jos Stam, "Real-Time
+// Fluid Dynamics for Games", Proceedings of the Game Developer Conference,
+// March 2003. See
+// http://www.dgp.toronto.edu/people/stam/reality/Research/pub.html
+package main
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "log"
+ "sync"
+ "time"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+const (
+ N = 128 // The grid of cells has size NxN.
+
+ tickDuration = time.Second / 60
+
+ // These remaining numbers have magic values, determined by trial and error
+ // to look good, rather than being derived from first principles.
+ iterations = 20
+ dt = 0.1
+ diff = 0
+ visc = 0
+ force = 5
+ source = 20
+ fade = 0.89
+)
+
+func main() {
+ driver.Main(func(s screen.Screen) {
+ w, err := s.NewWindow(&screen.NewWindowOptions{
+ Title: "Fluid Shiny Example",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ buf, tex := screen.Buffer(nil), screen.Texture(nil)
+ defer func() {
+ if buf != nil {
+ tex.Release()
+ buf.Release()
+ }
+ w.Release()
+ }()
+
+ go simulate(w)
+
+ var (
+ buttonDown bool
+ sz size.Event
+ )
+ for {
+ publish := false
+
+ switch e := w.NextEvent().(type) {
+ case lifecycle.Event:
+ if e.To == lifecycle.StageDead {
+ return
+ }
+
+ switch e.Crosses(lifecycle.StageVisible) {
+ case lifecycle.CrossOn:
+ pauseChan <- play
+ var err error
+ buf, err = s.NewBuffer(image.Point{N, N})
+ if err != nil {
+ log.Fatal(err)
+ }
+ tex, err = s.NewTexture(image.Point{N, N})
+ if err != nil {
+ log.Fatal(err)
+ }
+ tex.Fill(tex.Bounds(), color.White, draw.Src)
+
+ case lifecycle.CrossOff:
+ pauseChan <- pause
+ tex.Release()
+ tex = nil
+ buf.Release()
+ buf = nil
+ }
+
+ case mouse.Event:
+ if e.Button == mouse.ButtonLeft {
+ buttonDown = e.Direction == mouse.DirPress
+ }
+ if !buttonDown {
+ break
+ }
+ z := sz.Size()
+ x := int(e.X) * N / z.X
+ y := int(e.Y) * N / z.Y
+ if x < 0 || N <= x || y < 0 || N <= y {
+ break
+ }
+
+ shared.mu.Lock()
+ shared.mouseEvents = append(shared.mouseEvents, image.Point{x, y})
+ shared.mu.Unlock()
+
+ case paint.Event:
+ publish = buf != nil
+
+ case size.Event:
+ sz = e
+
+ case uploadEvent:
+ shared.mu.Lock()
+ if buf != nil {
+ copy(buf.RGBA().Pix, shared.pix)
+ publish = true
+ }
+ shared.uploadEventSent = false
+ shared.mu.Unlock()
+
+ if publish {
+ tex.Upload(image.Point{}, buf, buf.Bounds())
+ }
+
+ case error:
+ log.Print(e)
+ }
+
+ if publish {
+ w.Scale(sz.Bounds(), tex, tex.Bounds(), draw.Src, nil)
+ w.Publish()
+ }
+ }
+ })
+}
+
+const (
+ pause = false
+ play = true
+)
+
+// pauseChan lets the UI event goroutine pause and play the CPU-intensive
+// simulation goroutine depending on whether the window is visible (e.g.
+// minimized). 64 should be large enough, in typical use, so that the former
+// doesn't ever block on the latter.
+var pauseChan = make(chan bool, 64)
+
+// uploadEvent signals that the shared pix slice should be uploaded to the
+// screen.Texture via the screen.Buffer.
+type uploadEvent struct{}
+
+var shared = struct {
+ mu sync.Mutex
+ uploadEventSent bool
+ mouseEvents []image.Point
+ pix []byte
+}{
+ pix: make([]byte, 4*N*N),
+}
+
+func simulate(q screen.EventDeque) {
+ var (
+ dens, densPrev array
+ u, uPrev array
+ v, vPrev array
+ xPrev, yPrev int
+ havePrevLoc bool
+ )
+
+ ticker := time.NewTicker(tickDuration)
+ var tickerC <-chan time.Time
+ for {
+ select {
+ case p := <-pauseChan:
+ if p == pause {
+ tickerC = nil
+ } else {
+ tickerC = ticker.C
+ }
+ continue
+ case <-tickerC:
+ }
+
+ shared.mu.Lock()
+ for _, p := range shared.mouseEvents {
+ dens[p.X+1][p.Y] = source
+ if havePrevLoc {
+ u[p.X+1][p.Y+1] = force * float32(p.X-xPrev)
+ v[p.X+1][p.Y+1] = force * float32(p.Y-yPrev)
+ }
+ xPrev, yPrev, havePrevLoc = p.X, p.Y, true
+ }
+ shared.mouseEvents = shared.mouseEvents[:0]
+ shared.mu.Unlock()
+
+ velStep(&u, &v, &uPrev, &vPrev)
+ densStep(&dens, &densPrev, &u, &v)
+
+ // This fade isn't part of Stam's GDC03 paper, but it looks nice.
+ for i := range dens {
+ for j := range dens[i] {
+ dens[i][j] *= fade
+ }
+ }
+
+ shared.mu.Lock()
+ for y := 0; y < N; y++ {
+ for x := 0; x < N; x++ {
+ d := int32(dens[x+1][y+1] * 0xff)
+ if d < 0 {
+ d = 0
+ } else if d > 0xff {
+ d = 0xff
+ }
+ v := 255 - uint8(d)
+ p := (N*y + x) * 4
+ shared.pix[p+0] = v
+ shared.pix[p+1] = v
+ shared.pix[p+2] = v
+ shared.pix[p+3] = 0xff
+ }
+ }
+ uploadEventSent := shared.uploadEventSent
+ shared.uploadEventSent = true
+ shared.mu.Unlock()
+
+ if !uploadEventSent {
+ q.Send(uploadEvent{})
+ }
+ }
+}
+
+// All of the remaining code more or less comes from Stam's GDC03 paper.
+
+type array [N + 2][N + 2]float32
+
+func addSource(x, s *array) {
+ for i := range x {
+ for j := range x[i] {
+ x[i][j] += dt * s[i][j]
+ }
+ }
+}
+
+func setBnd(b int, x *array) {
+ switch b {
+ case 0:
+ for i := 1; i <= N; i++ {
+ x[0+0][i] = +x[1][i]
+ x[N+1][i] = +x[N][i]
+ x[i][0+0] = +x[i][1]
+ x[i][N+1] = +x[i][N]
+ }
+ case 1:
+ for i := 1; i <= N; i++ {
+ x[0+0][i] = -x[1][i]
+ x[N+1][i] = -x[N][i]
+ x[i][0+0] = +x[i][1]
+ x[i][N+1] = +x[i][N]
+ }
+ case 2:
+ for i := 1; i <= N; i++ {
+ x[0+0][i] = +x[1][i]
+ x[N+1][i] = +x[N][i]
+ x[i][0+0] = -x[i][1]
+ x[i][N+1] = -x[i][N]
+ }
+ }
+ x[0+0][0+0] = 0.5 * (x[1][0+0] + x[0+0][1])
+ x[0+0][N+1] = 0.5 * (x[1][N+1] + x[0+0][N])
+ x[N+1][0+0] = 0.5 * (x[N][0+0] + x[N+1][1])
+ x[N+1][N+1] = 0.5 * (x[N][N+1] + x[N+1][N])
+}
+
+func linSolve(b int, x, x0 *array, a, c float32) {
+ // This if block isn't part of Stam's GDC03 paper, but it's a nice
+ // optimization when the diff diffusion parameter is zero.
+ if a == 0 && c == 1 {
+ for i := 1; i <= N; i++ {
+ for j := 1; j <= N; j++ {
+ x[i][j] = x0[i][j]
+ }
+ }
+ setBnd(b, x)
+ return
+ }
+
+ invC := 1 / c
+ for k := 0; k < iterations; k++ {
+ for i := 1; i <= N; i++ {
+ for j := 1; j <= N; j++ {
+ x[i][j] = (x0[i][j] + a*(x[i-1][j]+x[i+1][j]+x[i][j-1]+x[i][j+1])) * invC
+ }
+ }
+ setBnd(b, x)
+ }
+}
+
+func diffuse(b int, x, x0 *array, diff float32) {
+ a := dt * diff * N * N
+ linSolve(b, x, x0, a, 1+4*a)
+}
+
+func advect(b int, d, d0, u, v *array) {
+ const dt0 = dt * N
+ for i := 1; i <= N; i++ {
+ for j := 1; j <= N; j++ {
+ x := float32(i) - dt0*u[i][j]
+ if x < 0.5 {
+ x = 0.5
+ }
+ if x > N+0.5 {
+ x = N + 0.5
+ }
+ i0 := int(x)
+ i1 := i0 + 1
+
+ y := float32(j) - dt0*v[i][j]
+ if y < 0.5 {
+ y = 0.5
+ }
+ if y > N+0.5 {
+ y = N + 0.5
+ }
+ j0 := int(y)
+ j1 := j0 + 1
+
+ s1 := x - float32(i0)
+ s0 := 1 - s1
+ t1 := y - float32(j0)
+ t0 := 1 - t1
+ d[i][j] = s0*(t0*d0[i0][j0]+t1*d0[i0][j1]) + s1*(t0*d0[i1][j0]+t1*d0[i1][j1])
+ }
+ }
+ setBnd(b, d)
+}
+
+func project(u, v, p, div *array) {
+ for i := 1; i <= N; i++ {
+ for j := 1; j <= N; j++ {
+ div[i][j] = (u[i+1][j] - u[i-1][j] + v[i][j+1] - v[i][j-1]) / (-2 * N)
+ p[i][j] = 0
+ }
+ }
+ setBnd(0, div)
+ setBnd(0, p)
+ linSolve(0, p, div, 1, 4)
+ for i := 1; i <= N; i++ {
+ for j := 1; j <= N; j++ {
+ u[i][j] -= (N / 2) * (p[i+1][j+0] - p[i-1][j+0])
+ v[i][j] -= (N / 2) * (p[i+0][j+1] - p[i+0][j-1])
+ }
+ }
+ setBnd(1, u)
+ setBnd(2, v)
+}
+
+func velStep(u, v, u0, v0 *array) {
+ addSource(u, u0)
+ addSource(v, v0)
+ u0, u = u, u0
+ diffuse(1, u, u0, visc)
+ v0, v = v, v0
+ diffuse(2, v, v0, visc)
+ project(u, v, u0, v0)
+ u0, u = u, u0
+ v0, v = v, v0
+ advect(1, u, u0, u0, v0)
+ advect(2, v, v0, u0, v0)
+ project(u, v, u0, v0)
+}
+
+func densStep(x, x0, u, v *array) {
+ addSource(x, x0)
+ x0, x = x, x0
+ diffuse(0, x, x0, diff)
+ x0, x = x, x0
+ advect(0, x, x0, u, v)
+}
diff --git a/shiny/example/goban/asset/blackstone01.jpg b/shiny/example/goban/asset/blackstone01.jpg
new file mode 100644
index 0000000..7de3700
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone01.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone02.jpg b/shiny/example/goban/asset/blackstone02.jpg
new file mode 100644
index 0000000..ead5267
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone02.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone03.jpg b/shiny/example/goban/asset/blackstone03.jpg
new file mode 100644
index 0000000..461c439
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone03.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone04.jpg b/shiny/example/goban/asset/blackstone04.jpg
new file mode 100644
index 0000000..303aea2
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone04.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone05.jpg b/shiny/example/goban/asset/blackstone05.jpg
new file mode 100644
index 0000000..b75278b
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone05.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone06.jpg b/shiny/example/goban/asset/blackstone06.jpg
new file mode 100644
index 0000000..5438841
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone06.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone07.jpg b/shiny/example/goban/asset/blackstone07.jpg
new file mode 100644
index 0000000..1dc32e9
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone07.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone08.jpg b/shiny/example/goban/asset/blackstone08.jpg
new file mode 100644
index 0000000..6ae4e79
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone08.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone09.jpg b/shiny/example/goban/asset/blackstone09.jpg
new file mode 100644
index 0000000..d9cc064
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone09.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone10.jpg b/shiny/example/goban/asset/blackstone10.jpg
new file mode 100644
index 0000000..4215cba
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone10.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone11.jpg b/shiny/example/goban/asset/blackstone11.jpg
new file mode 100644
index 0000000..097214f
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone11.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone12.jpg b/shiny/example/goban/asset/blackstone12.jpg
new file mode 100644
index 0000000..0a6229b
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone12.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/blackstone13.jpg b/shiny/example/goban/asset/blackstone13.jpg
new file mode 100644
index 0000000..c013904
--- /dev/null
+++ b/shiny/example/goban/asset/blackstone13.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/goboard.jpg b/shiny/example/goban/asset/goboard.jpg
new file mode 100644
index 0000000..5e7878b
--- /dev/null
+++ b/shiny/example/goban/asset/goboard.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/resize.go b/shiny/example/goban/asset/resize.go
new file mode 100644
index 0000000..7d91152
--- /dev/null
+++ b/shiny/example/goban/asset/resize.go
@@ -0,0 +1,59 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+// +build ignore
+
+// Custom image resizer. Saved for posterity.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "image"
+ "image/jpeg"
+ _ "image/png"
+ "log"
+ "os"
+
+ "golang.org/x/image/draw"
+)
+
+var n = flag.Int("n", 0, "number of pixels to inset before scaling")
+
+func main() {
+ flag.Parse()
+ fmt.Println(flag.Args())
+ for _, s := range flag.Args() {
+ resize(s)
+ }
+}
+
+func resize(s string) {
+ in, err := os.Open(s)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer in.Close()
+ src, _, err := image.Decode(in)
+ if err != nil {
+ log.Fatal(err)
+ }
+ name := "../" + s
+ out, err := os.Create(name)
+ fmt.Println(name)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer out.Close()
+ dst := image.NewGray(image.Rect(0, 0, 256, 256))
+ draw.Draw(dst, dst.Bounds(), image.White, image.ZP, draw.Src)
+ dr := image.Rect(*n, *n, 256-*n, 256-*n)
+ draw.ApproxBiLinear.Scale(dst, dr, src, src.Bounds(), draw.Src, nil)
+ err = jpeg.Encode(out, dst, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/shiny/example/goban/asset/whitestone01.jpg b/shiny/example/goban/asset/whitestone01.jpg
new file mode 100644
index 0000000..43b3aa3
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone01.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone02.jpg b/shiny/example/goban/asset/whitestone02.jpg
new file mode 100644
index 0000000..ac1f08e
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone02.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone03.jpg b/shiny/example/goban/asset/whitestone03.jpg
new file mode 100644
index 0000000..93be38e
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone03.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone04.jpg b/shiny/example/goban/asset/whitestone04.jpg
new file mode 100644
index 0000000..191ddba
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone04.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone05.jpg b/shiny/example/goban/asset/whitestone05.jpg
new file mode 100644
index 0000000..ae3861b
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone05.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone06.jpg b/shiny/example/goban/asset/whitestone06.jpg
new file mode 100644
index 0000000..6b090fe
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone06.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone07.jpg b/shiny/example/goban/asset/whitestone07.jpg
new file mode 100644
index 0000000..4fbb068
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone07.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone08.jpg b/shiny/example/goban/asset/whitestone08.jpg
new file mode 100644
index 0000000..980a2d0
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone08.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone09.jpg b/shiny/example/goban/asset/whitestone09.jpg
new file mode 100644
index 0000000..a29ac56
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone09.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone10.jpg b/shiny/example/goban/asset/whitestone10.jpg
new file mode 100644
index 0000000..98717ce
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone10.jpg
Binary files differ
diff --git a/shiny/example/goban/asset/whitestone11.jpg b/shiny/example/goban/asset/whitestone11.jpg
new file mode 100644
index 0000000..50cddfd
--- /dev/null
+++ b/shiny/example/goban/asset/whitestone11.jpg
Binary files differ
diff --git a/shiny/example/goban/board.go b/shiny/example/goban/board.go
new file mode 100644
index 0000000..f176a90
--- /dev/null
+++ b/shiny/example/goban/board.go
@@ -0,0 +1,312 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go board.go xy.go" to run it
+// or "go install -tags=example" to install it.
+
+package main
+
+import (
+ "image"
+ "image/color"
+ _ "image/jpeg"
+ _ "image/png"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/image/draw"
+)
+
+const maxBoard = 21 // Maximum board size we can handle.
+
+var ZP = image.ZP // For brevity.
+
+type stoneColor uint8
+
+const (
+ blank stoneColor = iota // Unused
+ black
+ white
+)
+
+// Piece represents a stone on the board. A nil Piece is "blank".
+// The delta records pixel offset from the central dot.
+type Piece struct {
+ stone *Stone
+ ij IJ
+ delta image.Point
+ color stoneColor
+}
+
+type Board struct {
+ Dims
+ pieces []*Piece // The board. Dimensions are 1-indexed. 1, 1 is the lower left corner.
+ image *image.RGBA
+ stone []Stone // All the black stones, followed by all the white stones.
+ numBlackStones int
+ numWhiteStones int
+}
+
+type Stone struct {
+ originalImage *image.RGBA
+ originalMask *image.Alpha
+ image *image.RGBA
+ mask *image.Alpha
+}
+
+func NewBoard(dim, percent int) *Board {
+ switch dim {
+ case 9, 13, 19, 21:
+ default:
+ return nil
+ }
+ boardTexture := get("goboard.jpg", 0)
+ b := new(Board)
+ b.Dims.Init(dim, 100)
+ b.pieces = make([]*Piece, maxBoard*maxBoard)
+ b.image = image.NewRGBA(boardTexture.Bounds())
+ draw.Draw(b.image, b.image.Bounds(), boardTexture, ZP, draw.Src)
+ dir, err := os.Open("asset")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer dir.Close()
+ names, err := dir.Readdirnames(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ circleMask := makeCircle()
+ // Blackstones go first
+ for _, name := range names {
+ if strings.HasPrefix(name, "blackstone") {
+ s, m := makeStone(name, circleMask)
+ b.stone = append(b.stone, Stone{s, m, nil, nil})
+ b.numBlackStones++
+ }
+ }
+ for _, name := range names {
+ if strings.HasPrefix(name, "whitestone") {
+ s, m := makeStone(name, circleMask)
+ b.stone = append(b.stone, Stone{s, m, nil, nil})
+ b.numWhiteStones++
+ }
+ }
+ b.Resize(percent) // TODO
+ return b
+}
+
+func (b *Board) Resize(percent int) {
+ b.Dims.Resize(percent)
+ for i := range b.stone {
+ stone := &b.stone[i]
+ stone.image = resizeRGBA(stone.originalImage, b.stoneDiam)
+ stone.mask = resizeAlpha(stone.originalMask, b.stoneDiam)
+ }
+}
+
+func resizeRGBA(src *image.RGBA, size int) *image.RGBA {
+ dst := image.NewRGBA(image.Rect(0, 0, size, size))
+ draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil)
+ return dst
+}
+
+func resizeAlpha(src *image.Alpha, size int) *image.Alpha {
+ dst := image.NewAlpha(image.Rect(0, 0, size, size))
+ draw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil)
+ return dst
+}
+
+func (b *Board) piece(ij IJ) *Piece {
+ return b.pieces[(ij.j-1)*b.Dims.dim+ij.i-1]
+}
+
+func jitter() int {
+ max := 25 * *scale / 100
+ if max&1 == 0 {
+ max++
+ }
+ return rand.Intn(max) - max/2
+}
+
+func (b *Board) putPiece(ij IJ, piece *Piece) {
+ b.pieces[(ij.j-1)*b.Dims.dim+ij.i-1] = piece
+ if piece != nil {
+ piece.ij = ij
+ piece.delta = image.Point{jitter(), jitter()}
+ }
+}
+
+func (b *Board) selectBlackPiece() *Piece {
+ return &Piece{
+ stone: &b.stone[rand.Intn(b.numBlackStones)],
+ color: black,
+ }
+}
+
+func (b *Board) selectWhitePiece() *Piece {
+ return &Piece{
+ stone: &b.stone[b.numBlackStones+rand.Intn(b.numWhiteStones)],
+ color: white,
+ }
+}
+
+func makeStone(name string, circleMask *image.Alpha) (*image.RGBA, *image.Alpha) {
+ stone := get(name, stoneSize0)
+ dst := image.NewRGBA(stone.Bounds())
+ // Make the whole area black, for the shadow.
+ draw.Draw(dst, dst.Bounds(), image.Black, ZP, draw.Src)
+ // Lay in the stone within the circle so it shows up inside the shadow.
+ draw.DrawMask(dst, dst.Bounds(), stone, ZP, circleMask, ZP, draw.Over)
+ return dst, makeShadowMask(stone)
+}
+
+func get(name string, size int) image.Image {
+ f, err := os.Open(filepath.Join("asset", name))
+ if err != nil {
+ log.Fatal(err)
+ }
+ i, _, err := image.Decode(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+ f.Close()
+ if size != 0 {
+ r := i.Bounds()
+ if r.Dx() != size || r.Dy() != size {
+ log.Fatalf("bad stone size %s for %s; must be %d[2]Γ—%d[2]", r, name, size)
+ }
+ }
+ return i
+}
+
+func makeCircle() *image.Alpha {
+ mask := image.NewAlpha(image.Rect(0, 0, stoneSize0, stoneSize0))
+ // Make alpha work on stone.
+ // Shade gives shape, to be applied with black.
+ for y := 0; y < stoneSize0; y++ {
+ y2 := stoneSize0/2 - y
+ y2 *= y2
+ for x := 0; x < stoneSize0; x++ {
+ x2 := stoneSize0/2 - x
+ x2 *= x2
+ if x2+y2 <= stoneRad2 {
+ mask.SetAlpha(x, y, color.Alpha{255})
+ }
+ }
+ }
+ return mask
+}
+
+func makeShadowMask(stone image.Image) *image.Alpha {
+ mask := image.NewAlpha(stone.Bounds())
+ // Make alpha work on stone.
+ // Shade gives shape, to be applied with black.
+ const size = 256
+ const diam = 225
+ for y := 0; y < size; y++ {
+ y2 := size/2 - y
+ y2 *= y2
+ for x := 0; x < size; x++ {
+ x2 := size/2 - x
+ x2 *= x2
+ if x2+y2 > stoneRad2 {
+ red, _, _, _ := stone.At(x, y).RGBA()
+ mask.SetAlpha(x, y, color.Alpha{255 - uint8(red>>8)})
+ } else {
+ mask.SetAlpha(x, y, color.Alpha{255})
+ }
+ }
+ }
+ return mask
+}
+
+func (b *Board) Draw(m *image.RGBA) {
+ r := b.image.Bounds()
+ draw.Draw(m, r, b.image, ZP, draw.Src)
+ // Vertical lines.
+ x := b.xInset + b.squareWidth/2
+ y := b.yInset + b.squareHeight/2
+ wid := b.lineWidth
+ for i := 0; i < b.dim; i++ {
+ r := image.Rect(x, y, x+wid, y+(b.dim-1)*b.squareHeight)
+ draw.Draw(m, r, image.Black, ZP, draw.Src)
+ x += b.squareWidth
+ }
+ // Horizontal lines.
+ x = b.xInset + b.squareWidth/2
+ for i := 0; i < b.dim; i++ {
+ r := image.Rect(x, y, x+(b.dim-1)*b.squareWidth+wid, y+wid)
+ draw.Draw(m, r, image.Black, ZP, draw.Src)
+ y += b.squareHeight
+ }
+ // Points.
+ spot := 4
+ if b.dim < 13 {
+ spot = 3
+ }
+ points := []IJ{
+ {spot, spot},
+ {spot, (b.dim + 1) / 2},
+ {spot, b.dim + 1 - spot},
+ {(b.dim + 1) / 2, spot},
+ {(b.dim + 1) / 2, (b.dim + 1) / 2},
+ {(b.dim + 1) / 2, b.dim + 1 - spot},
+ {b.dim + 1 - spot, spot},
+ {b.dim + 1 - spot, (b.dim + 1) / 2},
+ {b.dim + 1 - spot, b.dim + 1 - spot},
+ }
+ for _, ij := range points {
+ b.drawPoint(m, ij)
+ }
+ // Pieces.
+ for i := 1; i <= b.dim; i++ {
+ for j := 1; j <= b.dim; j++ {
+ ij := IJ{i, j}
+ if p := b.piece(ij); p != nil {
+ b.drawPiece(m, ij, p)
+ }
+ }
+ }
+}
+
+func (b *Board) drawPoint(m *image.RGBA, ij IJ) {
+ pt := ij.XYCenter(&b.Dims)
+ wid := b.lineWidth
+ sz := wid * 3 / 2
+ r := image.Rect(pt.x-sz, pt.y-sz, pt.x+wid+sz, pt.y+wid+sz)
+ draw.Draw(m, r, image.Black, ZP, draw.Src)
+}
+
+func (b *Board) drawPiece(m *image.RGBA, ij IJ, piece *Piece) {
+ xy := ij.XYStone(&b.Dims)
+ xy = xy.Add(piece.delta)
+ draw.DrawMask(m, xy, piece.stone.image, ZP, piece.stone.mask, ZP, draw.Over)
+}
+
+func (b *Board) click(m *image.RGBA, x, y, button int) bool {
+ ij, ok := XY{x, y}.IJ(&b.Dims)
+ if !ok {
+ return false
+ }
+ switch button {
+ default:
+ return false
+ case 1:
+ b.putPiece(ij, b.selectBlackPiece())
+ case 2:
+ b.putPiece(ij, b.selectWhitePiece())
+ case 3:
+ b.putPiece(ij, nil)
+ }
+ render(m, b) // TODO: Connect this to paint events.
+ return true
+}
diff --git a/shiny/example/goban/main.go b/shiny/example/goban/main.go
new file mode 100644
index 0000000..f692b5c
--- /dev/null
+++ b/shiny/example/goban/main.go
@@ -0,0 +1,113 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go board.go xy.go" to run it
+// or "go install -tags=example" to install it.
+
+// Goban is a simple example of a graphics program using shiny.
+// It implements a Go board that two people can use to play the game.
+// TODO: Improve the main function.
+// TODO: Provide more functionality.
+package main
+
+import (
+ "flag"
+ "image"
+ "log"
+ "math/rand"
+ "time"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+var scale = flag.Int("scale", 35, "`percent` to scale images (TODO: a poor design)")
+
+func main() {
+ flag.Parse()
+
+ rand.Seed(int64(time.Now().Nanosecond()))
+ board := NewBoard(9, *scale)
+
+ driver.Main(func(s screen.Screen) {
+ w, err := s.NewWindow(&screen.NewWindowOptions{
+ Title: "Goban Shiny Example",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer w.Release()
+
+ var b screen.Buffer
+ defer func() {
+ if b != nil {
+ b.Release()
+ }
+ }()
+
+ for {
+ switch e := w.NextEvent().(type) {
+ case lifecycle.Event:
+ if e.To == lifecycle.StageDead {
+ return
+ }
+
+ case key.Event:
+ if e.Code == key.CodeEscape {
+ return
+ }
+
+ case mouse.Event:
+ if e.Direction != mouse.DirRelease {
+ break
+ }
+
+ // Re-map control-click to middle-click, etc, for computers with one-button mice.
+ if e.Modifiers&key.ModControl != 0 {
+ e.Button = mouse.ButtonMiddle
+ } else if e.Modifiers&key.ModAlt != 0 {
+ e.Button = mouse.ButtonRight
+ } else if e.Modifiers&key.ModMeta != 0 {
+ e.Button = mouse.ButtonMiddle
+ }
+
+ if board.click(b.RGBA(), int(e.X), int(e.Y), int(e.Button)) {
+ w.Send(paint.Event{})
+ }
+
+ case paint.Event:
+ w.Upload(image.Point{}, b, b.Bounds())
+ w.Publish()
+
+ case size.Event:
+ // TODO: Set board size.
+ if b != nil {
+ b.Release()
+ }
+ b, err = s.NewBuffer(e.Size())
+ if err != nil {
+ log.Fatal(err)
+ }
+ render(b.RGBA(), board)
+
+ case error:
+ log.Print(e)
+ }
+ }
+ })
+}
+
+func render(m *image.RGBA, board *Board) {
+ board.Draw(m)
+}
diff --git a/shiny/example/goban/xy.go b/shiny/example/goban/xy.go
new file mode 100644
index 0000000..7519e9c
--- /dev/null
+++ b/shiny/example/goban/xy.go
@@ -0,0 +1,133 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go board.go xy.go" to run it
+// or "go install -tags=example" to install it.
+
+package main
+
+import (
+ "fmt"
+ "image"
+)
+
+// Note that a "square" is not a drawn square on the board, but
+// the rectangle (sic) centered on the game point.
+type Dims struct {
+ dim int // Size of board, always square. Full size is 19.
+ percent int // Scale.
+ xInset int // From left edge to left edge of first square.
+ yInset int // From top edge to top edge of first square.
+ stoneDiam int // Diameter of stone in pixels.
+ stoneRad2 int // Stone radius squared.
+ squareWidth int // Width of square in pixels.
+ squareHeight int // Height of square in pixels.
+ lineWidth int // Width of lines.
+}
+
+// Initial values, in pixels. These numbers match the images that are loaded; they are resized.
+const (
+ // These numbers are tuned for the images in asset/.
+ xInset0 = 100 // Distance from the edge of the board image to the left side of the first stone.
+ yInset0 = 115 // Distance from the top of the board image to the top side of the first stone.
+ stoneSize0 = 256 // Size of a stone on the board.
+ stoneDiam0 = 225 // Diameter of the circular part of the stone within the square.
+ // The square is a little smaller than the stone, for crowding.
+ squareWidth0 = 215 // Width of a square on the board.
+ squareHeight0 = 218 // Height of a square on the board.
+ stoneRad2 = stoneDiam0 * stoneDiam0 / 4
+)
+
+func (d *Dims) Init(dim, percent int) {
+ d.dim = dim
+ d.Resize(percent)
+}
+
+func (d *Dims) Resize(percent int) {
+ d.percent = percent
+ d.xInset = xInset0 * percent / 100
+ d.yInset = yInset0 * percent / 100
+ d.stoneDiam = stoneDiam0 * percent / 100
+ d.squareWidth = squareWidth0 * percent / 100
+ d.squareHeight = squareHeight0 * percent / 100
+ d.stoneRad2 = d.stoneDiam * d.stoneDiam / 4
+ d.lineWidth = 4 * percent / 100
+ if d.lineWidth < 2 {
+ d.lineWidth = 2
+ }
+ if d.lineWidth > 2 {
+ d.lineWidth = 4
+ }
+}
+
+// An XY represents a pixel in the board image. Y is downwards.
+type XY struct {
+ x, y int
+}
+
+// An IJ represents a position on the board. It is 1-indexed and J is upwards.
+type IJ struct {
+ i, j int
+}
+
+func (ij IJ) String() string {
+ return fmt.Sprintf("%c%d", 'A'+int(ij.i-1), ij.j)
+}
+
+// A go board is played on centers, not on squares, but we think of it internally
+// as IJ squares (1, 1) through (19, 19), and draw the lines through the middle
+// of those squares. That is how the arithmetic is done converting between
+// IJ and XY.
+
+// IJ converts a position in the board image to a Go position (IJ) on the board.
+// The boolean is false if the point does not represent a Go position.
+func (xy XY) IJ(d *Dims) (IJ, bool) {
+ x, y := xy.x, xy.y
+ x -= d.xInset
+ y -= d.yInset
+ i := x / d.squareWidth
+ j := y / d.squareHeight
+ // Now zero indexed. Make j goes up and switch to 1-indexed.
+ j = d.dim - 1 - j
+ i++
+ j++
+ if 1 <= i && i <= d.dim && 1 <= j && j <= d.dim {
+ return IJ{i, j}, true
+ }
+ return IJ{}, false
+}
+
+// XY converts a Go position to the upper left corner of the square for that position
+// on the board image.
+func (ij IJ) XY(d *Dims) XY {
+ // Change to 0-indexed.
+ ij.i--
+ ij.j--
+ // j goes down.
+ ij.j = (d.dim - 1) - ij.j
+ return XY{d.xInset + ij.i*d.squareWidth, d.yInset + ij.j*d.squareHeight}
+}
+
+// IJtoXYCenter converts a Go position to the center of the square for that position
+// on the board image.
+func (ij IJ) XYCenter(d *Dims) XY {
+ xy := ij.XY(d)
+ xy.x += d.squareWidth / 2
+ xy.y += d.squareHeight / 2
+ return xy
+}
+
+// IJtoXYStone converts a Go position to the square holding the stone for that position
+// on the board image.
+func (ij IJ) XYStone(d *Dims) image.Rectangle {
+ center := ij.XYCenter(d)
+ min := image.Point{center.x - d.stoneDiam/2, center.y - d.stoneDiam/2}
+ max := image.Point{center.x + d.stoneDiam/2, center.y + d.stoneDiam/2}
+ return image.Rectangle{Min: min, Max: max}
+}
diff --git a/shiny/example/goban/xy_test.go b/shiny/example/goban/xy_test.go
new file mode 100644
index 0000000..fa8e608
--- /dev/null
+++ b/shiny/example/goban/xy_test.go
@@ -0,0 +1,79 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// Use "go test -tags=example" to run this test.
+
+package main
+
+import "testing"
+
+func TestIJXYCorners(t *testing.T) {
+ var d Dims
+ d.Init(19, 100)
+ xSize := (d.dim - 1) * d.squareWidth
+ ySize := (d.dim - 1) * d.squareHeight
+ ij := IJ{1, 1}
+ xy := ij.XY(&d)
+ if xy.x != d.xInset || xy.y != d.yInset+ySize {
+ t.Errorf("%d got %d", ij, xy)
+ }
+ ij = IJ{1, d.dim}
+ xy = ij.XY(&d)
+ if xy.x != d.xInset || xy.y != d.yInset {
+ t.Errorf("%d got %d", ij, xy)
+ }
+ ij = IJ{d.dim, 1}
+ xy = ij.XY(&d)
+ if xy.x != d.xInset+xSize || xy.y != d.yInset+ySize {
+ t.Errorf("%d got %d", ij, xy)
+ }
+ ij = IJ{d.dim, d.dim}
+ xy = ij.XY(&d)
+ if xy.x != d.xInset+xSize || xy.y != d.yInset {
+ t.Errorf("%d got %d", ij, xy)
+ }
+}
+
+func TestIJXYCenterRoundTrip(t *testing.T) {
+ var d Dims
+ d.Init(19, 100)
+ for i := 1; i <= d.dim; i++ {
+ for j := 1; j <= d.dim; j++ {
+ ij := IJ{i, j}
+ xy := ij.XYCenter(&d)
+ ij2, ok := xy.IJ(&d)
+ if !ok {
+ t.Error("failed to round trip")
+ }
+ if ij2 != ij {
+ t.Errorf("%d round trip got %d\n", ij, ij2)
+ }
+ }
+ }
+}
+
+func TestIJString(t *testing.T) {
+ var d Dims
+ d.Init(19, 100)
+ ij := IJ{1, 1}
+ if ij.String() != "A1" {
+ t.Errorf("%#v prints as %q", ij, ij.String())
+ }
+ ij = IJ{1, 19}
+ if ij.String() != "A19" {
+ t.Errorf("%#v prints as %q", ij, ij.String())
+ }
+ ij = IJ{19, 1}
+ if ij.String() != "S1" {
+ t.Errorf("%#v prints as %q", ij, ij.String())
+ }
+ ij = IJ{19, 19}
+ if ij.String() != "S19" {
+ t.Errorf("%#v prints as %q", ij, ij.String())
+ }
+}
diff --git a/shiny/example/icongallery/main.go b/shiny/example/icongallery/main.go
new file mode 100644
index 0000000..5f7c9ad
--- /dev/null
+++ b/shiny/example/icongallery/main.go
@@ -0,0 +1,1162 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Icongallery exhibits the Material Design icon set.
+package main
+
+// TODO: key events to navigate through the icons. Also add a search-by-name
+// drop-down combo box?
+
+import (
+ "image"
+ "image/draw"
+ "log"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/gesture"
+ "golang.org/x/exp/shiny/iconvg"
+ "golang.org/x/exp/shiny/materialdesign/icons"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+func stretch(n node.Node, alongWeight int) node.Node {
+ return widget.WithLayoutData(n, widget.FlowLayoutData{
+ AlongWeight: alongWeight,
+ ExpandAlong: true,
+ ShrinkAlong: true,
+ ExpandAcross: true,
+ ShrinkAcross: true,
+ })
+}
+
+type Body struct {
+ node.LeafEmbed
+ index int
+ z iconvg.Rasterizer
+}
+
+func NewBody() *Body {
+ w := &Body{}
+ w.Wrapper = w
+ return w
+}
+
+func (w *Body) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ wr := w.Rect.Add(origin)
+ for y := wr.Min.Y; y < wr.Max.Y; y += checkersLen {
+ for x := wr.Min.X; x < wr.Max.X; x += checkersLen {
+ r := wr.Intersect(image.Rectangle{
+ Min: image.Point{x + 0*checkersLen, y + 0*checkersLen},
+ Max: image.Point{x + 1*checkersLen, y + 1*checkersLen},
+ })
+ draw.Draw(ctx.Dst, r, checkers, image.Point{}, draw.Src)
+ }
+ }
+
+ wr = wr.Inset(16)
+ if wr.Empty() {
+ return nil
+ }
+ sz := wr.Size()
+ if d := sz.X - sz.Y; d > 0 {
+ wr.Min.X += d / 2
+ wr.Max.X = wr.Min.X + sz.Y
+ } else if d < 0 {
+ wr.Min.Y -= d / 2
+ wr.Max.Y = wr.Min.Y + sz.X
+ }
+ w.z.SetDstImage(ctx.Dst, wr, draw.Over)
+ return iconvg.Decode(&w.z, iconList[w.index].data, nil)
+}
+
+// TODO: make a standard widget.Button.
+
+type Button struct {
+ node.LeafEmbed
+ icon []byte
+ onClick func()
+ z iconvg.Rasterizer
+}
+
+func NewButton(icon []byte, onClick func()) *Button {
+ w := &Button{
+ icon: icon,
+ onClick: onClick,
+ }
+ w.Wrapper = w
+ return w
+}
+
+func (w *Button) Measure(t *theme.Theme, widthHint, heightHint int) {
+ px := t.Pixels(unit.Ems(1)).Ceil()
+ w.MeasuredSize = image.Point{px, px}
+}
+
+func (w *Button) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ w.z.SetDstImage(ctx.Dst, w.Rect.Add(origin), draw.Over)
+ return iconvg.Decode(&w.z, w.icon, nil)
+}
+
+func (w *Button) OnInputEvent(e interface{}, origin image.Point) node.EventHandled {
+ switch e := e.(type) {
+ case gesture.Event:
+ if e.Type != gesture.TypeTap {
+ break
+ }
+ if w.onClick != nil {
+ w.onClick()
+ }
+ return node.Handled
+ }
+ return node.NotHandled
+}
+
+func main() {
+ log.SetFlags(0)
+ driver.Main(func(s screen.Screen) {
+ body := NewBody()
+ iconName := widget.NewLabel(iconList[0].name)
+ inc := func(delta int) {
+ body.index += delta
+ if body.index < 0 {
+ body.index += len(iconList)
+ } else if body.index >= len(iconList) {
+ body.index -= len(iconList)
+ }
+ body.Mark(node.MarkNeedsPaintBase)
+ iconName.Text = iconList[body.index].name
+ iconName.Mark(node.MarkNeedsMeasureLayout | node.MarkNeedsPaintBase)
+ }
+
+ header := widget.NewUniform(theme.Neutral,
+ widget.NewPadder(widget.AxisBoth, unit.Ems(0.5),
+ widget.NewFlow(widget.AxisHorizontal,
+ NewButton(icons.NavigationChevronLeft, func() { inc(-1) }),
+ widget.NewSizer(unit.Ems(0.5), unit.Value{}, nil),
+ NewButton(icons.NavigationChevronRight, func() { inc(+1) }),
+ widget.NewSizer(unit.Ems(0.5), unit.Value{}, nil),
+ stretch(iconName, 1),
+ ),
+ ),
+ )
+ divider := widget.NewSizer(unit.Value{}, unit.DIPs(2),
+ widget.NewUniform(theme.Foreground, nil),
+ )
+
+ w := widget.NewFlow(widget.AxisVertical,
+ stretch(widget.NewSheet(header), 0),
+ stretch(widget.NewSheet(divider), 0),
+ stretch(widget.NewSheet(body), 1),
+ )
+
+ if err := widget.RunWindow(s, w, &widget.RunWindowOptions{
+ NewWindowOptions: screen.NewWindowOptions{
+ Title: "IconGallery Shiny Example",
+ },
+ }); err != nil {
+ log.Fatal(err)
+ }
+ })
+}
+
+const checkersLen = 256
+
+var checkers = func() image.Image {
+ m := image.NewRGBA(image.Rect(0, 0, checkersLen, checkersLen))
+ pix := m.Pix
+ for y := 0; y < checkersLen; y++ {
+ for x := 0; x < checkersLen; x++ {
+ g, b := uint8(0xc0), uint8(0xff)
+ if x&16 == y&16 {
+ g, b = b, g
+ }
+ pix[(y*checkersLen+x)*4+0] = 0xc0
+ pix[(y*checkersLen+x)*4+1] = g
+ pix[(y*checkersLen+x)*4+2] = b
+ pix[(y*checkersLen+x)*4+3] = 0xff
+ }
+ }
+ return m
+}()
+
+var iconList = []struct {
+ name string
+ data []byte
+}{
+ {"Action3DRotation", icons.Action3DRotation},
+ {"ActionAccessibility", icons.ActionAccessibility},
+ {"ActionAccessible", icons.ActionAccessible},
+ {"ActionAccountBalance", icons.ActionAccountBalance},
+ {"ActionAccountBalanceWallet", icons.ActionAccountBalanceWallet},
+ {"ActionAccountBox", icons.ActionAccountBox},
+ {"ActionAccountCircle", icons.ActionAccountCircle},
+ {"ActionAddShoppingCart", icons.ActionAddShoppingCart},
+ {"ActionAlarm", icons.ActionAlarm},
+ {"ActionAlarmAdd", icons.ActionAlarmAdd},
+ {"ActionAlarmOff", icons.ActionAlarmOff},
+ {"ActionAlarmOn", icons.ActionAlarmOn},
+ {"ActionAllOut", icons.ActionAllOut},
+ {"ActionAndroid", icons.ActionAndroid},
+ {"ActionAnnouncement", icons.ActionAnnouncement},
+ {"ActionAspectRatio", icons.ActionAspectRatio},
+ {"ActionAssessment", icons.ActionAssessment},
+ {"ActionAssignment", icons.ActionAssignment},
+ {"ActionAssignmentInd", icons.ActionAssignmentInd},
+ {"ActionAssignmentLate", icons.ActionAssignmentLate},
+ {"ActionAssignmentReturn", icons.ActionAssignmentReturn},
+ {"ActionAssignmentReturned", icons.ActionAssignmentReturned},
+ {"ActionAssignmentTurnedIn", icons.ActionAssignmentTurnedIn},
+ {"ActionAutorenew", icons.ActionAutorenew},
+ {"ActionBackup", icons.ActionBackup},
+ {"ActionBook", icons.ActionBook},
+ {"ActionBookmark", icons.ActionBookmark},
+ {"ActionBookmarkBorder", icons.ActionBookmarkBorder},
+ {"ActionBugReport", icons.ActionBugReport},
+ {"ActionBuild", icons.ActionBuild},
+ {"ActionCached", icons.ActionCached},
+ {"ActionCameraEnhance", icons.ActionCameraEnhance},
+ {"ActionCardGiftcard", icons.ActionCardGiftcard},
+ {"ActionCardMembership", icons.ActionCardMembership},
+ {"ActionCardTravel", icons.ActionCardTravel},
+ {"ActionChangeHistory", icons.ActionChangeHistory},
+ {"ActionCheckCircle", icons.ActionCheckCircle},
+ {"ActionChromeReaderMode", icons.ActionChromeReaderMode},
+ {"ActionClass", icons.ActionClass},
+ {"ActionCode", icons.ActionCode},
+ {"ActionCompareArrows", icons.ActionCompareArrows},
+ {"ActionCopyright", icons.ActionCopyright},
+ {"ActionCreditCard", icons.ActionCreditCard},
+ {"ActionDashboard", icons.ActionDashboard},
+ {"ActionDateRange", icons.ActionDateRange},
+ {"ActionDelete", icons.ActionDelete},
+ {"ActionDeleteForever", icons.ActionDeleteForever},
+ {"ActionDescription", icons.ActionDescription},
+ {"ActionDNS", icons.ActionDNS},
+ {"ActionDone", icons.ActionDone},
+ {"ActionDoneAll", icons.ActionDoneAll},
+ {"ActionDonutLarge", icons.ActionDonutLarge},
+ {"ActionDonutSmall", icons.ActionDonutSmall},
+ {"ActionEject", icons.ActionEject},
+ {"ActionEuroSymbol", icons.ActionEuroSymbol},
+ {"ActionEvent", icons.ActionEvent},
+ {"ActionEventSeat", icons.ActionEventSeat},
+ {"ActionExitToApp", icons.ActionExitToApp},
+ {"ActionExplore", icons.ActionExplore},
+ {"ActionExtension", icons.ActionExtension},
+ {"ActionFace", icons.ActionFace},
+ {"ActionFavorite", icons.ActionFavorite},
+ {"ActionFavoriteBorder", icons.ActionFavoriteBorder},
+ {"ActionFeedback", icons.ActionFeedback},
+ {"ActionFindInPage", icons.ActionFindInPage},
+ {"ActionFindReplace", icons.ActionFindReplace},
+ {"ActionFingerprint", icons.ActionFingerprint},
+ {"ActionFlightLand", icons.ActionFlightLand},
+ {"ActionFlightTakeoff", icons.ActionFlightTakeoff},
+ {"ActionFlipToBack", icons.ActionFlipToBack},
+ {"ActionFlipToFront", icons.ActionFlipToFront},
+ {"ActionGTranslate", icons.ActionGTranslate},
+ {"ActionGavel", icons.ActionGavel},
+ {"ActionGetApp", icons.ActionGetApp},
+ {"ActionGIF", icons.ActionGIF},
+ {"ActionGrade", icons.ActionGrade},
+ {"ActionGroupWork", icons.ActionGroupWork},
+ {"ActionHelp", icons.ActionHelp},
+ {"ActionHelpOutline", icons.ActionHelpOutline},
+ {"ActionHighlightOff", icons.ActionHighlightOff},
+ {"ActionHistory", icons.ActionHistory},
+ {"ActionHome", icons.ActionHome},
+ {"ActionHourglassEmpty", icons.ActionHourglassEmpty},
+ {"ActionHourglassFull", icons.ActionHourglassFull},
+ {"ActionHTTP", icons.ActionHTTP},
+ {"ActionHTTPS", icons.ActionHTTPS},
+ {"ActionImportantDevices", icons.ActionImportantDevices},
+ {"ActionInfo", icons.ActionInfo},
+ {"ActionInfoOutline", icons.ActionInfoOutline},
+ {"ActionInput", icons.ActionInput},
+ {"ActionInvertColors", icons.ActionInvertColors},
+ {"ActionLabel", icons.ActionLabel},
+ {"ActionLabelOutline", icons.ActionLabelOutline},
+ {"ActionLanguage", icons.ActionLanguage},
+ {"ActionLaunch", icons.ActionLaunch},
+ {"ActionLightbulbOutline", icons.ActionLightbulbOutline},
+ {"ActionLineStyle", icons.ActionLineStyle},
+ {"ActionLineWeight", icons.ActionLineWeight},
+ {"ActionList", icons.ActionList},
+ {"ActionLock", icons.ActionLock},
+ {"ActionLockOpen", icons.ActionLockOpen},
+ {"ActionLockOutline", icons.ActionLockOutline},
+ {"ActionLoyalty", icons.ActionLoyalty},
+ {"ActionMarkUnreadMailbox", icons.ActionMarkUnreadMailbox},
+ {"ActionMotorcycle", icons.ActionMotorcycle},
+ {"ActionNoteAdd", icons.ActionNoteAdd},
+ {"ActionOfflinePin", icons.ActionOfflinePin},
+ {"ActionOpacity", icons.ActionOpacity},
+ {"ActionOpenInBrowser", icons.ActionOpenInBrowser},
+ {"ActionOpenInNew", icons.ActionOpenInNew},
+ {"ActionOpenWith", icons.ActionOpenWith},
+ {"ActionPageview", icons.ActionPageview},
+ {"ActionPanTool", icons.ActionPanTool},
+ {"ActionPayment", icons.ActionPayment},
+ {"ActionPermCameraMic", icons.ActionPermCameraMic},
+ {"ActionPermContactCalendar", icons.ActionPermContactCalendar},
+ {"ActionPermDataSetting", icons.ActionPermDataSetting},
+ {"ActionPermDeviceInformation", icons.ActionPermDeviceInformation},
+ {"ActionPermIdentity", icons.ActionPermIdentity},
+ {"ActionPermMedia", icons.ActionPermMedia},
+ {"ActionPermPhoneMsg", icons.ActionPermPhoneMsg},
+ {"ActionPermScanWiFi", icons.ActionPermScanWiFi},
+ {"ActionPets", icons.ActionPets},
+ {"ActionPictureInPicture", icons.ActionPictureInPicture},
+ {"ActionPictureInPictureAlt", icons.ActionPictureInPictureAlt},
+ {"ActionPlayForWork", icons.ActionPlayForWork},
+ {"ActionPolymer", icons.ActionPolymer},
+ {"ActionPowerSettingsNew", icons.ActionPowerSettingsNew},
+ {"ActionPregnantWoman", icons.ActionPregnantWoman},
+ {"ActionPrint", icons.ActionPrint},
+ {"ActionQueryBuilder", icons.ActionQueryBuilder},
+ {"ActionQuestionAnswer", icons.ActionQuestionAnswer},
+ {"ActionReceipt", icons.ActionReceipt},
+ {"ActionRecordVoiceOver", icons.ActionRecordVoiceOver},
+ {"ActionRedeem", icons.ActionRedeem},
+ {"ActionRemoveShoppingCart", icons.ActionRemoveShoppingCart},
+ {"ActionReorder", icons.ActionReorder},
+ {"ActionReportProblem", icons.ActionReportProblem},
+ {"ActionRestore", icons.ActionRestore},
+ {"ActionRestorePage", icons.ActionRestorePage},
+ {"ActionRoom", icons.ActionRoom},
+ {"ActionRoundedCorner", icons.ActionRoundedCorner},
+ {"ActionRowing", icons.ActionRowing},
+ {"ActionSchedule", icons.ActionSchedule},
+ {"ActionSearch", icons.ActionSearch},
+ {"ActionSettings", icons.ActionSettings},
+ {"ActionSettingsApplications", icons.ActionSettingsApplications},
+ {"ActionSettingsBackupRestore", icons.ActionSettingsBackupRestore},
+ {"ActionSettingsBluetooth", icons.ActionSettingsBluetooth},
+ {"ActionSettingsBrightness", icons.ActionSettingsBrightness},
+ {"ActionSettingsCell", icons.ActionSettingsCell},
+ {"ActionSettingsEthernet", icons.ActionSettingsEthernet},
+ {"ActionSettingsInputAntenna", icons.ActionSettingsInputAntenna},
+ {"ActionSettingsInputComponent", icons.ActionSettingsInputComponent},
+ {"ActionSettingsInputComposite", icons.ActionSettingsInputComposite},
+ {"ActionSettingsInputHDMI", icons.ActionSettingsInputHDMI},
+ {"ActionSettingsInputSVideo", icons.ActionSettingsInputSVideo},
+ {"ActionSettingsOverscan", icons.ActionSettingsOverscan},
+ {"ActionSettingsPhone", icons.ActionSettingsPhone},
+ {"ActionSettingsPower", icons.ActionSettingsPower},
+ {"ActionSettingsRemote", icons.ActionSettingsRemote},
+ {"ActionSettingsVoice", icons.ActionSettingsVoice},
+ {"ActionShop", icons.ActionShop},
+ {"ActionShopTwo", icons.ActionShopTwo},
+ {"ActionShoppingBasket", icons.ActionShoppingBasket},
+ {"ActionShoppingCart", icons.ActionShoppingCart},
+ {"ActionSpeakerNotes", icons.ActionSpeakerNotes},
+ {"ActionSpeakerNotesOff", icons.ActionSpeakerNotesOff},
+ {"ActionSpellcheck", icons.ActionSpellcheck},
+ {"ActionStarRate", icons.ActionStarRate},
+ {"ActionStars", icons.ActionStars},
+ {"ActionStore", icons.ActionStore},
+ {"ActionSubject", icons.ActionSubject},
+ {"ActionSupervisorAccount", icons.ActionSupervisorAccount},
+ {"ActionSwapHoriz", icons.ActionSwapHoriz},
+ {"ActionSwapVert", icons.ActionSwapVert},
+ {"ActionSwapVerticalCircle", icons.ActionSwapVerticalCircle},
+ {"ActionSystemUpdateAlt", icons.ActionSystemUpdateAlt},
+ {"ActionTab", icons.ActionTab},
+ {"ActionTabUnselected", icons.ActionTabUnselected},
+ {"ActionTheaters", icons.ActionTheaters},
+ {"ActionThumbDown", icons.ActionThumbDown},
+ {"ActionThumbUp", icons.ActionThumbUp},
+ {"ActionThumbsUpDown", icons.ActionThumbsUpDown},
+ {"ActionTimeline", icons.ActionTimeline},
+ {"ActionTOC", icons.ActionTOC},
+ {"ActionToday", icons.ActionToday},
+ {"ActionToll", icons.ActionToll},
+ {"ActionTouchApp", icons.ActionTouchApp},
+ {"ActionTrackChanges", icons.ActionTrackChanges},
+ {"ActionTranslate", icons.ActionTranslate},
+ {"ActionTrendingDown", icons.ActionTrendingDown},
+ {"ActionTrendingFlat", icons.ActionTrendingFlat},
+ {"ActionTrendingUp", icons.ActionTrendingUp},
+ {"ActionTurnedIn", icons.ActionTurnedIn},
+ {"ActionTurnedInNot", icons.ActionTurnedInNot},
+ {"ActionUpdate", icons.ActionUpdate},
+ {"ActionVerifiedUser", icons.ActionVerifiedUser},
+ {"ActionViewAgenda", icons.ActionViewAgenda},
+ {"ActionViewArray", icons.ActionViewArray},
+ {"ActionViewCarousel", icons.ActionViewCarousel},
+ {"ActionViewColumn", icons.ActionViewColumn},
+ {"ActionViewDay", icons.ActionViewDay},
+ {"ActionViewHeadline", icons.ActionViewHeadline},
+ {"ActionViewList", icons.ActionViewList},
+ {"ActionViewModule", icons.ActionViewModule},
+ {"ActionViewQuilt", icons.ActionViewQuilt},
+ {"ActionViewStream", icons.ActionViewStream},
+ {"ActionViewWeek", icons.ActionViewWeek},
+ {"ActionVisibility", icons.ActionVisibility},
+ {"ActionVisibilityOff", icons.ActionVisibilityOff},
+ {"ActionWatchLater", icons.ActionWatchLater},
+ {"ActionWork", icons.ActionWork},
+ {"ActionYoutubeSearchedFor", icons.ActionYoutubeSearchedFor},
+ {"ActionZoomIn", icons.ActionZoomIn},
+ {"ActionZoomOut", icons.ActionZoomOut},
+ {"AlertAddAlert", icons.AlertAddAlert},
+ {"AlertError", icons.AlertError},
+ {"AlertErrorOutline", icons.AlertErrorOutline},
+ {"AlertWarning", icons.AlertWarning},
+ {"AVAddToQueue", icons.AVAddToQueue},
+ {"AVAirplay", icons.AVAirplay},
+ {"AVAlbum", icons.AVAlbum},
+ {"AVArtTrack", icons.AVArtTrack},
+ {"AVAVTimer", icons.AVAVTimer},
+ {"AVBrandingWatermark", icons.AVBrandingWatermark},
+ {"AVCallToAction", icons.AVCallToAction},
+ {"AVClosedCaption", icons.AVClosedCaption},
+ {"AVEqualizer", icons.AVEqualizer},
+ {"AVExplicit", icons.AVExplicit},
+ {"AVFastForward", icons.AVFastForward},
+ {"AVFastRewind", icons.AVFastRewind},
+ {"AVFeaturedPlayList", icons.AVFeaturedPlayList},
+ {"AVFeaturedVideo", icons.AVFeaturedVideo},
+ {"AVFiberDVR", icons.AVFiberDVR},
+ {"AVFiberManualRecord", icons.AVFiberManualRecord},
+ {"AVFiberNew", icons.AVFiberNew},
+ {"AVFiberPin", icons.AVFiberPin},
+ {"AVFiberSmartRecord", icons.AVFiberSmartRecord},
+ {"AVForward10", icons.AVForward10},
+ {"AVForward30", icons.AVForward30},
+ {"AVForward5", icons.AVForward5},
+ {"AVGames", icons.AVGames},
+ {"AVHD", icons.AVHD},
+ {"AVHearing", icons.AVHearing},
+ {"AVHighQuality", icons.AVHighQuality},
+ {"AVLibraryAdd", icons.AVLibraryAdd},
+ {"AVLibraryBooks", icons.AVLibraryBooks},
+ {"AVLibraryMusic", icons.AVLibraryMusic},
+ {"AVLoop", icons.AVLoop},
+ {"AVMic", icons.AVMic},
+ {"AVMicNone", icons.AVMicNone},
+ {"AVMicOff", icons.AVMicOff},
+ {"AVMovie", icons.AVMovie},
+ {"AVMusicVideo", icons.AVMusicVideo},
+ {"AVNewReleases", icons.AVNewReleases},
+ {"AVNotInterested", icons.AVNotInterested},
+ {"AVNote", icons.AVNote},
+ {"AVPause", icons.AVPause},
+ {"AVPauseCircleFilled", icons.AVPauseCircleFilled},
+ {"AVPauseCircleOutline", icons.AVPauseCircleOutline},
+ {"AVPlayArrow", icons.AVPlayArrow},
+ {"AVPlayCircleFilled", icons.AVPlayCircleFilled},
+ {"AVPlayCircleOutline", icons.AVPlayCircleOutline},
+ {"AVPlaylistAdd", icons.AVPlaylistAdd},
+ {"AVPlaylistAddCheck", icons.AVPlaylistAddCheck},
+ {"AVPlaylistPlay", icons.AVPlaylistPlay},
+ {"AVQueue", icons.AVQueue},
+ {"AVQueueMusic", icons.AVQueueMusic},
+ {"AVQueuePlayNext", icons.AVQueuePlayNext},
+ {"AVRadio", icons.AVRadio},
+ {"AVRecentActors", icons.AVRecentActors},
+ {"AVRemoveFromQueue", icons.AVRemoveFromQueue},
+ {"AVRepeat", icons.AVRepeat},
+ {"AVRepeatOne", icons.AVRepeatOne},
+ {"AVReplay", icons.AVReplay},
+ {"AVReplay10", icons.AVReplay10},
+ {"AVReplay30", icons.AVReplay30},
+ {"AVReplay5", icons.AVReplay5},
+ {"AVShuffle", icons.AVShuffle},
+ {"AVSkipNext", icons.AVSkipNext},
+ {"AVSkipPrevious", icons.AVSkipPrevious},
+ {"AVSlowMotionVideo", icons.AVSlowMotionVideo},
+ {"AVSnooze", icons.AVSnooze},
+ {"AVSortByAlpha", icons.AVSortByAlpha},
+ {"AVStop", icons.AVStop},
+ {"AVSubscriptions", icons.AVSubscriptions},
+ {"AVSubtitles", icons.AVSubtitles},
+ {"AVSurroundSound", icons.AVSurroundSound},
+ {"AVVideoCall", icons.AVVideoCall},
+ {"AVVideoLabel", icons.AVVideoLabel},
+ {"AVVideoLibrary", icons.AVVideoLibrary},
+ {"AVVideocam", icons.AVVideocam},
+ {"AVVideocamOff", icons.AVVideocamOff},
+ {"AVVolumeDown", icons.AVVolumeDown},
+ {"AVVolumeMute", icons.AVVolumeMute},
+ {"AVVolumeOff", icons.AVVolumeOff},
+ {"AVVolumeUp", icons.AVVolumeUp},
+ {"AVWeb", icons.AVWeb},
+ {"AVWebAsset", icons.AVWebAsset},
+ {"CommunicationBusiness", icons.CommunicationBusiness},
+ {"CommunicationCall", icons.CommunicationCall},
+ {"CommunicationCallEnd", icons.CommunicationCallEnd},
+ {"CommunicationCallMade", icons.CommunicationCallMade},
+ {"CommunicationCallMerge", icons.CommunicationCallMerge},
+ {"CommunicationCallMissed", icons.CommunicationCallMissed},
+ {"CommunicationCallMissedOutgoing", icons.CommunicationCallMissedOutgoing},
+ {"CommunicationCallReceived", icons.CommunicationCallReceived},
+ {"CommunicationCallSplit", icons.CommunicationCallSplit},
+ {"CommunicationChat", icons.CommunicationChat},
+ {"CommunicationChatBubble", icons.CommunicationChatBubble},
+ {"CommunicationChatBubbleOutline", icons.CommunicationChatBubbleOutline},
+ {"CommunicationClearAll", icons.CommunicationClearAll},
+ {"CommunicationComment", icons.CommunicationComment},
+ {"CommunicationContactMail", icons.CommunicationContactMail},
+ {"CommunicationContactPhone", icons.CommunicationContactPhone},
+ {"CommunicationContacts", icons.CommunicationContacts},
+ {"CommunicationDialerSIP", icons.CommunicationDialerSIP},
+ {"CommunicationDialpad", icons.CommunicationDialpad},
+ {"CommunicationEmail", icons.CommunicationEmail},
+ {"CommunicationForum", icons.CommunicationForum},
+ {"CommunicationImportContacts", icons.CommunicationImportContacts},
+ {"CommunicationImportExport", icons.CommunicationImportExport},
+ {"CommunicationInvertColorsOff", icons.CommunicationInvertColorsOff},
+ {"CommunicationLiveHelp", icons.CommunicationLiveHelp},
+ {"CommunicationLocationOff", icons.CommunicationLocationOff},
+ {"CommunicationLocationOn", icons.CommunicationLocationOn},
+ {"CommunicationMailOutline", icons.CommunicationMailOutline},
+ {"CommunicationMessage", icons.CommunicationMessage},
+ {"CommunicationNoSIM", icons.CommunicationNoSIM},
+ {"CommunicationPhone", icons.CommunicationPhone},
+ {"CommunicationPhoneLinkErase", icons.CommunicationPhoneLinkErase},
+ {"CommunicationPhoneLinkLock", icons.CommunicationPhoneLinkLock},
+ {"CommunicationPhoneLinkRing", icons.CommunicationPhoneLinkRing},
+ {"CommunicationPhoneLinkSetup", icons.CommunicationPhoneLinkSetup},
+ {"CommunicationPortableWiFiOff", icons.CommunicationPortableWiFiOff},
+ {"CommunicationPresentToAll", icons.CommunicationPresentToAll},
+ {"CommunicationRingVolume", icons.CommunicationRingVolume},
+ {"CommunicationRSSFeed", icons.CommunicationRSSFeed},
+ {"CommunicationScreenShare", icons.CommunicationScreenShare},
+ {"CommunicationSpeakerPhone", icons.CommunicationSpeakerPhone},
+ {"CommunicationStayCurrentLandscape", icons.CommunicationStayCurrentLandscape},
+ {"CommunicationStayCurrentPortrait", icons.CommunicationStayCurrentPortrait},
+ {"CommunicationStayPrimaryLandscape", icons.CommunicationStayPrimaryLandscape},
+ {"CommunicationStayPrimaryPortrait", icons.CommunicationStayPrimaryPortrait},
+ {"CommunicationStopScreenShare", icons.CommunicationStopScreenShare},
+ {"CommunicationSwapCalls", icons.CommunicationSwapCalls},
+ {"CommunicationTextSMS", icons.CommunicationTextSMS},
+ {"CommunicationVoicemail", icons.CommunicationVoicemail},
+ {"CommunicationVPNKey", icons.CommunicationVPNKey},
+ {"ContentAdd", icons.ContentAdd},
+ {"ContentAddBox", icons.ContentAddBox},
+ {"ContentAddCircle", icons.ContentAddCircle},
+ {"ContentAddCircleOutline", icons.ContentAddCircleOutline},
+ {"ContentArchive", icons.ContentArchive},
+ {"ContentBackspace", icons.ContentBackspace},
+ {"ContentBlock", icons.ContentBlock},
+ {"ContentClear", icons.ContentClear},
+ {"ContentContentCopy", icons.ContentContentCopy},
+ {"ContentContentCut", icons.ContentContentCut},
+ {"ContentContentPaste", icons.ContentContentPaste},
+ {"ContentCreate", icons.ContentCreate},
+ {"ContentDeleteSweep", icons.ContentDeleteSweep},
+ {"ContentDrafts", icons.ContentDrafts},
+ {"ContentFilterList", icons.ContentFilterList},
+ {"ContentFlag", icons.ContentFlag},
+ {"ContentFontDownload", icons.ContentFontDownload},
+ {"ContentForward", icons.ContentForward},
+ {"ContentGesture", icons.ContentGesture},
+ {"ContentInbox", icons.ContentInbox},
+ {"ContentLink", icons.ContentLink},
+ {"ContentLowPriority", icons.ContentLowPriority},
+ {"ContentMail", icons.ContentMail},
+ {"ContentMarkUnread", icons.ContentMarkUnread},
+ {"ContentMoveToInbox", icons.ContentMoveToInbox},
+ {"ContentNextWeek", icons.ContentNextWeek},
+ {"ContentRedo", icons.ContentRedo},
+ {"ContentRemove", icons.ContentRemove},
+ {"ContentRemoveCircle", icons.ContentRemoveCircle},
+ {"ContentRemoveCircleOutline", icons.ContentRemoveCircleOutline},
+ {"ContentReply", icons.ContentReply},
+ {"ContentReplyAll", icons.ContentReplyAll},
+ {"ContentReport", icons.ContentReport},
+ {"ContentSave", icons.ContentSave},
+ {"ContentSelectAll", icons.ContentSelectAll},
+ {"ContentSend", icons.ContentSend},
+ {"ContentSort", icons.ContentSort},
+ {"ContentTextFormat", icons.ContentTextFormat},
+ {"ContentUnarchive", icons.ContentUnarchive},
+ {"ContentUndo", icons.ContentUndo},
+ {"ContentWeekend", icons.ContentWeekend},
+ {"DeviceAccessAlarm", icons.DeviceAccessAlarm},
+ {"DeviceAccessAlarms", icons.DeviceAccessAlarms},
+ {"DeviceAccessTime", icons.DeviceAccessTime},
+ {"DeviceAddAlarm", icons.DeviceAddAlarm},
+ {"DeviceAirplaneModeActive", icons.DeviceAirplaneModeActive},
+ {"DeviceAirplaneModeInactive", icons.DeviceAirplaneModeInactive},
+ {"DeviceBattery20", icons.DeviceBattery20},
+ {"DeviceBattery30", icons.DeviceBattery30},
+ {"DeviceBattery50", icons.DeviceBattery50},
+ {"DeviceBattery60", icons.DeviceBattery60},
+ {"DeviceBattery80", icons.DeviceBattery80},
+ {"DeviceBattery90", icons.DeviceBattery90},
+ {"DeviceBatteryAlert", icons.DeviceBatteryAlert},
+ {"DeviceBatteryCharging20", icons.DeviceBatteryCharging20},
+ {"DeviceBatteryCharging30", icons.DeviceBatteryCharging30},
+ {"DeviceBatteryCharging50", icons.DeviceBatteryCharging50},
+ {"DeviceBatteryCharging60", icons.DeviceBatteryCharging60},
+ {"DeviceBatteryCharging80", icons.DeviceBatteryCharging80},
+ {"DeviceBatteryCharging90", icons.DeviceBatteryCharging90},
+ {"DeviceBatteryChargingFull", icons.DeviceBatteryChargingFull},
+ {"DeviceBatteryFull", icons.DeviceBatteryFull},
+ {"DeviceBatteryStd", icons.DeviceBatteryStd},
+ {"DeviceBatteryUnknown", icons.DeviceBatteryUnknown},
+ {"DeviceBluetooth", icons.DeviceBluetooth},
+ {"DeviceBluetoothConnected", icons.DeviceBluetoothConnected},
+ {"DeviceBluetoothDisabled", icons.DeviceBluetoothDisabled},
+ {"DeviceBluetoothSearching", icons.DeviceBluetoothSearching},
+ {"DeviceBrightnessAuto", icons.DeviceBrightnessAuto},
+ {"DeviceBrightnessHigh", icons.DeviceBrightnessHigh},
+ {"DeviceBrightnessLow", icons.DeviceBrightnessLow},
+ {"DeviceBrightnessMedium", icons.DeviceBrightnessMedium},
+ {"DeviceDataUsage", icons.DeviceDataUsage},
+ {"DeviceDeveloperMode", icons.DeviceDeveloperMode},
+ {"DeviceDevices", icons.DeviceDevices},
+ {"DeviceDVR", icons.DeviceDVR},
+ {"DeviceGPSFixed", icons.DeviceGPSFixed},
+ {"DeviceGPSNotFixed", icons.DeviceGPSNotFixed},
+ {"DeviceGPSOff", icons.DeviceGPSOff},
+ {"DeviceGraphicEq", icons.DeviceGraphicEq},
+ {"DeviceLocationDisabled", icons.DeviceLocationDisabled},
+ {"DeviceLocationSearching", icons.DeviceLocationSearching},
+ {"DeviceNetworkCell", icons.DeviceNetworkCell},
+ {"DeviceNetworkWiFi", icons.DeviceNetworkWiFi},
+ {"DeviceNFC", icons.DeviceNFC},
+ {"DeviceScreenLockLandscape", icons.DeviceScreenLockLandscape},
+ {"DeviceScreenLockPortrait", icons.DeviceScreenLockPortrait},
+ {"DeviceScreenLockRotation", icons.DeviceScreenLockRotation},
+ {"DeviceScreenRotation", icons.DeviceScreenRotation},
+ {"DeviceSDStorage", icons.DeviceSDStorage},
+ {"DeviceSettingsSystemDaydream", icons.DeviceSettingsSystemDaydream},
+ {"DeviceSignalCellular0Bar", icons.DeviceSignalCellular0Bar},
+ {"DeviceSignalCellular1Bar", icons.DeviceSignalCellular1Bar},
+ {"DeviceSignalCellular2Bar", icons.DeviceSignalCellular2Bar},
+ {"DeviceSignalCellular3Bar", icons.DeviceSignalCellular3Bar},
+ {"DeviceSignalCellular4Bar", icons.DeviceSignalCellular4Bar},
+ {"DeviceSignalCellularConnectedNoInternet0Bar", icons.DeviceSignalCellularConnectedNoInternet0Bar},
+ {"DeviceSignalCellularConnectedNoInternet1Bar", icons.DeviceSignalCellularConnectedNoInternet1Bar},
+ {"DeviceSignalCellularConnectedNoInternet2Bar", icons.DeviceSignalCellularConnectedNoInternet2Bar},
+ {"DeviceSignalCellularConnectedNoInternet3Bar", icons.DeviceSignalCellularConnectedNoInternet3Bar},
+ {"DeviceSignalCellularConnectedNoInternet4Bar", icons.DeviceSignalCellularConnectedNoInternet4Bar},
+ {"DeviceSignalCellularNoSIM", icons.DeviceSignalCellularNoSIM},
+ {"DeviceSignalCellularNull", icons.DeviceSignalCellularNull},
+ {"DeviceSignalCellularOff", icons.DeviceSignalCellularOff},
+ {"DeviceSignalWiFi0Bar", icons.DeviceSignalWiFi0Bar},
+ {"DeviceSignalWiFi1Bar", icons.DeviceSignalWiFi1Bar},
+ {"DeviceSignalWiFi1BarLock", icons.DeviceSignalWiFi1BarLock},
+ {"DeviceSignalWiFi2Bar", icons.DeviceSignalWiFi2Bar},
+ {"DeviceSignalWiFi2BarLock", icons.DeviceSignalWiFi2BarLock},
+ {"DeviceSignalWiFi3Bar", icons.DeviceSignalWiFi3Bar},
+ {"DeviceSignalWiFi3BarLock", icons.DeviceSignalWiFi3BarLock},
+ {"DeviceSignalWiFi4Bar", icons.DeviceSignalWiFi4Bar},
+ {"DeviceSignalWiFi4BarLock", icons.DeviceSignalWiFi4BarLock},
+ {"DeviceSignalWiFiOff", icons.DeviceSignalWiFiOff},
+ {"DeviceStorage", icons.DeviceStorage},
+ {"DeviceUSB", icons.DeviceUSB},
+ {"DeviceWallpaper", icons.DeviceWallpaper},
+ {"DeviceWidgets", icons.DeviceWidgets},
+ {"DeviceWiFiLock", icons.DeviceWiFiLock},
+ {"DeviceWiFiTethering", icons.DeviceWiFiTethering},
+ {"EditorAttachFile", icons.EditorAttachFile},
+ {"EditorAttachMoney", icons.EditorAttachMoney},
+ {"EditorBorderAll", icons.EditorBorderAll},
+ {"EditorBorderBottom", icons.EditorBorderBottom},
+ {"EditorBorderClear", icons.EditorBorderClear},
+ {"EditorBorderColor", icons.EditorBorderColor},
+ {"EditorBorderHorizontal", icons.EditorBorderHorizontal},
+ {"EditorBorderInner", icons.EditorBorderInner},
+ {"EditorBorderLeft", icons.EditorBorderLeft},
+ {"EditorBorderOuter", icons.EditorBorderOuter},
+ {"EditorBorderRight", icons.EditorBorderRight},
+ {"EditorBorderStyle", icons.EditorBorderStyle},
+ {"EditorBorderTop", icons.EditorBorderTop},
+ {"EditorBorderVertical", icons.EditorBorderVertical},
+ {"EditorBubbleChart", icons.EditorBubbleChart},
+ {"EditorDragHandle", icons.EditorDragHandle},
+ {"EditorFormatAlignCenter", icons.EditorFormatAlignCenter},
+ {"EditorFormatAlignJustify", icons.EditorFormatAlignJustify},
+ {"EditorFormatAlignLeft", icons.EditorFormatAlignLeft},
+ {"EditorFormatAlignRight", icons.EditorFormatAlignRight},
+ {"EditorFormatBold", icons.EditorFormatBold},
+ {"EditorFormatClear", icons.EditorFormatClear},
+ {"EditorFormatColorFill", icons.EditorFormatColorFill},
+ {"EditorFormatColorReset", icons.EditorFormatColorReset},
+ {"EditorFormatColorText", icons.EditorFormatColorText},
+ {"EditorFormatIndentDecrease", icons.EditorFormatIndentDecrease},
+ {"EditorFormatIndentIncrease", icons.EditorFormatIndentIncrease},
+ {"EditorFormatItalic", icons.EditorFormatItalic},
+ {"EditorFormatLineSpacing", icons.EditorFormatLineSpacing},
+ {"EditorFormatListBulleted", icons.EditorFormatListBulleted},
+ {"EditorFormatListNumbered", icons.EditorFormatListNumbered},
+ {"EditorFormatPaint", icons.EditorFormatPaint},
+ {"EditorFormatQuote", icons.EditorFormatQuote},
+ {"EditorFormatShapes", icons.EditorFormatShapes},
+ {"EditorFormatSize", icons.EditorFormatSize},
+ {"EditorFormatStrikethrough", icons.EditorFormatStrikethrough},
+ {"EditorFormatTextDirectionLToR", icons.EditorFormatTextDirectionLToR},
+ {"EditorFormatTextDirectionRToL", icons.EditorFormatTextDirectionRToL},
+ {"EditorFormatUnderlined", icons.EditorFormatUnderlined},
+ {"EditorFunctions", icons.EditorFunctions},
+ {"EditorHighlight", icons.EditorHighlight},
+ {"EditorInsertChart", icons.EditorInsertChart},
+ {"EditorInsertComment", icons.EditorInsertComment},
+ {"EditorInsertDriveFile", icons.EditorInsertDriveFile},
+ {"EditorInsertEmoticon", icons.EditorInsertEmoticon},
+ {"EditorInsertInvitation", icons.EditorInsertInvitation},
+ {"EditorInsertLink", icons.EditorInsertLink},
+ {"EditorInsertPhoto", icons.EditorInsertPhoto},
+ {"EditorLinearScale", icons.EditorLinearScale},
+ {"EditorMergeType", icons.EditorMergeType},
+ {"EditorModeComment", icons.EditorModeComment},
+ {"EditorModeEdit", icons.EditorModeEdit},
+ {"EditorMonetizationOn", icons.EditorMonetizationOn},
+ {"EditorMoneyOff", icons.EditorMoneyOff},
+ {"EditorMultilineChart", icons.EditorMultilineChart},
+ {"EditorPieChart", icons.EditorPieChart},
+ {"EditorPieChartOutlined", icons.EditorPieChartOutlined},
+ {"EditorPublish", icons.EditorPublish},
+ {"EditorShortText", icons.EditorShortText},
+ {"EditorShowChart", icons.EditorShowChart},
+ {"EditorSpaceBar", icons.EditorSpaceBar},
+ {"EditorStrikethroughS", icons.EditorStrikethroughS},
+ {"EditorTextFields", icons.EditorTextFields},
+ {"EditorTitle", icons.EditorTitle},
+ {"EditorVerticalAlignBottom", icons.EditorVerticalAlignBottom},
+ {"EditorVerticalAlignCenter", icons.EditorVerticalAlignCenter},
+ {"EditorVerticalAlignTop", icons.EditorVerticalAlignTop},
+ {"EditorWrapText", icons.EditorWrapText},
+ {"FileAttachment", icons.FileAttachment},
+ {"FileCloud", icons.FileCloud},
+ {"FileCloudCircle", icons.FileCloudCircle},
+ {"FileCloudDone", icons.FileCloudDone},
+ {"FileCloudDownload", icons.FileCloudDownload},
+ {"FileCloudOff", icons.FileCloudOff},
+ {"FileCloudQueue", icons.FileCloudQueue},
+ {"FileCloudUpload", icons.FileCloudUpload},
+ {"FileCreateNewFolder", icons.FileCreateNewFolder},
+ {"FileFileDownload", icons.FileFileDownload},
+ {"FileFileUpload", icons.FileFileUpload},
+ {"FileFolder", icons.FileFolder},
+ {"FileFolderOpen", icons.FileFolderOpen},
+ {"FileFolderShared", icons.FileFolderShared},
+ {"HardwareCast", icons.HardwareCast},
+ {"HardwareCastConnected", icons.HardwareCastConnected},
+ {"HardwareComputer", icons.HardwareComputer},
+ {"HardwareDesktopMac", icons.HardwareDesktopMac},
+ {"HardwareDesktopWindows", icons.HardwareDesktopWindows},
+ {"HardwareDeveloperBoard", icons.HardwareDeveloperBoard},
+ {"HardwareDeviceHub", icons.HardwareDeviceHub},
+ {"HardwareDevicesOther", icons.HardwareDevicesOther},
+ {"HardwareDock", icons.HardwareDock},
+ {"HardwareGamepad", icons.HardwareGamepad},
+ {"HardwareHeadset", icons.HardwareHeadset},
+ {"HardwareHeadsetMic", icons.HardwareHeadsetMic},
+ {"HardwareKeyboard", icons.HardwareKeyboard},
+ {"HardwareKeyboardArrowDown", icons.HardwareKeyboardArrowDown},
+ {"HardwareKeyboardArrowLeft", icons.HardwareKeyboardArrowLeft},
+ {"HardwareKeyboardArrowRight", icons.HardwareKeyboardArrowRight},
+ {"HardwareKeyboardArrowUp", icons.HardwareKeyboardArrowUp},
+ {"HardwareKeyboardBackspace", icons.HardwareKeyboardBackspace},
+ {"HardwareKeyboardCapslock", icons.HardwareKeyboardCapslock},
+ {"HardwareKeyboardHide", icons.HardwareKeyboardHide},
+ {"HardwareKeyboardReturn", icons.HardwareKeyboardReturn},
+ {"HardwareKeyboardTab", icons.HardwareKeyboardTab},
+ {"HardwareKeyboardVoice", icons.HardwareKeyboardVoice},
+ {"HardwareLaptop", icons.HardwareLaptop},
+ {"HardwareLaptopChromebook", icons.HardwareLaptopChromebook},
+ {"HardwareLaptopMac", icons.HardwareLaptopMac},
+ {"HardwareLaptopWindows", icons.HardwareLaptopWindows},
+ {"HardwareMemory", icons.HardwareMemory},
+ {"HardwareMouse", icons.HardwareMouse},
+ {"HardwarePhoneAndroid", icons.HardwarePhoneAndroid},
+ {"HardwarePhoneIPhone", icons.HardwarePhoneIPhone},
+ {"HardwarePhoneLink", icons.HardwarePhoneLink},
+ {"HardwarePhoneLinkOff", icons.HardwarePhoneLinkOff},
+ {"HardwarePowerInput", icons.HardwarePowerInput},
+ {"HardwareRouter", icons.HardwareRouter},
+ {"HardwareScanner", icons.HardwareScanner},
+ {"HardwareSecurity", icons.HardwareSecurity},
+ {"HardwareSIMCard", icons.HardwareSIMCard},
+ {"HardwareSmartphone", icons.HardwareSmartphone},
+ {"HardwareSpeaker", icons.HardwareSpeaker},
+ {"HardwareSpeakerGroup", icons.HardwareSpeakerGroup},
+ {"HardwareTablet", icons.HardwareTablet},
+ {"HardwareTabletAndroid", icons.HardwareTabletAndroid},
+ {"HardwareTabletMac", icons.HardwareTabletMac},
+ {"HardwareToys", icons.HardwareToys},
+ {"HardwareTV", icons.HardwareTV},
+ {"HardwareVideogameAsset", icons.HardwareVideogameAsset},
+ {"HardwareWatch", icons.HardwareWatch},
+ {"ImageAddAPhoto", icons.ImageAddAPhoto},
+ {"ImageAddToPhotos", icons.ImageAddToPhotos},
+ {"ImageAdjust", icons.ImageAdjust},
+ {"ImageAssistant", icons.ImageAssistant},
+ {"ImageAssistantPhoto", icons.ImageAssistantPhoto},
+ {"ImageAudiotrack", icons.ImageAudiotrack},
+ {"ImageBlurCircular", icons.ImageBlurCircular},
+ {"ImageBlurLinear", icons.ImageBlurLinear},
+ {"ImageBlurOff", icons.ImageBlurOff},
+ {"ImageBlurOn", icons.ImageBlurOn},
+ {"ImageBrightness1", icons.ImageBrightness1},
+ {"ImageBrightness2", icons.ImageBrightness2},
+ {"ImageBrightness3", icons.ImageBrightness3},
+ {"ImageBrightness4", icons.ImageBrightness4},
+ {"ImageBrightness5", icons.ImageBrightness5},
+ {"ImageBrightness6", icons.ImageBrightness6},
+ {"ImageBrightness7", icons.ImageBrightness7},
+ {"ImageBrokenImage", icons.ImageBrokenImage},
+ {"ImageBrush", icons.ImageBrush},
+ {"ImageBurstMode", icons.ImageBurstMode},
+ {"ImageCamera", icons.ImageCamera},
+ {"ImageCameraAlt", icons.ImageCameraAlt},
+ {"ImageCameraFront", icons.ImageCameraFront},
+ {"ImageCameraRear", icons.ImageCameraRear},
+ {"ImageCameraRoll", icons.ImageCameraRoll},
+ {"ImageCenterFocusStrong", icons.ImageCenterFocusStrong},
+ {"ImageCenterFocusWeak", icons.ImageCenterFocusWeak},
+ {"ImageCollections", icons.ImageCollections},
+ {"ImageCollectionsBookmark", icons.ImageCollectionsBookmark},
+ {"ImageColorLens", icons.ImageColorLens},
+ {"ImageColorize", icons.ImageColorize},
+ {"ImageCompare", icons.ImageCompare},
+ {"ImageControlPoint", icons.ImageControlPoint},
+ {"ImageControlPointDuplicate", icons.ImageControlPointDuplicate},
+ {"ImageCrop", icons.ImageCrop},
+ {"ImageCrop169", icons.ImageCrop169},
+ {"ImageCrop32", icons.ImageCrop32},
+ {"ImageCrop54", icons.ImageCrop54},
+ {"ImageCrop75", icons.ImageCrop75},
+ {"ImageCropDIN", icons.ImageCropDIN},
+ {"ImageCropFree", icons.ImageCropFree},
+ {"ImageCropLandscape", icons.ImageCropLandscape},
+ {"ImageCropOriginal", icons.ImageCropOriginal},
+ {"ImageCropPortrait", icons.ImageCropPortrait},
+ {"ImageCropRotate", icons.ImageCropRotate},
+ {"ImageCropSquare", icons.ImageCropSquare},
+ {"ImageDehaze", icons.ImageDehaze},
+ {"ImageDetails", icons.ImageDetails},
+ {"ImageEdit", icons.ImageEdit},
+ {"ImageExposure", icons.ImageExposure},
+ {"ImageExposureNeg1", icons.ImageExposureNeg1},
+ {"ImageExposureNeg2", icons.ImageExposureNeg2},
+ {"ImageExposurePlus1", icons.ImageExposurePlus1},
+ {"ImageExposurePlus2", icons.ImageExposurePlus2},
+ {"ImageExposureZero", icons.ImageExposureZero},
+ {"ImageFilter", icons.ImageFilter},
+ {"ImageFilter1", icons.ImageFilter1},
+ {"ImageFilter2", icons.ImageFilter2},
+ {"ImageFilter3", icons.ImageFilter3},
+ {"ImageFilter4", icons.ImageFilter4},
+ {"ImageFilter5", icons.ImageFilter5},
+ {"ImageFilter6", icons.ImageFilter6},
+ {"ImageFilter7", icons.ImageFilter7},
+ {"ImageFilter8", icons.ImageFilter8},
+ {"ImageFilter9", icons.ImageFilter9},
+ {"ImageFilter9Plus", icons.ImageFilter9Plus},
+ {"ImageFilterBAndW", icons.ImageFilterBAndW},
+ {"ImageFilterCenterFocus", icons.ImageFilterCenterFocus},
+ {"ImageFilterDrama", icons.ImageFilterDrama},
+ {"ImageFilterFrames", icons.ImageFilterFrames},
+ {"ImageFilterHDR", icons.ImageFilterHDR},
+ {"ImageFilterNone", icons.ImageFilterNone},
+ {"ImageFilterTiltShift", icons.ImageFilterTiltShift},
+ {"ImageFilterVintage", icons.ImageFilterVintage},
+ {"ImageFlare", icons.ImageFlare},
+ {"ImageFlashAuto", icons.ImageFlashAuto},
+ {"ImageFlashOff", icons.ImageFlashOff},
+ {"ImageFlashOn", icons.ImageFlashOn},
+ {"ImageFlip", icons.ImageFlip},
+ {"ImageGradient", icons.ImageGradient},
+ {"ImageGrain", icons.ImageGrain},
+ {"ImageGridOff", icons.ImageGridOff},
+ {"ImageGridOn", icons.ImageGridOn},
+ {"ImageHDROff", icons.ImageHDROff},
+ {"ImageHDROn", icons.ImageHDROn},
+ {"ImageHDRStrong", icons.ImageHDRStrong},
+ {"ImageHDRWeak", icons.ImageHDRWeak},
+ {"ImageHealing", icons.ImageHealing},
+ {"ImageImage", icons.ImageImage},
+ {"ImageImageAspectRatio", icons.ImageImageAspectRatio},
+ {"ImageISO", icons.ImageISO},
+ {"ImageLandscape", icons.ImageLandscape},
+ {"ImageLeakAdd", icons.ImageLeakAdd},
+ {"ImageLeakRemove", icons.ImageLeakRemove},
+ {"ImageLens", icons.ImageLens},
+ {"ImageLinkedCamera", icons.ImageLinkedCamera},
+ {"ImageLooks", icons.ImageLooks},
+ {"ImageLooks3", icons.ImageLooks3},
+ {"ImageLooks4", icons.ImageLooks4},
+ {"ImageLooks5", icons.ImageLooks5},
+ {"ImageLooks6", icons.ImageLooks6},
+ {"ImageLooksOne", icons.ImageLooksOne},
+ {"ImageLooksTwo", icons.ImageLooksTwo},
+ {"ImageLoupe", icons.ImageLoupe},
+ {"ImageMonochromePhotos", icons.ImageMonochromePhotos},
+ {"ImageMovieCreation", icons.ImageMovieCreation},
+ {"ImageMovieFilter", icons.ImageMovieFilter},
+ {"ImageMusicNote", icons.ImageMusicNote},
+ {"ImageNature", icons.ImageNature},
+ {"ImageNaturePeople", icons.ImageNaturePeople},
+ {"ImageNavigateBefore", icons.ImageNavigateBefore},
+ {"ImageNavigateNext", icons.ImageNavigateNext},
+ {"ImagePalette", icons.ImagePalette},
+ {"ImagePanorama", icons.ImagePanorama},
+ {"ImagePanoramaFishEye", icons.ImagePanoramaFishEye},
+ {"ImagePanoramaHorizontal", icons.ImagePanoramaHorizontal},
+ {"ImagePanoramaVertical", icons.ImagePanoramaVertical},
+ {"ImagePanoramaWideAngle", icons.ImagePanoramaWideAngle},
+ {"ImagePhoto", icons.ImagePhoto},
+ {"ImagePhotoAlbum", icons.ImagePhotoAlbum},
+ {"ImagePhotoCamera", icons.ImagePhotoCamera},
+ {"ImagePhotoFilter", icons.ImagePhotoFilter},
+ {"ImagePhotoLibrary", icons.ImagePhotoLibrary},
+ {"ImagePhotoSizeSelectActual", icons.ImagePhotoSizeSelectActual},
+ {"ImagePhotoSizeSelectLarge", icons.ImagePhotoSizeSelectLarge},
+ {"ImagePhotoSizeSelectSmall", icons.ImagePhotoSizeSelectSmall},
+ {"ImagePictureAsPDF", icons.ImagePictureAsPDF},
+ {"ImagePortrait", icons.ImagePortrait},
+ {"ImageRemoveRedEye", icons.ImageRemoveRedEye},
+ {"ImageRotate90DegreesCCW", icons.ImageRotate90DegreesCCW},
+ {"ImageRotateLeft", icons.ImageRotateLeft},
+ {"ImageRotateRight", icons.ImageRotateRight},
+ {"ImageSlideshow", icons.ImageSlideshow},
+ {"ImageStraighten", icons.ImageStraighten},
+ {"ImageStyle", icons.ImageStyle},
+ {"ImageSwitchCamera", icons.ImageSwitchCamera},
+ {"ImageSwitchVideo", icons.ImageSwitchVideo},
+ {"ImageTagFaces", icons.ImageTagFaces},
+ {"ImageTexture", icons.ImageTexture},
+ {"ImageTimeLapse", icons.ImageTimeLapse},
+ {"ImageTimer", icons.ImageTimer},
+ {"ImageTimer10", icons.ImageTimer10},
+ {"ImageTimer3", icons.ImageTimer3},
+ {"ImageTimerOff", icons.ImageTimerOff},
+ {"ImageTonality", icons.ImageTonality},
+ {"ImageTransform", icons.ImageTransform},
+ {"ImageTune", icons.ImageTune},
+ {"ImageViewComfy", icons.ImageViewComfy},
+ {"ImageViewCompact", icons.ImageViewCompact},
+ {"ImageVignette", icons.ImageVignette},
+ {"ImageWBAuto", icons.ImageWBAuto},
+ {"ImageWBCloudy", icons.ImageWBCloudy},
+ {"ImageWBIncandescent", icons.ImageWBIncandescent},
+ {"ImageWBIridescent", icons.ImageWBIridescent},
+ {"ImageWBSunny", icons.ImageWBSunny},
+ {"MapsAddLocation", icons.MapsAddLocation},
+ {"MapsBeenhere", icons.MapsBeenhere},
+ {"MapsDirections", icons.MapsDirections},
+ {"MapsDirectionsBike", icons.MapsDirectionsBike},
+ {"MapsDirectionsBoat", icons.MapsDirectionsBoat},
+ {"MapsDirectionsBus", icons.MapsDirectionsBus},
+ {"MapsDirectionsCar", icons.MapsDirectionsCar},
+ {"MapsDirectionsRailway", icons.MapsDirectionsRailway},
+ {"MapsDirectionsRun", icons.MapsDirectionsRun},
+ {"MapsDirectionsSubway", icons.MapsDirectionsSubway},
+ {"MapsDirectionsTransit", icons.MapsDirectionsTransit},
+ {"MapsDirectionsWalk", icons.MapsDirectionsWalk},
+ {"MapsEditLocation", icons.MapsEditLocation},
+ {"MapsEVStation", icons.MapsEVStation},
+ {"MapsFlight", icons.MapsFlight},
+ {"MapsHotel", icons.MapsHotel},
+ {"MapsLayers", icons.MapsLayers},
+ {"MapsLayersClear", icons.MapsLayersClear},
+ {"MapsLocalActivity", icons.MapsLocalActivity},
+ {"MapsLocalAirport", icons.MapsLocalAirport},
+ {"MapsLocalATM", icons.MapsLocalATM},
+ {"MapsLocalBar", icons.MapsLocalBar},
+ {"MapsLocalCafe", icons.MapsLocalCafe},
+ {"MapsLocalCarWash", icons.MapsLocalCarWash},
+ {"MapsLocalConvenienceStore", icons.MapsLocalConvenienceStore},
+ {"MapsLocalDining", icons.MapsLocalDining},
+ {"MapsLocalDrink", icons.MapsLocalDrink},
+ {"MapsLocalFlorist", icons.MapsLocalFlorist},
+ {"MapsLocalGasStation", icons.MapsLocalGasStation},
+ {"MapsLocalGroceryStore", icons.MapsLocalGroceryStore},
+ {"MapsLocalHospital", icons.MapsLocalHospital},
+ {"MapsLocalHotel", icons.MapsLocalHotel},
+ {"MapsLocalLaundryService", icons.MapsLocalLaundryService},
+ {"MapsLocalLibrary", icons.MapsLocalLibrary},
+ {"MapsLocalMall", icons.MapsLocalMall},
+ {"MapsLocalMovies", icons.MapsLocalMovies},
+ {"MapsLocalOffer", icons.MapsLocalOffer},
+ {"MapsLocalParking", icons.MapsLocalParking},
+ {"MapsLocalPharmacy", icons.MapsLocalPharmacy},
+ {"MapsLocalPhone", icons.MapsLocalPhone},
+ {"MapsLocalPizza", icons.MapsLocalPizza},
+ {"MapsLocalPlay", icons.MapsLocalPlay},
+ {"MapsLocalPostOffice", icons.MapsLocalPostOffice},
+ {"MapsLocalPrintshop", icons.MapsLocalPrintshop},
+ {"MapsLocalSee", icons.MapsLocalSee},
+ {"MapsLocalShipping", icons.MapsLocalShipping},
+ {"MapsLocalTaxi", icons.MapsLocalTaxi},
+ {"MapsMap", icons.MapsMap},
+ {"MapsMyLocation", icons.MapsMyLocation},
+ {"MapsNavigation", icons.MapsNavigation},
+ {"MapsNearMe", icons.MapsNearMe},
+ {"MapsPersonPin", icons.MapsPersonPin},
+ {"MapsPersonPinCircle", icons.MapsPersonPinCircle},
+ {"MapsPinDrop", icons.MapsPinDrop},
+ {"MapsPlace", icons.MapsPlace},
+ {"MapsRateReview", icons.MapsRateReview},
+ {"MapsRestaurant", icons.MapsRestaurant},
+ {"MapsRestaurantMenu", icons.MapsRestaurantMenu},
+ {"MapsSatellite", icons.MapsSatellite},
+ {"MapsStoreMallDirectory", icons.MapsStoreMallDirectory},
+ {"MapsStreetView", icons.MapsStreetView},
+ {"MapsSubway", icons.MapsSubway},
+ {"MapsTerrain", icons.MapsTerrain},
+ {"MapsTraffic", icons.MapsTraffic},
+ {"MapsTrain", icons.MapsTrain},
+ {"MapsTram", icons.MapsTram},
+ {"MapsTransferWithinAStation", icons.MapsTransferWithinAStation},
+ {"MapsZoomOutMap", icons.MapsZoomOutMap},
+ {"NavigationApps", icons.NavigationApps},
+ {"NavigationArrowBack", icons.NavigationArrowBack},
+ {"NavigationArrowDownward", icons.NavigationArrowDownward},
+ {"NavigationArrowDropDown", icons.NavigationArrowDropDown},
+ {"NavigationArrowDropDownCircle", icons.NavigationArrowDropDownCircle},
+ {"NavigationArrowDropUp", icons.NavigationArrowDropUp},
+ {"NavigationArrowForward", icons.NavigationArrowForward},
+ {"NavigationArrowUpward", icons.NavigationArrowUpward},
+ {"NavigationCancel", icons.NavigationCancel},
+ {"NavigationCheck", icons.NavigationCheck},
+ {"NavigationChevronLeft", icons.NavigationChevronLeft},
+ {"NavigationChevronRight", icons.NavigationChevronRight},
+ {"NavigationClose", icons.NavigationClose},
+ {"NavigationExpandLess", icons.NavigationExpandLess},
+ {"NavigationExpandMore", icons.NavigationExpandMore},
+ {"NavigationFirstPage", icons.NavigationFirstPage},
+ {"NavigationFullscreen", icons.NavigationFullscreen},
+ {"NavigationFullscreenExit", icons.NavigationFullscreenExit},
+ {"NavigationLastPage", icons.NavigationLastPage},
+ {"NavigationMenu", icons.NavigationMenu},
+ {"NavigationMoreHoriz", icons.NavigationMoreHoriz},
+ {"NavigationMoreVert", icons.NavigationMoreVert},
+ {"NavigationRefresh", icons.NavigationRefresh},
+ {"NavigationSubdirectoryArrowLeft", icons.NavigationSubdirectoryArrowLeft},
+ {"NavigationSubdirectoryArrowRight", icons.NavigationSubdirectoryArrowRight},
+ {"NavigationUnfoldLess", icons.NavigationUnfoldLess},
+ {"NavigationUnfoldMore", icons.NavigationUnfoldMore},
+ {"NotificationADB", icons.NotificationADB},
+ {"NotificationAirlineSeatFlat", icons.NotificationAirlineSeatFlat},
+ {"NotificationAirlineSeatFlatAngled", icons.NotificationAirlineSeatFlatAngled},
+ {"NotificationAirlineSeatIndividualSuite", icons.NotificationAirlineSeatIndividualSuite},
+ {"NotificationAirlineSeatLegroomExtra", icons.NotificationAirlineSeatLegroomExtra},
+ {"NotificationAirlineSeatLegroomNormal", icons.NotificationAirlineSeatLegroomNormal},
+ {"NotificationAirlineSeatLegroomReduced", icons.NotificationAirlineSeatLegroomReduced},
+ {"NotificationAirlineSeatReclineExtra", icons.NotificationAirlineSeatReclineExtra},
+ {"NotificationAirlineSeatReclineNormal", icons.NotificationAirlineSeatReclineNormal},
+ {"NotificationBluetoothAudio", icons.NotificationBluetoothAudio},
+ {"NotificationConfirmationNumber", icons.NotificationConfirmationNumber},
+ {"NotificationDiscFull", icons.NotificationDiscFull},
+ {"NotificationDoNotDisturb", icons.NotificationDoNotDisturb},
+ {"NotificationDoNotDisturbAlt", icons.NotificationDoNotDisturbAlt},
+ {"NotificationDoNotDisturbOff", icons.NotificationDoNotDisturbOff},
+ {"NotificationDoNotDisturbOn", icons.NotificationDoNotDisturbOn},
+ {"NotificationDriveETA", icons.NotificationDriveETA},
+ {"NotificationEnhancedEncryption", icons.NotificationEnhancedEncryption},
+ {"NotificationEventAvailable", icons.NotificationEventAvailable},
+ {"NotificationEventBusy", icons.NotificationEventBusy},
+ {"NotificationEventNote", icons.NotificationEventNote},
+ {"NotificationFolderSpecial", icons.NotificationFolderSpecial},
+ {"NotificationLiveTV", icons.NotificationLiveTV},
+ {"NotificationMMS", icons.NotificationMMS},
+ {"NotificationMore", icons.NotificationMore},
+ {"NotificationNetworkCheck", icons.NotificationNetworkCheck},
+ {"NotificationNetworkLocked", icons.NotificationNetworkLocked},
+ {"NotificationNoEncryption", icons.NotificationNoEncryption},
+ {"NotificationOnDemandVideo", icons.NotificationOnDemandVideo},
+ {"NotificationPersonalVideo", icons.NotificationPersonalVideo},
+ {"NotificationPhoneBluetoothSpeaker", icons.NotificationPhoneBluetoothSpeaker},
+ {"NotificationPhoneForwarded", icons.NotificationPhoneForwarded},
+ {"NotificationPhoneInTalk", icons.NotificationPhoneInTalk},
+ {"NotificationPhoneLocked", icons.NotificationPhoneLocked},
+ {"NotificationPhoneMissed", icons.NotificationPhoneMissed},
+ {"NotificationPhonePaused", icons.NotificationPhonePaused},
+ {"NotificationPower", icons.NotificationPower},
+ {"NotificationPriorityHigh", icons.NotificationPriorityHigh},
+ {"NotificationRVHookup", icons.NotificationRVHookup},
+ {"NotificationSDCard", icons.NotificationSDCard},
+ {"NotificationSIMCardAlert", icons.NotificationSIMCardAlert},
+ {"NotificationSMS", icons.NotificationSMS},
+ {"NotificationSMSFailed", icons.NotificationSMSFailed},
+ {"NotificationSync", icons.NotificationSync},
+ {"NotificationSyncDisabled", icons.NotificationSyncDisabled},
+ {"NotificationSyncProblem", icons.NotificationSyncProblem},
+ {"NotificationSystemUpdate", icons.NotificationSystemUpdate},
+ {"NotificationTapAndPlay", icons.NotificationTapAndPlay},
+ {"NotificationTimeToLeave", icons.NotificationTimeToLeave},
+ {"NotificationVibration", icons.NotificationVibration},
+ {"NotificationVoiceChat", icons.NotificationVoiceChat},
+ {"NotificationVPNLock", icons.NotificationVPNLock},
+ {"NotificationWC", icons.NotificationWC},
+ {"NotificationWiFi", icons.NotificationWiFi},
+ {"PlacesACUnit", icons.PlacesACUnit},
+ {"PlacesAirportShuttle", icons.PlacesAirportShuttle},
+ {"PlacesAllInclusive", icons.PlacesAllInclusive},
+ {"PlacesBeachAccess", icons.PlacesBeachAccess},
+ {"PlacesBusinessCenter", icons.PlacesBusinessCenter},
+ {"PlacesCasino", icons.PlacesCasino},
+ {"PlacesChildCare", icons.PlacesChildCare},
+ {"PlacesChildFriendly", icons.PlacesChildFriendly},
+ {"PlacesFitnessCenter", icons.PlacesFitnessCenter},
+ {"PlacesFreeBreakfast", icons.PlacesFreeBreakfast},
+ {"PlacesGolfCourse", icons.PlacesGolfCourse},
+ {"PlacesHotTub", icons.PlacesHotTub},
+ {"PlacesKitchen", icons.PlacesKitchen},
+ {"PlacesPool", icons.PlacesPool},
+ {"PlacesRoomService", icons.PlacesRoomService},
+ {"PlacesRVHookup", icons.PlacesRVHookup},
+ {"PlacesSmokeFree", icons.PlacesSmokeFree},
+ {"PlacesSmokingRooms", icons.PlacesSmokingRooms},
+ {"PlacesSpa", icons.PlacesSpa},
+ {"SocialCake", icons.SocialCake},
+ {"SocialDomain", icons.SocialDomain},
+ {"SocialGroup", icons.SocialGroup},
+ {"SocialGroupAdd", icons.SocialGroupAdd},
+ {"SocialLocationCity", icons.SocialLocationCity},
+ {"SocialMood", icons.SocialMood},
+ {"SocialMoodBad", icons.SocialMoodBad},
+ {"SocialNotifications", icons.SocialNotifications},
+ {"SocialNotificationsActive", icons.SocialNotificationsActive},
+ {"SocialNotificationsNone", icons.SocialNotificationsNone},
+ {"SocialNotificationsOff", icons.SocialNotificationsOff},
+ {"SocialNotificationsPaused", icons.SocialNotificationsPaused},
+ {"SocialPages", icons.SocialPages},
+ {"SocialPartyMode", icons.SocialPartyMode},
+ {"SocialPeople", icons.SocialPeople},
+ {"SocialPeopleOutline", icons.SocialPeopleOutline},
+ {"SocialPerson", icons.SocialPerson},
+ {"SocialPersonAdd", icons.SocialPersonAdd},
+ {"SocialPersonOutline", icons.SocialPersonOutline},
+ {"SocialPlusOne", icons.SocialPlusOne},
+ {"SocialPoll", icons.SocialPoll},
+ {"SocialPublic", icons.SocialPublic},
+ {"SocialSchool", icons.SocialSchool},
+ {"SocialSentimentDissatisfied", icons.SocialSentimentDissatisfied},
+ {"SocialSentimentNeutral", icons.SocialSentimentNeutral},
+ {"SocialSentimentSatisfied", icons.SocialSentimentSatisfied},
+ {"SocialSentimentVeryDissatisfied", icons.SocialSentimentVeryDissatisfied},
+ {"SocialSentimentVerySatisfied", icons.SocialSentimentVerySatisfied},
+ {"SocialShare", icons.SocialShare},
+ {"SocialWhatsHot", icons.SocialWhatsHot},
+ {"ToggleCheckBox", icons.ToggleCheckBox},
+ {"ToggleCheckBoxOutlineBlank", icons.ToggleCheckBoxOutlineBlank},
+ {"ToggleIndeterminateCheckBox", icons.ToggleIndeterminateCheckBox},
+ {"ToggleRadioButtonChecked", icons.ToggleRadioButtonChecked},
+ {"ToggleRadioButtonUnchecked", icons.ToggleRadioButtonUnchecked},
+ {"ToggleStar", icons.ToggleStar},
+ {"ToggleStarBorder", icons.ToggleStarBorder},
+ {"ToggleStarHalf", icons.ToggleStarHalf},
+}
diff --git a/shiny/example/imageview/main.go b/shiny/example/imageview/main.go
new file mode 100644
index 0000000..0aa91fa
--- /dev/null
+++ b/shiny/example/imageview/main.go
@@ -0,0 +1,71 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Imageview is a basic image viewer. Supported image formats include BMP, GIF,
+// JPEG, PNG, TIFF and WEBP.
+package main
+
+import (
+ "fmt"
+ "image"
+ "log"
+ "os"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/widget"
+
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
+
+ _ "golang.org/x/image/bmp"
+ _ "golang.org/x/image/tiff"
+ _ "golang.org/x/image/webp"
+)
+
+// TODO: scrolling, such as when images are larger than the window.
+
+func decode(filename string) (image.Image, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ m, _, err := image.Decode(f)
+ if err != nil {
+ return nil, fmt.Errorf("could not decode %s: %v", filename, err)
+ }
+ return m, nil
+}
+
+func main() {
+ log.SetFlags(0)
+ driver.Main(func(s screen.Screen) {
+ if len(os.Args) < 2 {
+ log.Fatal("no image file specified")
+ }
+ // TODO: view multiple images.
+ src, err := decode(os.Args[1])
+ if err != nil {
+ log.Fatal(err)
+ }
+ w := widget.NewSheet(widget.NewImage(src, src.Bounds()))
+ if err := widget.RunWindow(s, w, &widget.RunWindowOptions{
+ NewWindowOptions: screen.NewWindowOptions{
+ Title: "ImageView Shiny Example",
+ },
+ }); err != nil {
+ log.Fatal(err)
+ }
+ })
+}
diff --git a/shiny/example/layout/main.go b/shiny/example/layout/main.go
new file mode 100644
index 0000000..a6f4532
--- /dev/null
+++ b/shiny/example/layout/main.go
@@ -0,0 +1,104 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// layout is an example of a laying out a widget node tree. Real GUI programs
+// won't need to do this explicitly, as the shiny/widget package will
+// coordinate with the shiny/screen package to call Measure, Layout and Paint
+// as necessary, and will re-layout widgets when windows are re-sized. This
+// program merely demonstrates how a widget node tree can be rendered onto a
+// statically sized RGBA image, for visual verification of widget code without
+// having to bring up and manually inspect an interactive GUI window.
+package main
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "image/png"
+ "log"
+ "os"
+
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+var px = unit.Pixels
+
+func colorPatch(c color.Color, w, h unit.Value) *widget.Sizer {
+ return widget.NewSizer(w, h, widget.NewUniform(theme.StaticColor(c), nil))
+}
+
+func main() {
+ t := theme.Default
+
+ // Make the widget node tree.
+ hf := widget.NewFlow(widget.AxisHorizontal,
+ widget.NewLabel("Cyan:"),
+ widget.WithLayoutData(
+ colorPatch(color.RGBA{0x00, 0x7f, 0x7f, 0xff}, px(0), px(20)),
+ widget.FlowLayoutData{AlongWeight: 1, ExpandAlong: true},
+ ),
+ widget.NewLabel("Magenta:"),
+ widget.WithLayoutData(
+ colorPatch(color.RGBA{0x7f, 0x00, 0x7f, 0xff}, px(0), px(30)),
+ widget.FlowLayoutData{AlongWeight: 2, ExpandAlong: true},
+ ),
+ widget.NewLabel("Yellow:"),
+ widget.WithLayoutData(
+ colorPatch(color.RGBA{0x7f, 0x7f, 0x00, 0xff}, px(0), px(40)),
+ widget.FlowLayoutData{AlongWeight: 3, ExpandAlong: true},
+ ),
+ )
+
+ vf := widget.NewFlow(widget.AxisVertical,
+ colorPatch(color.RGBA{0xff, 0x00, 0x00, 0xff}, px(80), px(40)),
+ colorPatch(color.RGBA{0x00, 0xff, 0x00, 0xff}, px(50), px(50)),
+ colorPatch(color.RGBA{0x00, 0x00, 0xff, 0xff}, px(20), px(60)),
+ widget.WithLayoutData(
+ hf,
+ widget.FlowLayoutData{ExpandAcross: true},
+ ),
+ widget.NewLabel(fmt.Sprintf(
+ "The black rectangle is 1.5 inches x 1 inch when viewed at %v DPI.", t.GetDPI())),
+ widget.NewPadder(widget.AxisBoth, unit.Pixels(8),
+ colorPatch(color.Black, unit.Inches(1.5), unit.Inches(1)),
+ ),
+ )
+
+ // Make the RGBA image.
+ const width, height = 640, 480
+ rgba := image.NewRGBA(image.Rect(0, 0, width, height))
+ draw.Draw(rgba, rgba.Bounds(), t.GetPalette().Neutral(), image.Point{}, draw.Src)
+
+ // Measure, layout and paint.
+ vf.Measure(t, width, height)
+ vf.Rect = rgba.Bounds()
+ vf.Layout(t)
+ vf.PaintBase(&node.PaintBaseContext{
+ Theme: t,
+ Dst: rgba,
+ }, image.Point{})
+
+ // Encode to PNG.
+ out, err := os.Create("out.png")
+ if err != nil {
+ log.Fatalf("os.Create: %v", err)
+ }
+ defer out.Close()
+ if err := png.Encode(out, rgba); err != nil {
+ log.Fatalf("png.Encode: %v", err)
+ }
+ fmt.Println("Wrote out.png OK.")
+}
diff --git a/shiny/example/textedit/main.go b/shiny/example/textedit/main.go
new file mode 100644
index 0000000..70ac599
--- /dev/null
+++ b/shiny/example/textedit/main.go
@@ -0,0 +1,150 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Textedit is a basic text editor.
+package main
+
+// TODO: cursors + editing (not just viewing) text, key + mouse events,
+// scrolling, load/save, clipboard.
+
+import (
+ "log"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+func stretch(n node.Node, alongWeight int) node.Node {
+ return widget.WithLayoutData(n, widget.FlowLayoutData{
+ AlongWeight: alongWeight,
+ ExpandAlong: true,
+ ShrinkAlong: true,
+ ExpandAcross: true,
+ ShrinkAcross: true,
+ })
+}
+
+func main() {
+ log.SetFlags(0)
+ driver.Main(func(s screen.Screen) {
+ header := widget.NewUniform(theme.Neutral,
+ widget.NewPadder(widget.AxisBoth, unit.Ems(0.5),
+ widget.NewFlow(widget.AxisHorizontal,
+ widget.NewLabel("TODO: status"),
+ stretch(widget.NewSpace(), 1),
+ widget.NewLabel("TODO: Menu"),
+ ),
+ ),
+ )
+ divider := widget.NewSizer(unit.Value{}, unit.DIPs(2),
+ widget.NewUniform(theme.Foreground, nil),
+ )
+ body := widget.NewText(prideAndPrejudice)
+
+ w := widget.NewFlow(widget.AxisVertical,
+ stretch(widget.NewSheet(header), 0),
+ stretch(widget.NewSheet(divider), 0),
+ // TODO: make the body's sheet scrollable.
+ stretch(widget.NewSheet(body), 1),
+ )
+
+ if err := widget.RunWindow(s, w, &widget.RunWindowOptions{
+ NewWindowOptions: screen.NewWindowOptions{
+ Title: "TextEdit Shiny Example",
+ },
+ }); err != nil {
+ log.Fatal(err)
+ }
+ })
+}
+
+// prideAndPrejudice comes from http://www.gutenberg.org/cache/epub/1342/pg1342.txt
+const prideAndPrejudice = `PRIDE AND PREJUDICE
+
+By Jane Austen
+
+
+
+Chapter 1
+
+
+It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.
+
+However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters.
+
+"My dear Mr. Bennet," said his lady to him one day, "have you heard that Netherfield Park is let at last?"
+
+Mr. Bennet replied that he had not.
+
+"But it is," returned she; "for Mrs. Long has just been here, and she told me all about it."
+
+Mr. Bennet made no answer.
+
+"Do you not want to know who has taken it?" cried his wife impatiently.
+
+"_You_ want to tell me, and I have no objection to hearing it."
+
+This was invitation enough.
+
+"Why, my dear, you must know, Mrs. Long says that Netherfield is taken by a young man of large fortune from the north of England; that he came down on Monday in a chaise and four to see the place, and was so much delighted with it, that he agreed with Mr. Morris immediately; that he is to take possession before Michaelmas, and some of his servants are to be in the house by the end of next week."
+
+"What is his name?"
+
+"Bingley."
+
+"Is he married or single?"
+
+"Oh! Single, my dear, to be sure! A single man of large fortune; four or five thousand a year. What a fine thing for our girls!"
+
+"How so? How can it affect them?"
+
+"My dear Mr. Bennet," replied his wife, "how can you be so tiresome! You must know that I am thinking of his marrying one of them."
+
+"Is that his design in settling here?"
+
+"Design! Nonsense, how can you talk so! But it is very likely that he _may_ fall in love with one of them, and therefore you must visit him as soon as he comes."
+
+"I see no occasion for that. You and the girls may go, or you may send them by themselves, which perhaps will be still better, for as you are as handsome as any of them, Mr. Bingley may like you the best of the party."
+
+"My dear, you flatter me. I certainly _have_ had my share of beauty, but I do not pretend to be anything extraordinary now. When a woman has five grown-up daughters, she ought to give over thinking of her own beauty."
+
+"In such cases, a woman has not often much beauty to think of."
+
+"But, my dear, you must indeed go and see Mr. Bingley when he comes into the neighbourhood."
+
+"It is more than I engage for, I assure you."
+
+"But consider your daughters. Only think what an establishment it would be for one of them. Sir William and Lady Lucas are determined to go, merely on that account, for in general, you know, they visit no newcomers. Indeed you must go, for it will be impossible for _us_ to visit him if you do not."
+
+"You are over-scrupulous, surely. I dare say Mr. Bingley will be very glad to see you; and I will send a few lines by you to assure him of my hearty consent to his marrying whichever he chooses of the girls; though I must throw in a good word for my little Lizzy."
+
+"I desire you will do no such thing. Lizzy is not a bit better than the others; and I am sure she is not half so handsome as Jane, nor half so good-humoured as Lydia. But you are always giving _her_ the preference."
+
+"They have none of them much to recommend them," replied he; "they are all silly and ignorant like other girls; but Lizzy has something more of quickness than her sisters."
+
+"Mr. Bennet, how _can_ you abuse your own children in such a way? You take delight in vexing me. You have no compassion for my poor nerves."
+
+"You mistake me, my dear. I have a high respect for your nerves. They are my old friends. I have heard you mention them with consideration these last twenty years at least."
+
+"Ah, you do not know what I suffer."
+
+"But I hope you will get over it, and live to see many young men of four thousand a year come into the neighbourhood."
+
+"It will be no use to us, if twenty such should come, since you will not visit them."
+
+"Depend upon it, my dear, that when there are twenty, I will visit them all."
+
+Mr. Bennet was so odd a mixture of quick parts, sarcastic humour, reserve, and caprice, that the experience of three-and-twenty years had been insufficient to make his wife understand his character. _Her_ mind was less difficult to develop. She was a woman of mean understanding, little information, and uncertain temper. When she was discontented, she fancied herself nervous. The business of her life was to get her daughters married; its solace was visiting and news.`
diff --git a/shiny/example/tile/main.go b/shiny/example/tile/main.go
new file mode 100644
index 0000000..c99e247
--- /dev/null
+++ b/shiny/example/tile/main.go
@@ -0,0 +1,237 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Tile demonstrates tiling a screen with textures.
+package main
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "log"
+ "sync"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/image/font"
+ "golang.org/x/image/font/inconsolata"
+ "golang.org/x/image/math/fixed"
+ "golang.org/x/mobile/event/key"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+func main() {
+ driver.Main(func(s screen.Screen) {
+ w, err := s.NewWindow(&screen.NewWindowOptions{
+ Title: "Tile Shiny Example",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer w.Release()
+
+ var (
+ pool = &tilePool{
+ screen: s,
+ drawRGBA: drawRGBA,
+ m: map[image.Point]*tilePoolEntry{},
+ }
+ dragging bool
+ paintPending bool
+ drag image.Point
+ origin image.Point
+ sz size.Event
+ )
+ for {
+ switch e := w.NextEvent().(type) {
+ case lifecycle.Event:
+ if e.To == lifecycle.StageDead {
+ return
+ }
+
+ case key.Event:
+ if e.Code == key.CodeEscape {
+ return
+ }
+
+ case mouse.Event:
+ p := image.Point{X: int(e.X), Y: int(e.Y)}
+ if e.Button == mouse.ButtonLeft && e.Direction != mouse.DirNone {
+ dragging = e.Direction == mouse.DirPress
+ drag = p
+ }
+ if !dragging {
+ break
+ }
+ origin = origin.Sub(p.Sub(drag))
+ drag = p
+ if origin.X < 0 {
+ origin.X = 0
+ }
+ if origin.Y < 0 {
+ origin.Y = 0
+ }
+ if !paintPending {
+ paintPending = true
+ w.Send(paint.Event{})
+ }
+
+ case paint.Event:
+ generation++
+ var wg sync.WaitGroup
+ for y := -(origin.Y & 0xff); y < sz.HeightPx; y += 256 {
+ for x := -(origin.X & 0xff); x < sz.WidthPx; x += 256 {
+ wg.Add(1)
+ go drawTile(&wg, w, pool, origin, x, y)
+ }
+ }
+ wg.Wait()
+ w.Publish()
+ paintPending = false
+ pool.releaseUnused()
+
+ case size.Event:
+ sz = e
+
+ case error:
+ log.Print(e)
+ }
+ }
+ })
+}
+
+func drawTile(wg *sync.WaitGroup, w screen.Window, pool *tilePool, origin image.Point, x, y int) {
+ defer wg.Done()
+ tp := image.Point{
+ (x + origin.X) >> 8,
+ (y + origin.Y) >> 8,
+ }
+ tex, err := pool.get(tp)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ w.Copy(image.Point{x, y}, tex, tileBounds, screen.Src, nil)
+}
+
+func drawRGBA(m *image.RGBA, tp image.Point) {
+ draw.Draw(m, m.Bounds(), image.White, image.Point{}, draw.Src)
+ for _, p := range crossPoints {
+ m.SetRGBA(p.X, p.Y, crossColor)
+ }
+ d := font.Drawer{
+ Dst: m,
+ Src: image.Black,
+ Face: inconsolata.Regular8x16,
+ Dot: fixed.Point26_6{
+ Y: inconsolata.Regular8x16.Metrics().Ascent,
+ },
+ }
+ d.DrawString(fmt.Sprint(tp))
+}
+
+var (
+ crossColor = color.RGBA{0x7f, 0x00, 0x00, 0xff}
+ crossPoints = []image.Point{
+ {0x00, 0xfe},
+ {0x00, 0xff},
+ {0xfe, 0x00},
+ {0xff, 0x00},
+ {0x00, 0x00},
+ {0x01, 0x00},
+ {0x02, 0x00},
+ {0x00, 0x01},
+ {0x00, 0x02},
+
+ {0x80, 0x7f},
+ {0x7f, 0x80},
+ {0x80, 0x80},
+ {0x81, 0x80},
+ {0x80, 0x81},
+
+ {0x80, 0x00},
+
+ {0x00, 0x80},
+ }
+
+ generation int
+
+ tileSize = image.Point{256, 256}
+ tileBounds = image.Rectangle{Max: tileSize}
+)
+
+type tilePoolEntry struct {
+ tex screen.Texture
+ gen int
+}
+
+type tilePool struct {
+ screen screen.Screen
+ drawRGBA func(*image.RGBA, image.Point)
+
+ mu sync.Mutex
+ m map[image.Point]*tilePoolEntry
+}
+
+func (p *tilePool) get(tp image.Point) (screen.Texture, error) {
+ p.mu.Lock()
+ v, ok := p.m[tp]
+ if v != nil {
+ v.gen = generation
+ }
+ p.mu.Unlock()
+
+ if ok {
+ return v.tex, nil
+ }
+ tex, err := p.screen.NewTexture(tileSize)
+ if err != nil {
+ return nil, err
+ }
+ buf, err := p.screen.NewBuffer(tileSize)
+ if err != nil {
+ tex.Release()
+ return nil, err
+ }
+ p.drawRGBA(buf.RGBA(), tp)
+ tex.Upload(image.Point{}, buf, tileBounds)
+ buf.Release()
+
+ p.mu.Lock()
+ p.m[tp] = &tilePoolEntry{
+ tex: tex,
+ gen: generation,
+ }
+ n := len(p.m)
+ p.mu.Unlock()
+
+ fmt.Printf("%4d textures; created %v\n", n, tp)
+ return tex, nil
+}
+
+func (p *tilePool) releaseUnused() {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ for tp, v := range p.m {
+ if v.gen == generation {
+ continue
+ }
+ v.tex.Release()
+ delete(p.m, tp)
+ fmt.Printf("%4d textures; released %v\n", len(p.m), tp)
+ }
+}
diff --git a/shiny/example/widgetgallery/main.go b/shiny/example/widgetgallery/main.go
new file mode 100644
index 0000000..bbff7b6
--- /dev/null
+++ b/shiny/example/widgetgallery/main.go
@@ -0,0 +1,84 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build example
+// +build example
+
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it or "go install
+// -tags=example" to install it.
+
+// Widgetgallery exhibits the shiny/widget package's widget set.
+package main
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "log"
+
+ "golang.org/x/exp/shiny/driver"
+ "golang.org/x/exp/shiny/gesture"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/node"
+)
+
+var uniforms = [...]*image.Uniform{
+ image.NewUniform(color.RGBA{0xbf, 0x00, 0x00, 0xff}),
+ image.NewUniform(color.RGBA{0x9f, 0x9f, 0x00, 0xff}),
+ image.NewUniform(color.RGBA{0x00, 0xbf, 0x00, 0xff}),
+ image.NewUniform(color.RGBA{0x00, 0x9f, 0x9f, 0xff}),
+ image.NewUniform(color.RGBA{0x00, 0x00, 0xbf, 0xff}),
+ image.NewUniform(color.RGBA{0x9f, 0x00, 0x9f, 0xff}),
+}
+
+// custom is a custom widget.
+type custom struct {
+ node.LeafEmbed
+ index int
+}
+
+func newCustom() *custom {
+ w := &custom{}
+ w.Wrapper = w
+ return w
+}
+
+func (w *custom) OnInputEvent(e interface{}, origin image.Point) node.EventHandled {
+ switch e := e.(type) {
+ case gesture.Event:
+ if e.Type != gesture.TypeTap {
+ break
+ }
+ w.index++
+ if w.index == len(uniforms) {
+ w.index = 0
+ }
+ w.Mark(node.MarkNeedsPaintBase)
+ }
+ return node.Handled
+}
+
+func (w *custom) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ draw.Draw(ctx.Dst, w.Rect.Add(origin), uniforms[w.index], image.Point{}, draw.Src)
+ return nil
+}
+
+func main() {
+ log.SetFlags(0)
+ driver.Main(func(s screen.Screen) {
+ // TODO: create a bunch of standard widgets: buttons, labels, etc.
+ w := widget.NewSheet(newCustom())
+ if err := widget.RunWindow(s, w, &widget.RunWindowOptions{
+ NewWindowOptions: screen.NewWindowOptions{
+ Title: "WidgetGallery Shiny Example",
+ },
+ }); err != nil {
+ log.Fatal(err)
+ }
+ })
+}
diff --git a/shiny/gesture/gesture.go b/shiny/gesture/gesture.go
new file mode 100644
index 0000000..8078615
--- /dev/null
+++ b/shiny/gesture/gesture.go
@@ -0,0 +1,326 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package gesture provides gesture events such as long presses and drags.
+// These are higher level than underlying mouse and touch events.
+package gesture
+
+import (
+ "fmt"
+ "time"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/mobile/event/mouse"
+)
+
+// TODO: handle touch events, not just mouse events.
+//
+// TODO: multi-button / multi-touch gestures such as pinch, rotate and tilt?
+
+const (
+ // TODO: use a resolution-independent unit such as DIPs or Millimetres?
+ dragThreshold = 10 // Pixels.
+
+ doublePressThreshold = 300 * time.Millisecond
+ longPressThreshold = 500 * time.Millisecond
+)
+
+// Type describes the type of a touch event.
+type Type uint8
+
+const (
+ // TypeStart and TypeEnd are the start and end of a gesture. A gesture
+ // spans multiple events.
+ TypeStart Type = 0
+ TypeEnd Type = 1
+
+ // TypeIsXxx is when the gesture is recognized as a long press, double
+ // press or drag. For example, a mouse button press won't generate a
+ // TypeIsLongPress immediately, but if a threshold duration passes without
+ // the corresponding mouse button release, a TypeIsLongPress event is sent.
+ //
+ // Once a TypeIsXxx event is sent, the corresponding Event.Xxx bool field
+ // is set for this and subsequent events. For example, a TypeTap event by
+ // itself doesn't say whether or not it is a single tap or the first tap of
+ // a double tap. If the app needs to distinguish these two sorts of taps,
+ // it can wait until a TypeEnd or TypeIsDoublePress event is seen. If a
+ // TypeEnd is seen before TypeIsDoublePress, or equivalently, if the
+ // TypeEnd event's DoublePress field is false, the gesture is a single tap.
+ //
+ // These attributes aren't exclusive. A long press drag is perfectly valid.
+ //
+ // The uncommon "double press" instead of "double tap" terminology is
+ // because, in this package, taps are associated with button releases, not
+ // button presses. Note also that "double" really means "at least two".
+ TypeIsLongPress Type = 10
+ TypeIsDoublePress Type = 11
+ TypeIsDrag Type = 12
+
+ // TypeTap and TypeDrag are tap and drag events.
+ //
+ // For 'flinging' drags, to simulate inertia, look to the Velocity field of
+ // the TypeEnd event.
+ //
+ // TODO: implement velocity.
+ TypeTap Type = 20
+ TypeDrag Type = 21
+
+ // All internal types are >= typeInternal.
+ typeInternal Type = 100
+
+ // The typeXxxSchedule and typeXxxResolve constants are used for the two
+ // step process for sending an event after a timeout, in a separate
+ // goroutine. There are two steps so that the spawned goroutine is
+ // guaranteed to execute only after any other EventDeque.SendFirst calls
+ // are made for the one underlying mouse or touch event.
+
+ typeDoublePressSchedule Type = 100
+ typeDoublePressResolve Type = 101
+
+ typeLongPressSchedule Type = 110
+ typeLongPressResolve Type = 111
+)
+
+func (t Type) String() string {
+ switch t {
+ case TypeStart:
+ return "Start"
+ case TypeEnd:
+ return "End"
+ case TypeIsLongPress:
+ return "IsLongPress"
+ case TypeIsDoublePress:
+ return "IsDoublePress"
+ case TypeIsDrag:
+ return "IsDrag"
+ case TypeTap:
+ return "Tap"
+ case TypeDrag:
+ return "Drag"
+ default:
+ return fmt.Sprintf("gesture.Type(%d)", t)
+ }
+}
+
+// Point is a mouse or touch location, in pixels.
+type Point struct {
+ X, Y float32
+}
+
+// Event is a gesture event.
+type Event struct {
+ // Type is the gesture type.
+ Type Type
+
+ // Drag, LongPress and DoublePress are set when the gesture is recognized as
+ // a drag, etc.
+ //
+ // Note that these status fields can be lost during a gesture's events over
+ // time: LongPress can be set for the first press of a double press, but
+ // unset on the second press.
+ Drag bool
+ LongPress bool
+ DoublePress bool
+
+ // InitialPos is the initial position of the button press or touch that
+ // started this gesture.
+ InitialPos Point
+
+ // CurrentPos is the current position of the button or touch event.
+ CurrentPos Point
+
+ // TODO: a "Velocity Point" field. See
+ // - frameworks/native/libs/input/VelocityTracker.cpp in AOSP, or
+ // - https://chromium.googlesource.com/chromium/src/+/master/ui/events/gesture_detection/velocity_tracker.cc in Chromium,
+ // for some velocity tracking implementations.
+
+ // Time is the event's time.
+ Time time.Time
+
+ // TODO: include the mouse Button and key Modifiers?
+}
+
+type internalEvent struct {
+ eventFilter *EventFilter
+
+ typ Type
+ x, y float32
+
+ // pressCounter is the EventFilter.pressCounter value at the time this
+ // internal event was scheduled to be delivered after a timeout. It detects
+ // whether there have been other button presses and releases during that
+ // timeout, and hence whether this internalEvent is obsolete.
+ pressCounter uint32
+}
+
+// EventFilter generates gesture events from lower level mouse and touch
+// events.
+type EventFilter struct {
+ EventDeque screen.EventDeque
+
+ inProgress bool
+ drag bool
+ longPress bool
+ doublePress bool
+
+ // initialPos is the initial position of the button press or touch that
+ // started this gesture.
+ initialPos Point
+
+ // pressButton is the initial button that started this gesture. If
+ // button.None, no gesture is in progress.
+ pressButton mouse.Button
+
+ // pressCounter is incremented on every button press and release.
+ pressCounter uint32
+}
+
+func (f *EventFilter) sendFirst(t Type, x, y float32, now time.Time) {
+ if t >= typeInternal {
+ f.EventDeque.SendFirst(internalEvent{
+ eventFilter: f,
+ typ: t,
+ x: x,
+ y: y,
+ pressCounter: f.pressCounter,
+ })
+ return
+ }
+ f.EventDeque.SendFirst(Event{
+ Type: t,
+ Drag: f.drag,
+ LongPress: f.longPress,
+ DoublePress: f.doublePress,
+ InitialPos: f.initialPos,
+ CurrentPos: Point{
+ X: x,
+ Y: y,
+ },
+ // TODO: Velocity.
+ Time: now,
+ })
+}
+
+func (f *EventFilter) sendAfter(e internalEvent, sleep time.Duration) {
+ time.Sleep(sleep)
+ f.EventDeque.SendFirst(e)
+}
+
+func (f *EventFilter) end(x, y float32, now time.Time) {
+ f.sendFirst(TypeEnd, x, y, now)
+ f.inProgress = false
+ f.drag = false
+ f.longPress = false
+ f.doublePress = false
+ f.initialPos = Point{}
+ f.pressButton = mouse.ButtonNone
+}
+
+// Filter filters the event. It can return e, a different event, or nil to
+// consume the event. It can also trigger side effects such as pushing new
+// events onto its EventDeque.
+func (f *EventFilter) Filter(e interface{}) interface{} {
+ switch e := e.(type) {
+ case internalEvent:
+ if e.eventFilter != f {
+ break
+ }
+
+ now := time.Now()
+
+ switch e.typ {
+ case typeDoublePressSchedule:
+ e.typ = typeDoublePressResolve
+ go f.sendAfter(e, doublePressThreshold)
+
+ case typeDoublePressResolve:
+ if e.pressCounter == f.pressCounter {
+ // It's a single press only.
+ f.end(e.x, e.y, now)
+ }
+
+ case typeLongPressSchedule:
+ e.typ = typeLongPressResolve
+ go f.sendAfter(e, longPressThreshold)
+
+ case typeLongPressResolve:
+ if e.pressCounter == f.pressCounter && !f.drag {
+ f.longPress = true
+ f.sendFirst(TypeIsLongPress, e.x, e.y, now)
+ }
+ }
+ return nil
+
+ case mouse.Event:
+ now := time.Now()
+
+ switch e.Direction {
+ case mouse.DirNone:
+ if f.pressButton == mouse.ButtonNone {
+ break
+ }
+ startDrag := false
+ if !f.drag &&
+ (abs(e.X-f.initialPos.X) > dragThreshold || abs(e.Y-f.initialPos.Y) > dragThreshold) {
+ f.drag = true
+ startDrag = true
+ }
+ if f.drag {
+ f.sendFirst(TypeDrag, e.X, e.Y, now)
+ }
+ if startDrag {
+ f.sendFirst(TypeIsDrag, e.X, e.Y, now)
+ }
+
+ case mouse.DirPress:
+ if f.pressButton != mouse.ButtonNone {
+ break
+ }
+
+ oldInProgress := f.inProgress
+ oldDoublePress := f.doublePress
+
+ f.drag = false
+ f.longPress = false
+ f.doublePress = f.inProgress
+ f.initialPos = Point{e.X, e.Y}
+ f.pressButton = e.Button
+ f.pressCounter++
+
+ f.inProgress = true
+
+ f.sendFirst(typeLongPressSchedule, e.X, e.Y, now)
+ if !oldDoublePress && f.doublePress {
+ f.sendFirst(TypeIsDoublePress, e.X, e.Y, now)
+ }
+ if !oldInProgress {
+ f.sendFirst(TypeStart, e.X, e.Y, now)
+ }
+
+ case mouse.DirRelease:
+ if f.pressButton != e.Button {
+ break
+ }
+ f.pressButton = mouse.ButtonNone
+ f.pressCounter++
+
+ if f.drag {
+ f.end(e.X, e.Y, now)
+ break
+ }
+ f.sendFirst(typeDoublePressSchedule, e.X, e.Y, now)
+ f.sendFirst(TypeTap, e.X, e.Y, now)
+ }
+ }
+ return e
+}
+
+func abs(x float32) float32 {
+ if x < 0 {
+ return -x
+ } else if x == 0 {
+ return 0 // Handle floating point negative zero.
+ }
+ return x
+}
diff --git a/shiny/go.mod b/shiny/go.mod
new file mode 100644
index 0000000..7e784c3
--- /dev/null
+++ b/shiny/go.mod
@@ -0,0 +1,12 @@
+module golang.org/x/exp/shiny
+
+go 1.18
+
+require (
+ dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b
+ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4
+ github.com/jezek/xgb v1.0.0
+ golang.org/x/image v0.0.0-20190802002840-cff245a6509b
+ golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f
+ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654
+)
diff --git a/shiny/go.sum b/shiny/go.sum
new file mode 100644
index 0000000..92706bc
--- /dev/null
+++ b/shiny/go.sum
@@ -0,0 +1,34 @@
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b h1:a26Bdkl2B9PmYN6vGXnnfB2UGKjz0Moif1aEg+xTd7M=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/jezek/xgb v1.0.0 h1:s2rRzAV8KQRlpsYA7Uyxoidv1nodMF0m6dIG6FhhVLQ=
+github.com/jezek/xgb v1.0.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f h1:kgfVkAEEQXXQ0qc6dH7n6y37NAYmTFmz0YRwrRjgxKw=
+golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+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=
diff --git a/shiny/iconvg/buffer.go b/shiny/iconvg/buffer.go
new file mode 100644
index 0000000..e1515d0
--- /dev/null
+++ b/shiny/iconvg/buffer.go
@@ -0,0 +1,343 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "image/color"
+ "math"
+)
+
+// buffer holds an encoded IconVG graphic.
+//
+// The decodeXxx methods return the decoded value and an integer n, the number
+// of bytes that value was encoded in. They return n == 0 if an error occurred.
+//
+// The encodeXxx methods append to the buffer, modifying the slice in place.
+type buffer []byte
+
+func (b buffer) decodeNatural() (u uint32, n int) {
+ if len(b) < 1 {
+ return 0, 0
+ }
+ x := b[0]
+ if x&0x01 == 0 {
+ return uint32(x) >> 1, 1
+ }
+ if x&0x02 == 0 {
+ if len(b) >= 2 {
+ y := uint16(b[0]) | uint16(b[1])<<8
+ return uint32(y) >> 2, 2
+ }
+ return 0, 0
+ }
+ if len(b) >= 4 {
+ y := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+ return y >> 2, 4
+ }
+ return 0, 0
+}
+
+// decodeNaturalFFV1 is like decodeNatural but for File Format Version 1. See
+// https://github.com/google/iconvg/issues/33
+func (b buffer) decodeNaturalFFV1() (u uint32, n int) {
+ if len(b) < 1 {
+ return 0, 0
+ }
+ x := b[0]
+ if x&0x01 != 0 {
+ return uint32(x) >> 1, 1
+ }
+ if x&0x02 != 0 {
+ if len(b) >= 2 {
+ y := uint16(b[0]) | uint16(b[1])<<8
+ return uint32(y) >> 2, 2
+ }
+ return 0, 0
+ }
+ if len(b) >= 4 {
+ y := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+ return y >> 2, 4
+ }
+ return 0, 0
+}
+
+func (b buffer) decodeReal() (f float32, n int) {
+ switch u, n := b.decodeNatural(); n {
+ case 0:
+ return 0, n
+ case 1:
+ return float32(u), n
+ case 2:
+ return float32(u), n
+ default:
+ return math.Float32frombits(u << 2), n
+ }
+}
+
+func (b buffer) decodeCoordinate() (f float32, n int) {
+ switch u, n := b.decodeNatural(); n {
+ case 0:
+ return 0, n
+ case 1:
+ return float32(int32(u) - 64), n
+ case 2:
+ return float32(int32(u)-64*128) / 64, n
+ default:
+ return math.Float32frombits(u << 2), n
+ }
+}
+
+func (b buffer) decodeZeroToOne() (f float32, n int) {
+ switch u, n := b.decodeNatural(); n {
+ case 0:
+ return 0, n
+ case 1:
+ return float32(u) / 120, n
+ case 2:
+ return float32(u) / 15120, n
+ default:
+ return math.Float32frombits(u << 2), n
+ }
+}
+
+func (b buffer) decodeColor1() (c Color, n int) {
+ if len(b) < 1 {
+ return Color{}, 0
+ }
+ return decodeColor1(b[0]), 1
+}
+
+func (b buffer) decodeColor2() (c Color, n int) {
+ if len(b) < 2 {
+ return Color{}, 0
+ }
+ return RGBAColor(color.RGBA{
+ R: 0x11 * (b[0] >> 4),
+ G: 0x11 * (b[0] & 0x0f),
+ B: 0x11 * (b[1] >> 4),
+ A: 0x11 * (b[1] & 0x0f),
+ }), 2
+}
+
+func (b buffer) decodeColor3Direct() (c Color, n int) {
+ if len(b) < 3 {
+ return Color{}, 0
+ }
+ return RGBAColor(color.RGBA{
+ R: b[0],
+ G: b[1],
+ B: b[2],
+ A: 0xff,
+ }), 3
+}
+
+func (b buffer) decodeColor4() (c Color, n int) {
+ if len(b) < 4 {
+ return Color{}, 0
+ }
+ return RGBAColor(color.RGBA{
+ R: b[0],
+ G: b[1],
+ B: b[2],
+ A: b[3],
+ }), 4
+}
+
+func (b buffer) decodeColor3Indirect() (c Color, n int) {
+ if len(b) < 3 {
+ return Color{}, 0
+ }
+ return BlendColor(b[0], b[1], b[2]), 3
+}
+
+func (b *buffer) encodeNatural(u uint32) {
+ if u < 1<<7 {
+ u = (u << 1)
+ *b = append(*b, uint8(u))
+ return
+ }
+ if u < 1<<14 {
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return
+ }
+ u = (u << 2) | 3
+ *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+}
+
+// encodeNaturalFFV1 is like encodeNatural but for File Format Version 1. See
+// https://github.com/google/iconvg/issues/33
+func (b *buffer) encodeNaturalFFV1(u uint32) {
+ if u < 1<<7 {
+ u = (u << 1) | 0x01
+ *b = append(*b, uint8(u))
+ return
+ }
+ if u < 1<<14 {
+ u = (u << 2) | 0x02
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return
+ }
+ u = (u << 2)
+ *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+}
+
+func (b *buffer) encodeReal(f float32) int {
+ if u := uint32(f); float32(u) == f && u < 1<<14 {
+ if u < 1<<7 {
+ u = (u << 1)
+ *b = append(*b, uint8(u))
+ return 1
+ }
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return 2
+ }
+ b.encode4ByteReal(f)
+ return 4
+}
+
+func (b *buffer) encode4ByteReal(f float32) {
+ u := math.Float32bits(f)
+
+ // Round the fractional bits (the low 23 bits) to the nearest multiple of
+ // 4, being careful not to overflow into the upper bits.
+ v := u & 0x007fffff
+ if v < 0x007ffffe {
+ v += 2
+ }
+ u = (u & 0xff800000) | v
+
+ // A 4 byte encoding has the low two bits set.
+ u |= 0x03
+ *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+}
+
+// encode4ByteRealFFV1 is like encode4ByteReal but for File Format Version 1.
+// See https://github.com/google/iconvg/issues/33
+func (b *buffer) encode4ByteRealFFV1(f float32) {
+ u := math.Float32bits(f)
+
+ // Round the fractional bits (the low 23 bits) to the nearest multiple of
+ // 4, being careful not to overflow into the upper bits.
+ v := u & 0x007fffff
+ if v < 0x007ffffe {
+ v += 2
+ }
+ u = (u & 0xff800000) | v
+
+ // A 4 byte encoding has the low two bits unset.
+ u &= 0xfffffffc
+ *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+}
+
+func (b *buffer) encodeCoordinate(f float32) int {
+ if i := int32(f); -64 <= i && i < +64 && float32(i) == f {
+ u := uint32(i + 64)
+ u = (u << 1)
+ *b = append(*b, uint8(u))
+ return 1
+ }
+ if i := int32(f * 64); -128*64 <= i && i < +128*64 && float32(i) == f*64 {
+ u := uint32(i + 128*64)
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return 2
+ }
+ b.encode4ByteReal(f)
+ return 4
+}
+
+// encodeCoordinateFFV1 is like encodeCoordinate but for File Format Version 1.
+// See https://github.com/google/iconvg/issues/33
+func (b *buffer) encodeCoordinateFFV1(f float32) int {
+ if i := int32(f); -64 <= i && i < +64 && float32(i) == f {
+ u := uint32(i + 64)
+ u = (u << 1) | 0x01
+ *b = append(*b, uint8(u))
+ return 1
+ }
+ if i := int32(f * 64); -128*64 <= i && i < +128*64 && float32(i) == f*64 {
+ u := uint32(i + 128*64)
+ u = (u << 2) | 0x02
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return 2
+ }
+ b.encode4ByteRealFFV1(f)
+ return 4
+}
+
+func (b *buffer) encodeCoordinatePairFFV1(f [2]float32) int {
+ n0 := b.encodeCoordinateFFV1(f[0])
+ n1 := b.encodeCoordinateFFV1(f[1])
+ return n0 + n1
+}
+
+func (b *buffer) encodeAngle(f float32) int {
+ // Normalize f to the range [0, 1).
+ g := float64(f)
+ g -= math.Floor(g)
+ return b.encodeZeroToOne(float32(g))
+}
+
+func (b *buffer) encodeZeroToOne(f float32) int {
+ if u := uint32(f * 15120); float32(u) == f*15120 && u < 15120 {
+ if u%126 == 0 {
+ u = ((u / 126) << 1)
+ *b = append(*b, uint8(u))
+ return 1
+ }
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return 2
+ }
+ b.encode4ByteReal(f)
+ return 4
+}
+
+func (b *buffer) encodeColor1(c Color) {
+ if x, ok := encodeColor1(c); ok {
+ *b = append(*b, x)
+ return
+ }
+ // Default to opaque black.
+ *b = append(*b, 0x00)
+}
+
+func (b *buffer) encodeColor2(c Color) {
+ if x, ok := encodeColor2(c); ok {
+ *b = append(*b, x[0], x[1])
+ return
+ }
+ // Default to opaque black.
+ *b = append(*b, 0x00, 0x0f)
+}
+
+func (b *buffer) encodeColor3Direct(c Color) {
+ if x, ok := encodeColor3Direct(c); ok {
+ *b = append(*b, x[0], x[1], x[2])
+ return
+ }
+ // Default to opaque black.
+ *b = append(*b, 0x00, 0x00, 0x00)
+}
+
+func (b *buffer) encodeColor4(c Color) {
+ if x, ok := encodeColor4(c); ok {
+ *b = append(*b, x[0], x[1], x[2], x[3])
+ return
+ }
+ // Default to opaque black.
+ *b = append(*b, 0x00, 0x00, 0x00, 0xff)
+}
+
+func (b *buffer) encodeColor3Indirect(c Color) {
+ if x, ok := encodeColor3Indirect(c); ok {
+ *b = append(*b, x[0], x[1], x[2])
+ return
+ }
+ // Default to opaque black.
+ *b = append(*b, 0x00, 0x00, 0x00)
+}
diff --git a/shiny/iconvg/buffer_test.go b/shiny/iconvg/buffer_test.go
new file mode 100644
index 0000000..2c15821
--- /dev/null
+++ b/shiny/iconvg/buffer_test.go
@@ -0,0 +1,332 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "image/color"
+ "math"
+ "testing"
+)
+
+var naturalTestCases = []struct {
+ in buffer
+ want uint32
+ wantN int
+}{{
+ buffer{},
+ 0,
+ 0,
+}, {
+ buffer{0x28},
+ 20,
+ 1,
+}, {
+ buffer{0x59},
+ 0,
+ 0,
+}, {
+ buffer{0x59, 0x83},
+ 8406,
+ 2,
+}, {
+ buffer{0x07, 0x00, 0x80},
+ 0,
+ 0,
+}, {
+ buffer{0x07, 0x00, 0x80, 0x3f},
+ 266338305,
+ 4,
+}}
+
+func TestDecodeNatural(t *testing.T) {
+ for _, tc := range naturalTestCases {
+ got, gotN := tc.in.decodeNatural()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeNatural(t *testing.T) {
+ for _, tc := range naturalTestCases {
+ if tc.wantN == 0 {
+ continue
+ }
+ var b buffer
+ b.encodeNatural(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+var realTestCases = []struct {
+ in buffer
+ want float32
+ wantN int
+}{{
+ buffer{0x28},
+ 20,
+ 1,
+}, {
+ buffer{0x59, 0x83},
+ 8406,
+ 2,
+}, {
+ buffer{0x07, 0x00, 0x80, 0x3f},
+ 1.000000476837158203125,
+ 4,
+}}
+
+func TestDecodeReal(t *testing.T) {
+ for _, tc := range realTestCases {
+ got, gotN := tc.in.decodeReal()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeReal(t *testing.T) {
+ for _, tc := range realTestCases {
+ var b buffer
+ b.encodeReal(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+var coordinateTestCases = []struct {
+ in buffer
+ want float32
+ wantN int
+}{{
+ buffer{0x8e},
+ 7,
+ 1,
+}, {
+ buffer{0x81, 0x87},
+ 7.5,
+ 2,
+}, {
+ buffer{0x03, 0x00, 0xf0, 0x40},
+ 7.5,
+ 4,
+}, {
+ buffer{0x07, 0x00, 0xf0, 0x40},
+ 7.5000019073486328125,
+ 4,
+}}
+
+func TestDecodeCoordinate(t *testing.T) {
+ for _, tc := range coordinateTestCases {
+ got, gotN := tc.in.decodeCoordinate()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeCoordinate(t *testing.T) {
+ for _, tc := range coordinateTestCases {
+ if tc.want == 7.5 && tc.wantN == 4 {
+ // 7.5 can be encoded in fewer than 4 bytes.
+ continue
+ }
+ var b buffer
+ b.encodeCoordinate(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+func trunc(x float32) float32 {
+ u := math.Float32bits(x)
+ u &^= 0x03
+ return math.Float32frombits(u)
+}
+
+var zeroToOneTestCases = []struct {
+ in buffer
+ want float32
+ wantN int
+}{{
+ buffer{0x0a},
+ 1.0 / 24,
+ 1,
+}, {
+ buffer{0x41, 0x1a},
+ 1.0 / 9,
+ 2,
+}, {
+ buffer{0x63, 0x0b, 0x36, 0x3b},
+ trunc(1.0 / 360),
+ 4,
+}}
+
+func TestDecodeZeroToOne(t *testing.T) {
+ for _, tc := range zeroToOneTestCases {
+ got, gotN := tc.in.decodeZeroToOne()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeZeroToOne(t *testing.T) {
+ for _, tc := range zeroToOneTestCases {
+ var b buffer
+ b.encodeZeroToOne(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+var colorTestCases = []struct {
+ in buffer
+ decode func(buffer) (Color, int)
+ encode func(*buffer, Color)
+ want Color
+ wantN int
+}{{
+ buffer{},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ Color{},
+ 0,
+}, {
+ buffer{0x00},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0xff}),
+ 1,
+}, {
+ buffer{0x30},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ RGBAColor(color.RGBA{0x40, 0xff, 0xc0, 0xff}),
+ 1,
+}, {
+ buffer{0x7c},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ RGBAColor(color.RGBA{0xff, 0xff, 0xff, 0xff}),
+ 1,
+}, {
+ buffer{0x7d},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ RGBAColor(color.RGBA{0xc0, 0xc0, 0xc0, 0xc0}),
+ 1,
+}, {
+ buffer{0x7e},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ RGBAColor(color.RGBA{0x80, 0x80, 0x80, 0x80}),
+ 1,
+}, {
+ buffer{0x7f},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0x00}),
+ 1,
+}, {
+ buffer{0x80},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ PaletteIndexColor(0x00),
+ 1,
+}, {
+ buffer{0xbf},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ PaletteIndexColor(0x3f),
+ 1,
+}, {
+ buffer{0xc0},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ CRegColor(0x00),
+ 1,
+}, {
+ buffer{0xff},
+ buffer.decodeColor1,
+ (*buffer).encodeColor1,
+ CRegColor(0x3f),
+ 1,
+}, {
+ buffer{0x01},
+ buffer.decodeColor2,
+ (*buffer).encodeColor2,
+ Color{},
+ 0,
+}, {
+ buffer{0x38, 0x0f},
+ buffer.decodeColor2,
+ (*buffer).encodeColor2,
+ RGBAColor(color.RGBA{0x33, 0x88, 0x00, 0xff}),
+ 2,
+}, {
+ buffer{0x00, 0x02},
+ buffer.decodeColor3Direct,
+ (*buffer).encodeColor3Direct,
+ Color{},
+ 0,
+}, {
+ buffer{0x30, 0x66, 0x07},
+ buffer.decodeColor3Direct,
+ (*buffer).encodeColor3Direct,
+ RGBAColor(color.RGBA{0x30, 0x66, 0x07, 0xff}),
+ 3,
+}, {
+ buffer{0x00, 0x00, 0x03},
+ buffer.decodeColor4,
+ (*buffer).encodeColor4,
+ Color{},
+ 0,
+}, {
+ buffer{0x30, 0x66, 0x07, 0x80},
+ buffer.decodeColor4,
+ (*buffer).encodeColor4,
+ RGBAColor(color.RGBA{0x30, 0x66, 0x07, 0x80}),
+ 4,
+}, {
+ buffer{0x00, 0x04},
+ buffer.decodeColor3Indirect,
+ (*buffer).encodeColor3Indirect,
+ Color{},
+ 0,
+}, {
+ buffer{0x40, 0x7f, 0x82},
+ buffer.decodeColor3Indirect,
+ (*buffer).encodeColor3Indirect,
+ BlendColor(0x40, 0x7f, 0x82),
+ 3,
+}}
+
+func TestDecodeColor(t *testing.T) {
+ for _, tc := range colorTestCases {
+ got, gotN := tc.decode(tc.in)
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeColor(t *testing.T) {
+ for _, tc := range colorTestCases {
+ if tc.wantN == 0 {
+ continue
+ }
+ var b buffer
+ tc.encode(&b, tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
diff --git a/shiny/iconvg/color.go b/shiny/iconvg/color.go
new file mode 100644
index 0000000..b2fa7d3
--- /dev/null
+++ b/shiny/iconvg/color.go
@@ -0,0 +1,180 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "image/color"
+)
+
+func validAlphaPremulColor(c color.RGBA) bool {
+ return c.R <= c.A && c.G <= c.A && c.B <= c.A
+}
+
+// ColorType distinguishes types of Colors.
+type ColorType uint8
+
+const (
+ // ColorTypeRGBA is a direct RGBA color.
+ ColorTypeRGBA ColorType = iota
+
+ // ColorTypePaletteIndex is an indirect color, indexing the custom palette.
+ ColorTypePaletteIndex
+
+ // ColorTypeCReg is an indirect color, indexing the CREG color registers.
+ ColorTypeCReg
+
+ // ColorTypeBlend is an indirect color, blending two other colors.
+ ColorTypeBlend
+)
+
+// Color is an IconVG color, whose RGBA values can depend on context. Some
+// Colors are direct RGBA values. Other Colors are indirect, referring to an
+// index of the custom palette, a color register of the decoder virtual
+// machine, or a blend of two other Colors.
+//
+// See the "Colors" section in the package documentation for details.
+type Color struct {
+ typ ColorType
+ data color.RGBA
+}
+
+func (c Color) rgba() color.RGBA { return c.data }
+func (c Color) paletteIndex() uint8 { return c.data.R }
+func (c Color) cReg() uint8 { return c.data.R }
+func (c Color) blend() (t, c0, c1 uint8) { return c.data.R, c.data.G, c.data.B }
+
+// Resolve resolves the Color's RGBA value, given its context: the custom
+// palette and the color registers of the decoder virtual machine.
+func (c Color) Resolve(pal *Palette, cReg *[64]color.RGBA) color.RGBA {
+ switch c.typ {
+ case ColorTypeRGBA:
+ return c.rgba()
+ case ColorTypePaletteIndex:
+ return pal[c.paletteIndex()&0x3f]
+ case ColorTypeCReg:
+ return cReg[c.cReg()&0x3f]
+ }
+ t, c0, c1 := c.blend()
+ p, q := uint32(255-t), uint32(t)
+ rgba0 := decodeColor1(c0).Resolve(pal, cReg)
+ rgba1 := decodeColor1(c1).Resolve(pal, cReg)
+ return color.RGBA{
+ uint8(((p * uint32(rgba0.R)) + q*uint32(rgba1.R) + 128) / 255),
+ uint8(((p * uint32(rgba0.G)) + q*uint32(rgba1.G) + 128) / 255),
+ uint8(((p * uint32(rgba0.B)) + q*uint32(rgba1.B) + 128) / 255),
+ uint8(((p * uint32(rgba0.A)) + q*uint32(rgba1.A) + 128) / 255),
+ }
+}
+
+// RGBAColor returns a direct Color.
+func RGBAColor(c color.RGBA) Color { return Color{ColorTypeRGBA, c} }
+
+// PaletteIndexColor returns an indirect Color referring to an index of the
+// custom palette.
+func PaletteIndexColor(i uint8) Color { return Color{ColorTypePaletteIndex, color.RGBA{R: i & 0x3f}} }
+
+// CRegColor returns an indirect Color referring to a color register of the
+// decoder virtual machine.
+func CRegColor(i uint8) Color { return Color{ColorTypeCReg, color.RGBA{R: i & 0x3f}} }
+
+// BlendColor returns an indirect Color that blends two other Colors. Those two
+// other Colors must both be encodable as a 1 byte color.
+//
+// To blend a Color that is not encodable as a 1 byte color, first load that
+// Color into a CREG color register, then call CRegColor to produce a Color
+// that is encodable as a 1 byte color. See testdata/favicon.ivg for an
+// example.
+//
+// See the "Colors" section in the package documentation for details.
+func BlendColor(t, c0, c1 uint8) Color { return Color{ColorTypeBlend, color.RGBA{R: t, G: c0, B: c1}} }
+
+func decodeColor1(x byte) Color {
+ if x >= 0x80 {
+ if x >= 0xc0 {
+ return CRegColor(x)
+ } else {
+ return PaletteIndexColor(x)
+ }
+ }
+ if x >= 125 {
+ switch x - 125 {
+ case 0:
+ return RGBAColor(color.RGBA{0xc0, 0xc0, 0xc0, 0xc0})
+ case 1:
+ return RGBAColor(color.RGBA{0x80, 0x80, 0x80, 0x80})
+ case 2:
+ return RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0x00})
+ }
+ }
+ blue := dc1Table[x%5]
+ x = x / 5
+ green := dc1Table[x%5]
+ x = x / 5
+ red := dc1Table[x]
+ return RGBAColor(color.RGBA{red, green, blue, 0xff})
+}
+
+var dc1Table = [5]byte{0x00, 0x40, 0x80, 0xc0, 0xff}
+
+func is1(u uint8) bool { return u&0x3f == 0 || u == 0xff }
+
+func encodeColor1(c Color) (x byte, ok bool) {
+ switch c.typ {
+ case ColorTypeRGBA:
+ if c.data.A != 0xff {
+ switch c.data {
+ case color.RGBA{0x00, 0x00, 0x00, 0x00}:
+ return 127, true
+ case color.RGBA{0x80, 0x80, 0x80, 0x80}:
+ return 126, true
+ case color.RGBA{0xc0, 0xc0, 0xc0, 0xc0}:
+ return 125, true
+ }
+ } else if is1(c.data.R) && is1(c.data.G) && is1(c.data.B) && is1(c.data.A) {
+ r := c.data.R / 0x3f
+ g := c.data.G / 0x3f
+ b := c.data.B / 0x3f
+ return 25*r + 5*g + b, true
+ }
+ case ColorTypePaletteIndex:
+ return c.data.R | 0x80, true
+ case ColorTypeCReg:
+ return c.data.R | 0xc0, true
+ }
+ return 0, false
+}
+
+func is2(u uint8) bool { return u%0x11 == 0 }
+
+func encodeColor2(c Color) (x [2]byte, ok bool) {
+ if c.typ == ColorTypeRGBA && is2(c.data.R) && is2(c.data.G) && is2(c.data.B) && is2(c.data.A) {
+ return [2]byte{
+ (c.data.R/0x11)<<4 | (c.data.G / 0x11),
+ (c.data.B/0x11)<<4 | (c.data.A / 0x11),
+ }, true
+ }
+ return [2]byte{}, false
+}
+
+func encodeColor3Direct(c Color) (x [3]byte, ok bool) {
+ if c.typ == ColorTypeRGBA && c.data.A == 0xff {
+ return [3]byte{c.data.R, c.data.G, c.data.B}, true
+ }
+ return [3]byte{}, false
+}
+
+func encodeColor4(c Color) (x [4]byte, ok bool) {
+ if c.typ == ColorTypeRGBA {
+ return [4]byte{c.data.R, c.data.G, c.data.B, c.data.A}, true
+ }
+ return [4]byte{}, false
+}
+
+func encodeColor3Indirect(c Color) (x [3]byte, ok bool) {
+ if c.typ == ColorTypeBlend {
+ return [3]byte{c.data.R, c.data.G, c.data.B}, true
+ }
+ return [3]byte{}, false
+}
diff --git a/shiny/iconvg/decode.go b/shiny/iconvg/decode.go
new file mode 100644
index 0000000..137e173
--- /dev/null
+++ b/shiny/iconvg/decode.go
@@ -0,0 +1,699 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "bytes"
+ "errors"
+ "image/color"
+)
+
+var (
+ errInconsistentMetadataChunkLength = errors.New("iconvg: inconsistent metadata chunk length")
+ errInvalidColor = errors.New("iconvg: invalid color")
+ errInvalidMagicIdentifier = errors.New("iconvg: invalid magic identifier")
+ errInvalidMetadataChunkLength = errors.New("iconvg: invalid metadata chunk length")
+ errInvalidMetadataIdentifier = errors.New("iconvg: invalid metadata identifier")
+ errInvalidNumber = errors.New("iconvg: invalid number")
+ errInvalidNumberOfMetadataChunks = errors.New("iconvg: invalid number of metadata chunks")
+ errInvalidSuggestedPalette = errors.New("iconvg: invalid suggested palette")
+ errInvalidViewBox = errors.New("iconvg: invalid view box")
+ errUnsupportedDrawingOpcode = errors.New("iconvg: unsupported drawing opcode")
+ errUnsupportedMetadataIdentifier = errors.New("iconvg: unsupported metadata identifier")
+ errUnsupportedStylingOpcode = errors.New("iconvg: unsupported styling opcode")
+ errUnsupportedUpgrade = errors.New("iconvg: unsupported upgrade")
+)
+
+var midDescriptions = [...]string{
+ midViewBox: "viewBox",
+ midSuggestedPalette: "suggested palette",
+}
+
+// Destination handles the actions decoded from an IconVG graphic's opcodes.
+//
+// When passed to Decode, the first method called (if any) will be Reset. No
+// methods will be called at all if an error is encountered in the encoded form
+// before the metadata is fully decoded.
+type Destination interface {
+ Reset(m Metadata)
+
+ SetCSel(cSel uint8)
+ SetNSel(nSel uint8)
+ SetCReg(adj uint8, incr bool, c Color)
+ SetNReg(adj uint8, incr bool, f float32)
+ SetLOD(lod0, lod1 float32)
+
+ StartPath(adj uint8, x, y float32)
+ ClosePathEndPath()
+ ClosePathAbsMoveTo(x, y float32)
+ ClosePathRelMoveTo(x, y float32)
+
+ AbsHLineTo(x float32)
+ RelHLineTo(x float32)
+ AbsVLineTo(y float32)
+ RelVLineTo(y float32)
+ AbsLineTo(x, y float32)
+ RelLineTo(x, y float32)
+ AbsSmoothQuadTo(x, y float32)
+ RelSmoothQuadTo(x, y float32)
+ AbsQuadTo(x1, y1, x, y float32)
+ RelQuadTo(x1, y1, x, y float32)
+ AbsSmoothCubeTo(x2, y2, x, y float32)
+ RelSmoothCubeTo(x2, y2, x, y float32)
+ AbsCubeTo(x1, y1, x2, y2, x, y float32)
+ RelCubeTo(x1, y1, x2, y2, x, y float32)
+ AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
+ RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
+}
+
+type printer func(b []byte, format string, args ...interface{})
+
+// DecodeOptions are the optional parameters to the Decode function.
+type DecodeOptions struct {
+ // Palette is an optional 64 color palette. If one isn't provided, the
+ // IconVG graphic's suggested palette will be used.
+ Palette *Palette
+}
+
+// DecodeMetadata decodes only the metadata in an IconVG graphic.
+func DecodeMetadata(src []byte) (m Metadata, err error) {
+ m.ViewBox = DefaultViewBox
+ m.Palette = DefaultPalette
+ if err = decode(nil, nil, &m, true, src, nil); err != nil {
+ return Metadata{}, err
+ }
+ return m, nil
+}
+
+// Decode decodes an IconVG graphic.
+func Decode(dst Destination, src []byte, opts *DecodeOptions) error {
+ m := Metadata{
+ ViewBox: DefaultViewBox,
+ Palette: DefaultPalette,
+ }
+ if opts != nil && opts.Palette != nil {
+ m.Palette = *opts.Palette
+ }
+ return decode(dst, nil, &m, false, src, opts)
+}
+
+func decode(dst Destination, p printer, m *Metadata, metadataOnly bool, src buffer, opts *DecodeOptions) (err error) {
+ if !bytes.HasPrefix(src, magicBytes) {
+ // TODO: detect FFV 1 (File Format Version 1), as opposed to the FFV 0
+ // that this package implements, and delegate to a FFV 1 decoder.
+ return errInvalidMagicIdentifier
+ }
+ if p != nil {
+ p(src[:len(magic)], "IconVG Magic identifier\n")
+ }
+ src = src[len(magic):]
+
+ nMetadataChunks, n := src.decodeNatural()
+ if n == 0 {
+ return errInvalidNumberOfMetadataChunks
+ }
+ if p != nil {
+ p(src[:n], "Number of metadata chunks: %d\n", nMetadataChunks)
+ }
+ src = src[n:]
+
+ for ; nMetadataChunks > 0; nMetadataChunks-- {
+ src, err = decodeMetadataChunk(p, m, src, opts)
+ if err != nil {
+ return err
+ }
+ }
+ if metadataOnly {
+ return nil
+ }
+ if dst != nil {
+ dst.Reset(*m)
+ }
+
+ mf := modeFunc(decodeStyling)
+ for len(src) > 0 {
+ mf, src, err = mf(dst, p, src)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func decodeMetadataChunk(p printer, m *Metadata, src buffer, opts *DecodeOptions) (src1 buffer, err error) {
+ length, n := src.decodeNatural()
+ if n == 0 {
+ return nil, errInvalidMetadataChunkLength
+ }
+ if p != nil {
+ p(src[:n], "Metadata chunk length: %d\n", length)
+ }
+ src = src[n:]
+ lenSrcWant := int64(len(src)) - int64(length)
+
+ mid, n := src.decodeNatural()
+ if n == 0 {
+ return nil, errInvalidMetadataIdentifier
+ }
+ if mid >= uint32(len(midDescriptions)) {
+ return nil, errUnsupportedMetadataIdentifier
+ }
+ if p != nil {
+ p(src[:n], "Metadata Identifier: %d (%s)\n", mid, midDescriptions[mid])
+ }
+ src = src[n:]
+
+ switch mid {
+ case midViewBox:
+ if m.ViewBox.Min[0], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Min[1], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Max[0], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Max[1], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Min[0] > m.ViewBox.Max[0] || m.ViewBox.Min[1] > m.ViewBox.Max[1] ||
+ isNaNOrInfinity(m.ViewBox.Min[0]) || isNaNOrInfinity(m.ViewBox.Min[1]) ||
+ isNaNOrInfinity(m.ViewBox.Max[0]) || isNaNOrInfinity(m.ViewBox.Max[1]) {
+ return nil, errInvalidViewBox
+ }
+
+ case midSuggestedPalette:
+ if len(src) == 0 {
+ return nil, errInvalidSuggestedPalette
+ }
+ length, format := 1+int(src[0]&0x3f), src[0]>>6
+ decode := buffer.decodeColor4
+ switch format {
+ case 0:
+ decode = buffer.decodeColor1
+ case 1:
+ decode = buffer.decodeColor2
+ case 2:
+ decode = buffer.decodeColor3Direct
+ }
+ if p != nil {
+ p(src[:1], " %d palette colors, %d bytes per color\n", length, 1+format)
+ }
+ src = src[1:]
+
+ for i := 0; i < length; i++ {
+ c, n := decode(src)
+ if n == 0 {
+ return nil, errInvalidSuggestedPalette
+ }
+ rgba := c.rgba()
+ if c.typ != ColorTypeRGBA || !validAlphaPremulColor(rgba) {
+ rgba = color.RGBA{0x00, 0x00, 0x00, 0xff}
+ }
+ if p != nil {
+ p(src[:n], " RGBA %02x%02x%02x%02x\n", rgba.R, rgba.G, rgba.B, rgba.A)
+ }
+ src = src[n:]
+ if opts == nil || opts.Palette == nil {
+ m.Palette[i] = rgba
+ }
+ }
+
+ default:
+ return nil, errUnsupportedMetadataIdentifier
+ }
+
+ if int64(len(src)) != lenSrcWant {
+ return nil, errInconsistentMetadataChunkLength
+ }
+ return src, nil
+}
+
+// modeFunc is the decoding mode: whether we are decoding styling or drawing
+// opcodes.
+//
+// It is a function type. The decoding loop calls this function to decode and
+// execute the next opcode from the src buffer, returning the subsequent mode
+// and the remaining source bytes.
+type modeFunc func(dst Destination, p printer, src buffer) (modeFunc, buffer, error)
+
+func decodeStyling(dst Destination, p printer, src buffer) (modeFunc, buffer, error) {
+ switch opcode := src[0]; {
+ case opcode < 0x80:
+ if opcode < 0x40 {
+ opcode &= 0x3f
+ if p != nil {
+ p(src[:1], "Set CSEL = %d\n", opcode)
+ }
+ src = src[1:]
+ if dst != nil {
+ dst.SetCSel(opcode)
+ }
+ } else {
+ opcode &= 0x3f
+ if p != nil {
+ p(src[:1], "Set NSEL = %d\n", opcode)
+ }
+ src = src[1:]
+ if dst != nil {
+ dst.SetNSel(opcode)
+ }
+ }
+ return decodeStyling, src, nil
+ case opcode < 0xa8:
+ return decodeSetCReg(dst, p, src, opcode)
+ case opcode < 0xc0:
+ return decodeSetNReg(dst, p, src, opcode)
+ case opcode < 0xc7:
+ return decodeStartPath(dst, p, src, opcode)
+ case opcode == 0xc7:
+ return decodeSetLOD(dst, p, src)
+ }
+ return nil, nil, errUnsupportedStylingOpcode
+}
+
+func decodeSetCReg(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
+ nBytes, directness, adj := 0, "", opcode&0x07
+ var decode func(buffer) (Color, int)
+ incr := adj == 7
+ if incr {
+ adj = 0
+ }
+
+ switch (opcode - 0x80) >> 3 {
+ case 0:
+ nBytes, directness, decode = 1, "", buffer.decodeColor1
+ case 1:
+ nBytes, directness, decode = 2, "", buffer.decodeColor2
+ case 2:
+ nBytes, directness, decode = 3, " (direct)", buffer.decodeColor3Direct
+ case 3:
+ nBytes, directness, decode = 4, "", buffer.decodeColor4
+ case 4:
+ nBytes, directness, decode = 3, " (indirect)", buffer.decodeColor3Indirect
+ }
+ if p != nil {
+ if incr {
+ p(src[:1], "Set CREG[CSEL-0] to a %d byte%s color; CSEL++\n", nBytes, directness)
+ } else {
+ p(src[:1], "Set CREG[CSEL-%d] to a %d byte%s color\n", adj, nBytes, directness)
+ }
+ }
+ src = src[1:]
+
+ c, n := decode(src)
+ if n == 0 {
+ return nil, nil, errInvalidColor
+ }
+
+ if p != nil {
+ printColor(src[:n], p, c, "")
+ }
+ src = src[n:]
+
+ if dst != nil {
+ dst.SetCReg(adj, incr, c)
+ }
+
+ return decodeStyling, src, nil
+}
+
+func printColor(src []byte, p printer, c Color, prefix string) {
+ switch c.typ {
+ case ColorTypeRGBA:
+ if rgba := c.rgba(); validAlphaPremulColor(rgba) {
+ p(src, " %sRGBA %02x%02x%02x%02x\n", prefix, rgba.R, rgba.G, rgba.B, rgba.A)
+ } else if rgba.A == 0 && rgba.B&0x80 != 0 {
+ p(src, " %sgradient (NSTOPS=%d, CBASE=%d, NBASE=%d, %s, %s)\n",
+ prefix,
+ rgba.R&0x3f,
+ rgba.G&0x3f,
+ rgba.B&0x3f,
+ gradientShapeNames[(rgba.B>>6)&0x01],
+ gradientSpreadNames[rgba.G>>6],
+ )
+ } else {
+ p(src, " %snonsensical color\n", prefix)
+ }
+ case ColorTypePaletteIndex:
+ p(src, " %scustomPalette[%d]\n", prefix, c.paletteIndex())
+ case ColorTypeCReg:
+ p(src, " %sCREG[%d]\n", prefix, c.cReg())
+ case ColorTypeBlend:
+ t, c0, c1 := c.blend()
+ p(src[:1], " blend %d:%d c0:c1\n", 0xff-t, t)
+ printColor(src[1:2], p, decodeColor1(c0), " c0: ")
+ printColor(src[2:3], p, decodeColor1(c1), " c1: ")
+ }
+}
+
+func decodeSetNReg(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
+ decode, typ, adj := buffer.decodeZeroToOne, "zero-to-one", opcode&0x07
+ incr := adj == 7
+ if incr {
+ adj = 0
+ }
+
+ switch (opcode - 0xa8) >> 3 {
+ case 0:
+ decode, typ = buffer.decodeReal, "real"
+ case 1:
+ decode, typ = buffer.decodeCoordinate, "coordinate"
+ }
+ if p != nil {
+ if incr {
+ p(src[:1], "Set NREG[NSEL-0] to a %s number; NSEL++\n", typ)
+ } else {
+ p(src[:1], "Set NREG[NSEL-%d] to a %s number\n", adj, typ)
+ }
+ }
+ src = src[1:]
+
+ f, n := decode(src)
+ if n == 0 {
+ return nil, nil, errInvalidNumber
+ }
+ if p != nil {
+ p(src[:n], " %g\n", f)
+ }
+ src = src[n:]
+
+ if dst != nil {
+ dst.SetNReg(adj, incr, f)
+ }
+
+ return decodeStyling, src, nil
+}
+
+func decodeStartPath(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
+ adj := opcode & 0x07
+ if p != nil {
+ p(src[:1], "Start path, filled with CREG[CSEL-%d]; M (absolute moveTo)\n", adj)
+ }
+ src = src[1:]
+
+ x, src, err := decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, nil, err
+ }
+ y, src, err := decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if dst != nil {
+ dst.StartPath(adj, x, y)
+ }
+
+ return decodeDrawing, src, nil
+}
+
+func decodeSetLOD(dst Destination, p printer, src buffer) (modeFunc, buffer, error) {
+ if p != nil {
+ p(src[:1], "Set LOD\n")
+ }
+ src = src[1:]
+
+ lod0, src, err := decodeNumber(p, src, buffer.decodeReal)
+ if err != nil {
+ return nil, nil, err
+ }
+ lod1, src, err := decodeNumber(p, src, buffer.decodeReal)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if dst != nil {
+ dst.SetLOD(lod0, lod1)
+ }
+ return decodeStyling, src, nil
+}
+
+func decodeDrawing(dst Destination, p printer, src buffer) (mf modeFunc, src1 buffer, err error) {
+ var coords [6]float32
+
+ switch opcode := src[0]; {
+ case opcode < 0xe0:
+ op, nCoords, nReps := "", 0, 1+int(opcode&0x0f)
+ switch opcode >> 4 {
+ case 0x00, 0x01:
+ op = "L (absolute lineTo)"
+ nCoords = 2
+ nReps = 1 + int(opcode&0x1f)
+ case 0x02, 0x03:
+ op = "l (relative lineTo)"
+ nCoords = 2
+ nReps = 1 + int(opcode&0x1f)
+ case 0x04:
+ op = "T (absolute smooth quadTo)"
+ nCoords = 2
+ case 0x05:
+ op = "t (relative smooth quadTo)"
+ nCoords = 2
+ case 0x06:
+ op = "Q (absolute quadTo)"
+ nCoords = 4
+ case 0x07:
+ op = "q (relative quadTo)"
+ nCoords = 4
+ case 0x08:
+ op = "S (absolute smooth cubeTo)"
+ nCoords = 4
+ case 0x09:
+ op = "s (relative smooth cubeTo)"
+ nCoords = 4
+ case 0x0a:
+ op = "C (absolute cubeTo)"
+ nCoords = 6
+ case 0x0b:
+ op = "c (relative cubeTo)"
+ nCoords = 6
+ case 0x0c:
+ op = "A (absolute arcTo)"
+ nCoords = 0
+ case 0x0d:
+ op = "a (relative arcTo)"
+ nCoords = 0
+ }
+
+ if p != nil {
+ p(src[:1], "%s, %d reps\n", op, nReps)
+ }
+ src = src[1:]
+
+ for i := 0; i < nReps; i++ {
+ if p != nil && i != 0 {
+ p(nil, "%s, implicit\n", op)
+ }
+ var largeArc, sweep bool
+ if op[0] != 'A' && op[0] != 'a' {
+ src, err = decodeCoordinates(coords[:nCoords], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ } else {
+ // We have an absolute or relative arcTo.
+ src, err = decodeCoordinates(coords[:2], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ coords[2], src, err = decodeAngle(p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ largeArc, sweep, src, err = decodeArcToFlags(p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ src, err = decodeCoordinates(coords[4:6], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ if dst == nil {
+ continue
+ }
+ switch op[0] {
+ case 'L':
+ dst.AbsLineTo(coords[0], coords[1])
+ case 'l':
+ dst.RelLineTo(coords[0], coords[1])
+ case 'T':
+ dst.AbsSmoothQuadTo(coords[0], coords[1])
+ case 't':
+ dst.RelSmoothQuadTo(coords[0], coords[1])
+ case 'Q':
+ dst.AbsQuadTo(coords[0], coords[1], coords[2], coords[3])
+ case 'q':
+ dst.RelQuadTo(coords[0], coords[1], coords[2], coords[3])
+ case 'S':
+ dst.AbsSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
+ case 's':
+ dst.RelSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
+ case 'C':
+ dst.AbsCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
+ case 'c':
+ dst.RelCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
+ case 'A':
+ dst.AbsArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
+ case 'a':
+ dst.RelArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
+ }
+ }
+
+ case opcode == 0xe1:
+ if p != nil {
+ p(src[:1], "z (closePath); end path\n")
+ }
+ src = src[1:]
+ if dst != nil {
+ dst.ClosePathEndPath()
+ }
+ return decodeStyling, src, nil
+
+ case opcode == 0xe2:
+ if p != nil {
+ p(src[:1], "z (closePath); M (absolute moveTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:2], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.ClosePathAbsMoveTo(coords[0], coords[1])
+ }
+
+ case opcode == 0xe3:
+ if p != nil {
+ p(src[:1], "z (closePath); m (relative moveTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:2], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.ClosePathRelMoveTo(coords[0], coords[1])
+ }
+
+ case opcode == 0xe6:
+ if p != nil {
+ p(src[:1], "H (absolute horizontal lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.AbsHLineTo(coords[0])
+ }
+
+ case opcode == 0xe7:
+ if p != nil {
+ p(src[:1], "h (relative horizontal lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.RelHLineTo(coords[0])
+ }
+
+ case opcode == 0xe8:
+ if p != nil {
+ p(src[:1], "V (absolute vertical lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.AbsVLineTo(coords[0])
+ }
+
+ case opcode == 0xe9:
+ if p != nil {
+ p(src[:1], "v (relative vertical lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.RelVLineTo(coords[0])
+ }
+
+ default:
+ return nil, nil, errUnsupportedDrawingOpcode
+ }
+ return decodeDrawing, src, nil
+}
+
+type decodeNumberFunc func(buffer) (float32, int)
+
+func decodeNumber(p printer, src buffer, dnf decodeNumberFunc) (float32, buffer, error) {
+ x, n := dnf(src)
+ if n == 0 {
+ return 0, nil, errInvalidNumber
+ }
+ if p != nil {
+ p(src[:n], " %+g\n", x)
+ }
+ return x, src[n:], nil
+}
+
+func decodeCoordinates(coords []float32, p printer, src buffer) (src1 buffer, err error) {
+ for i := range coords {
+ coords[i], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return src, nil
+}
+
+func decodeCoordinatePairs(coords [][2]float32, p printer, src buffer) (src1 buffer, err error) {
+ for i := range coords {
+ coords[i][0], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, err
+ }
+ coords[i][1], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return src, nil
+}
+
+func decodeAngle(p printer, src buffer) (float32, buffer, error) {
+ x, n := src.decodeZeroToOne()
+ if n == 0 {
+ return 0, nil, errInvalidNumber
+ }
+ if p != nil {
+ p(src[:n], " %v Γ— 360 degrees (%v degrees)\n", x, x*360)
+ }
+ return x, src[n:], nil
+}
+
+func decodeArcToFlags(p printer, src buffer) (bool, bool, buffer, error) {
+ x, n := src.decodeNatural()
+ if n == 0 {
+ return false, false, nil, errInvalidNumber
+ }
+ if p != nil {
+ p(src[:n], " %#x (largeArc=%d, sweep=%d)\n", x, (x>>0)&0x01, (x>>1)&0x01)
+ }
+ return (x>>0)&0x01 != 0, (x>>1)&0x01 != 0, src[n:], nil
+}
diff --git a/shiny/iconvg/decode_test.go b/shiny/iconvg/decode_test.go
new file mode 100644
index 0000000..33b0b7d
--- /dev/null
+++ b/shiny/iconvg/decode_test.go
@@ -0,0 +1,340 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "image/png"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+
+ "golang.org/x/image/math/f32"
+)
+
+// disassemble returns a disassembly of an encoded IconVG graphic. Users of
+// this package aren't expected to want to do this, so it lives in a _test.go
+// file, but it can be useful for debugging.
+func disassemble(src []byte) ([]byte, error) {
+ w := new(bytes.Buffer)
+ p := func(b []byte, format string, args ...interface{}) {
+ const hex = "0123456789abcdef"
+ var buf [14]byte
+ for i := range buf {
+ buf[i] = ' '
+ }
+ for i, x := range b {
+ buf[3*i+0] = hex[x>>4]
+ buf[3*i+1] = hex[x&0x0f]
+ }
+ w.Write(buf[:])
+ fmt.Fprintf(w, format, args...)
+ }
+ m := Metadata{}
+ if err := decode(nil, p, &m, false, buffer(src), nil); err != nil {
+ return nil, err
+ }
+ return w.Bytes(), nil
+}
+
+var (
+ _ Destination = (*Encoder)(nil)
+ _ Destination = (*Rasterizer)(nil)
+)
+
+func encodePNG(dstFilename string, src image.Image) error {
+ f, err := os.Create(dstFilename)
+ if err != nil {
+ return err
+ }
+ encErr := png.Encode(f, src)
+ closeErr := f.Close()
+ if encErr != nil {
+ return encErr
+ }
+ return closeErr
+}
+
+func decodePNG(srcFilename string) (image.Image, error) {
+ f, err := os.Open(srcFilename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return png.Decode(f)
+}
+
+func checkApproxEqual(m0, m1 image.Image) error {
+ diff := func(a, b uint32) uint32 {
+ if a < b {
+ return b - a
+ }
+ return a - b
+ }
+
+ bounds0 := m0.Bounds()
+ bounds1 := m1.Bounds()
+ if bounds0 != bounds1 {
+ return fmt.Errorf("bounds differ: got %v, want %v", bounds0, bounds1)
+ }
+ for y := bounds0.Min.Y; y < bounds0.Max.Y; y++ {
+ for x := bounds0.Min.X; x < bounds0.Max.X; x++ {
+ r0, g0, b0, a0 := m0.At(x, y).RGBA()
+ r1, g1, b1, a1 := m1.At(x, y).RGBA()
+
+ // TODO: be more principled in picking this magic threshold, other
+ // than what the difference is, in practice, in x/image/vector's
+ // fixed and floating point rasterizer?
+ const D = 0xffff * 12 / 100 // Diff threshold of 12%.
+
+ if diff(r0, r1) > D || diff(g0, g1) > D || diff(b0, b1) > D || diff(a0, a1) > D {
+ return fmt.Errorf("at (%d, %d):\n"+
+ "got RGBA %#04x, %#04x, %#04x, %#04x\n"+
+ "want RGBA %#04x, %#04x, %#04x, %#04x",
+ x, y, r0, g0, b0, a0, r1, g1, b1, a1)
+ }
+ }
+ }
+ return nil
+}
+
+func diffLines(t *testing.T, got, want string) {
+ gotLines := strings.Split(got, "\n")
+ wantLines := strings.Split(want, "\n")
+ for i := 1; ; i++ {
+ if len(gotLines) == 0 {
+ t.Errorf("line %d:\ngot %q\nwant %q", i, "", wantLines[0])
+ return
+ }
+ if len(wantLines) == 0 {
+ t.Errorf("line %d:\ngot %q\nwant %q", i, gotLines[0], "")
+ return
+ }
+ g, w := gotLines[0], wantLines[0]
+ gotLines = gotLines[1:]
+ wantLines = wantLines[1:]
+ if g != w {
+ t.Errorf("line %d:\ngot %q\nwant %q", i, g, w)
+ return
+ }
+ }
+}
+
+var testdataTestCases = []struct {
+ filename string
+ variants string
+}{
+ {"testdata/action-info.lores", ""},
+ {"testdata/action-info.hires", ""},
+ {"testdata/arcs", ""},
+ {"testdata/blank", ""},
+ {"testdata/cowbell", ""},
+ {"testdata/elliptical", ""},
+ {"testdata/favicon", ";pink"},
+ {"testdata/gradient", ""},
+ {"testdata/lod-polygon", ";64"},
+ {"testdata/video-005.primitive", ""},
+}
+
+func TestDisassembly(t *testing.T) {
+ for _, tc := range testdataTestCases {
+ ivgData, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
+ if err != nil {
+ t.Errorf("%s: ReadFile: %v", tc.filename, err)
+ continue
+ }
+ got, err := disassemble(ivgData)
+ if err != nil {
+ t.Errorf("%s: disassemble: %v", tc.filename, err)
+ continue
+ }
+ wantFilename := filepath.FromSlash(tc.filename) + ".ivg.disassembly"
+ if *updateFlag {
+ if err := os.WriteFile(filepath.FromSlash(wantFilename), got, 0666); err != nil {
+ t.Errorf("%s: WriteFile: %v", tc.filename, err)
+ }
+ continue
+ }
+ want, err := os.ReadFile(wantFilename)
+ if err != nil {
+ t.Errorf("%s: ReadFile: %v", tc.filename, err)
+ continue
+ }
+ if !bytes.Equal(got, want) {
+ t.Errorf("%s: got:\n%s\nwant:\n%s", tc.filename, got, want)
+ diffLines(t, string(got), string(want))
+ }
+ }
+}
+
+// The IconVG decoder and encoder are expected to be completely deterministic,
+// so check that we get the original bytes after a decode + encode round-trip.
+func TestDecodeEncodeRoundTrip(t *testing.T) {
+ for _, tc := range testdataTestCases {
+ ivgData, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
+ if err != nil {
+ t.Errorf("%s: ReadFile: %v", tc.filename, err)
+ continue
+ }
+ var e resolutionPreservingEncoder
+ e.HighResolutionCoordinates = strings.HasSuffix(tc.filename, ".hires")
+ if err := Decode(&e, ivgData, nil); err != nil {
+ t.Errorf("%s: Decode: %v", tc.filename, err)
+ continue
+ }
+ got, err := e.Bytes()
+ if err != nil {
+ t.Errorf("%s: Encoder.Bytes: %v", tc.filename, err)
+ continue
+ }
+ if want := ivgData; !bytes.Equal(got, want) {
+ t.Errorf("%s:\ngot %d bytes (on GOOS=%s GOARCH=%s, using compiler %q):\n% x\nwant %d bytes:\n% x",
+ tc.filename, len(got), runtime.GOOS, runtime.GOARCH, runtime.Compiler, got, len(want), want)
+ gotDisasm, err1 := disassemble(got)
+ wantDisasm, err2 := disassemble(want)
+ if err1 == nil && err2 == nil {
+ diffLines(t, string(gotDisasm), string(wantDisasm))
+ }
+ }
+ }
+}
+
+// resolutionPreservingEncoder is an Encoder
+// whose Reset method keeps prior resolution.
+type resolutionPreservingEncoder struct {
+ Encoder
+}
+
+// Reset resets the Encoder for the given Metadata.
+//
+// Unlike Encoder.Reset, it leaves the value
+// of e.HighResolutionCoordinates unmodified.
+func (e *resolutionPreservingEncoder) Reset(m Metadata) {
+ orig := e.HighResolutionCoordinates
+ e.Encoder.Reset(m)
+ e.HighResolutionCoordinates = orig
+}
+
+func TestDecodeAndRasterize(t *testing.T) {
+ for _, tc := range testdataTestCases {
+ ivgData, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
+ if err != nil {
+ t.Errorf("%s: ReadFile: %v", tc.filename, err)
+ continue
+ }
+ md, err := DecodeMetadata(ivgData)
+ if err != nil {
+ t.Errorf("%s: DecodeMetadata: %v", tc.filename, err)
+ continue
+ }
+
+ for _, variant := range strings.Split(tc.variants, ";") {
+ length := 256
+ if variant == "64" {
+ length = 64
+ }
+ width, height := length, length
+ if dx, dy := md.ViewBox.AspectRatio(); dx < dy {
+ width = int(float32(length) * dx / dy)
+ } else {
+ height = int(float32(length) * dy / dx)
+ }
+
+ opts := &DecodeOptions{}
+ if variant == "pink" {
+ pal := DefaultPalette
+ pal[0] = color.RGBA{0xfe, 0x76, 0xea, 0xff}
+ opts.Palette = &pal
+ }
+
+ got := image.NewRGBA(image.Rect(0, 0, width, height))
+ var z Rasterizer
+ z.SetDstImage(got, got.Bounds(), draw.Src)
+ if err := Decode(&z, ivgData, opts); err != nil {
+ t.Errorf("%s %q variant: Decode: %v", tc.filename, variant, err)
+ continue
+ }
+
+ wantFilename := filepath.FromSlash(tc.filename)
+ if variant != "" {
+ wantFilename += "." + variant
+ }
+ wantFilename += ".png"
+ if *updateFlag {
+ if err := encodePNG(filepath.FromSlash(wantFilename), got); err != nil {
+ t.Errorf("%s %q variant: encodePNG: %v", tc.filename, variant, err)
+ }
+ continue
+ }
+ want, err := decodePNG(wantFilename)
+ if err != nil {
+ t.Errorf("%s %q variant: decodePNG: %v", tc.filename, variant, err)
+ continue
+ }
+ if err := checkApproxEqual(got, want); err != nil {
+ t.Errorf("%s %q variant: %v", tc.filename, variant, err)
+ continue
+ }
+ }
+ }
+}
+
+func TestInvalidAlphaPremultipliedColor(t *testing.T) {
+ // See http://golang.org/issue/39526 for some discussion.
+
+ dst := image.NewRGBA(image.Rect(0, 0, 1, 1))
+ var z Rasterizer
+ z.SetDstImage(dst, dst.Bounds(), draw.Over)
+ z.Reset(Metadata{
+ ViewBox: Rectangle{
+ Min: f32.Vec2{0.0, 0.0},
+ Max: f32.Vec2{1.0, 1.0},
+ },
+ })
+
+ // Fill the unit square with red.
+ z.SetCReg(0, false, RGBAColor(color.RGBA{0x55, 0x00, 0x00, 0x66}))
+ z.StartPath(0, 0.0, 0.0)
+ z.AbsLineTo(1.0, 0.0)
+ z.AbsLineTo(1.0, 1.0)
+ z.AbsLineTo(0.0, 1.0)
+ z.ClosePathEndPath()
+
+ // Fill the unit square with an invalid (non-gradient) alpha-premultiplied
+ // color (super-saturated green). This should be a no-op (and not crash).
+ z.SetCReg(0, false, RGBAColor(color.RGBA{0x00, 0x99, 0x00, 0x88}))
+ z.StartPath(0, 0.0, 0.0)
+ z.AbsLineTo(1.0, 0.0)
+ z.AbsLineTo(1.0, 1.0)
+ z.AbsLineTo(0.0, 1.0)
+ z.ClosePathEndPath()
+
+ // We should see red.
+ got := dst.Pix
+ want := []byte{0x55, 0x00, 0x00, 0x66}
+ if !bytes.Equal(got, want) {
+ t.Errorf("got [% 02x], want [% 02x]", got, want)
+ }
+}
+
+func TestBlendColor(t *testing.T) {
+ // This example comes from doc.go. Look for "orange" in the "Colors"
+ // section.
+ pal := Palette{
+ 2: color.RGBA{0xff, 0xcc, 0x80, 0xff}, // "Material Design Orange 200".
+ }
+ cReg := [64]color.RGBA{}
+ got := BlendColor(0x40, 0x7f, 0x82).Resolve(&pal, &cReg)
+ want := color.RGBA{0x40, 0x33, 0x20, 0x40} // 25% opaque "Orange 200", alpha-premultiplied.
+ if got != want {
+ t.Errorf("\ngot %x\nwant %x", got, want)
+ }
+}
diff --git a/shiny/iconvg/doc.go b/shiny/iconvg/doc.go
new file mode 100644
index 0000000..559b040
--- /dev/null
+++ b/shiny/iconvg/doc.go
@@ -0,0 +1,31 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package iconvg implements a compact, binary format for simple vector graphics:
+icons, logos, glyphs and emoji.
+
+WARNING: THIS FORMAT IS EXPERIMENTAL AND SUBJECT TO INCOMPATIBLE CHANGES.
+
+A longer overview is at
+https://github.com/google/iconvg
+
+The file format is specified at
+https://github.com/google/iconvg/blob/main/spec/iconvg-spec.md
+
+This package's encoder emits byte-identical output for the same input,
+independent of the platform (and specifically its floating-point hardware).
+*/
+package iconvg
+
+// TODO: shapes (circles, rects) and strokes? Or can we assume that authoring
+// tools will convert shapes and strokes to paths?
+
+// TODO: mark somehow that a graphic (such as a back arrow) should be flipped
+// horizontally or its paths otherwise varied when presented in a Right-To-Left
+// context, such as among Arabic and Hebrew text? Or should that be the
+// responsibility of higher layers, selecting different IconVG graphics based
+// on context, the way they would select different PNG graphics.
+
+// TODO: hinting?
diff --git a/shiny/iconvg/encode.go b/shiny/iconvg/encode.go
new file mode 100644
index 0000000..3cf6360
--- /dev/null
+++ b/shiny/iconvg/encode.go
@@ -0,0 +1,605 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "errors"
+ "image/color"
+ "math"
+
+ "golang.org/x/image/math/f32"
+)
+
+var (
+ errCSELUsedAsBothGradientAndStop = errors.New("iconvg: CSEL used as both gradient and stop")
+ errDrawingOpsUsedInStylingMode = errors.New("iconvg: drawing ops used in styling mode")
+ errInvalidSelectorAdjustment = errors.New("iconvg: invalid selector adjustment")
+ errInvalidIncrementingAdjustment = errors.New("iconvg: invalid incrementing adjustment")
+ errStylingOpsUsedInDrawingMode = errors.New("iconvg: styling ops used in drawing mode")
+ errTooManyGradientStops = errors.New("iconvg: too many gradient stops")
+)
+
+type mode uint8
+
+const (
+ modeInitial mode = iota
+ modeStyling
+ modeDrawing
+)
+
+// Encoder is an IconVG encoder.
+//
+// The zero value is usable. Calling Reset, which is optional, sets the
+// Metadata for the subsequent encoded form. If Reset is not called before
+// other Encoder methods, the default metadata is implied.
+//
+// It aims to emit byte-identical Bytes output for the same input, independent
+// of the platform (and specifically its floating-point hardware).
+type Encoder struct {
+ // HighResolutionCoordinates is whether the encoder should encode
+ // coordinate numbers for subsequent paths at the best possible resolution
+ // afforded by the underlying graphic format.
+ //
+ // By default (false), the encoder quantizes coordinates to 1/64th of a
+ // unit if possible (the default graphic size is 64 by 64 units, so
+ // 1/4096th of the default width or height). Each such coordinate can
+ // therefore be encoded in either 1 or 2 bytes. If true, some coordinates
+ // will be encoded in 4 bytes, giving greater accuracy but larger file
+ // sizes. On the Material Design icon set, the 950 or so icons take up
+ // around 40% more bytes (172K vs 123K) at high resolution.
+ //
+ // See the package documentation for more details on the coordinate number
+ // encoding format.
+ HighResolutionCoordinates bool
+
+ // highResolutionCoordinates is a local copy, copied during StartPath, to
+ // avoid having to specify the semantics of modifying the exported field
+ // while drawing.
+ highResolutionCoordinates bool
+
+ buf buffer
+ altBuf buffer
+ metadata Metadata
+ err error
+
+ lod0 float32
+ lod1 float32
+ cSel uint8
+ nSel uint8
+
+ mode mode
+ drawOp byte
+ drawArgs []float32
+
+ scratch [12]byte
+}
+
+// Bytes returns the encoded form.
+func (e *Encoder) Bytes() ([]byte, error) {
+ if e.err != nil {
+ return nil, e.err
+ }
+ if e.mode == modeInitial {
+ e.appendDefaultMetadata()
+ }
+ return []byte(e.buf), nil
+}
+
+// Reset resets the Encoder for the given Metadata.
+//
+// This includes setting e.HighResolutionCoordinates to false.
+func (e *Encoder) Reset(m Metadata) {
+ *e = Encoder{
+ buf: append(e.buf[:0], magic...),
+ metadata: m,
+ mode: modeStyling,
+ lod1: positiveInfinity,
+ }
+
+ nMetadataChunks := 0
+ mcViewBox := m.ViewBox != DefaultViewBox
+ if mcViewBox {
+ nMetadataChunks++
+ }
+ mcSuggestedPalette := m.Palette != DefaultPalette
+ if mcSuggestedPalette {
+ nMetadataChunks++
+ }
+ e.buf.encodeNatural(uint32(nMetadataChunks))
+
+ if mcViewBox {
+ e.altBuf = e.altBuf[:0]
+ e.altBuf.encodeNatural(midViewBox)
+ e.altBuf.encodeCoordinate(m.ViewBox.Min[0])
+ e.altBuf.encodeCoordinate(m.ViewBox.Min[1])
+ e.altBuf.encodeCoordinate(m.ViewBox.Max[0])
+ e.altBuf.encodeCoordinate(m.ViewBox.Max[1])
+
+ e.buf.encodeNatural(uint32(len(e.altBuf)))
+ e.buf = append(e.buf, e.altBuf...)
+ }
+
+ if mcSuggestedPalette {
+ n := 63
+ for ; n >= 0 && m.Palette[n] == (color.RGBA{0x00, 0x00, 0x00, 0xff}); n-- {
+ }
+
+ // Find the shortest encoding that can represent all of m.Palette's n+1
+ // explicit colors.
+ enc1, enc2, enc3 := true, true, true
+ for _, c := range m.Palette[:n+1] {
+ if enc1 && (!is1(c.R) || !is1(c.G) || !is1(c.B) || !is1(c.A)) {
+ enc1 = false
+ }
+ if enc2 && (!is2(c.R) || !is2(c.G) || !is2(c.B) || !is2(c.A)) {
+ enc2 = false
+ }
+ if enc3 && (c.A != 0xff) {
+ enc3 = false
+ }
+ }
+
+ e.altBuf = e.altBuf[:0]
+ e.altBuf.encodeNatural(midSuggestedPalette)
+ if enc1 {
+ e.altBuf = append(e.altBuf, byte(n)|0x00)
+ for _, c := range m.Palette[:n+1] {
+ x, _ := encodeColor1(RGBAColor(c))
+ e.altBuf = append(e.altBuf, x)
+ }
+ } else if enc2 {
+ e.altBuf = append(e.altBuf, byte(n)|0x40)
+ for _, c := range m.Palette[:n+1] {
+ x, _ := encodeColor2(RGBAColor(c))
+ e.altBuf = append(e.altBuf, x[0], x[1])
+ }
+ } else if enc3 {
+ e.altBuf = append(e.altBuf, byte(n)|0x80)
+ for _, c := range m.Palette[:n+1] {
+ e.altBuf = append(e.altBuf, c.R, c.G, c.B)
+ }
+ } else {
+ e.altBuf = append(e.altBuf, byte(n)|0xc0)
+ for _, c := range m.Palette[:n+1] {
+ e.altBuf = append(e.altBuf, c.R, c.G, c.B, c.A)
+ }
+ }
+
+ e.buf.encodeNatural(uint32(len(e.altBuf)))
+ e.buf = append(e.buf, e.altBuf...)
+ }
+}
+
+func (e *Encoder) appendDefaultMetadata() {
+ e.buf = append(e.buf[:0], magic...)
+ e.buf = append(e.buf, 0x00) // There are zero metadata chunks.
+ e.mode = modeStyling
+}
+
+func (e *Encoder) CSel() uint8 {
+ if e.mode == modeInitial {
+ e.appendDefaultMetadata()
+ }
+ return e.cSel
+}
+
+func (e *Encoder) NSel() uint8 {
+ if e.mode == modeInitial {
+ e.appendDefaultMetadata()
+ }
+ return e.nSel
+}
+
+func (e *Encoder) LOD() (lod0, lod1 float32) {
+ if e.mode == modeInitial {
+ e.appendDefaultMetadata()
+ }
+ return e.lod0, e.lod1
+}
+
+func (e *Encoder) checkModeStyling() {
+ if e.mode == modeStyling {
+ return
+ }
+ if e.mode == modeInitial {
+ e.appendDefaultMetadata()
+ return
+ }
+ e.err = errStylingOpsUsedInDrawingMode
+}
+
+func (e *Encoder) SetCSel(cSel uint8) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ e.cSel = cSel & 0x3f
+ e.buf = append(e.buf, e.cSel)
+}
+
+func (e *Encoder) SetNSel(nSel uint8) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ e.nSel = nSel & 0x3f
+ e.buf = append(e.buf, e.nSel|0x40)
+}
+
+func (e *Encoder) SetCReg(adj uint8, incr bool, c Color) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ if adj > 6 {
+ e.err = errInvalidSelectorAdjustment
+ return
+ }
+ if incr {
+ if adj != 0 {
+ e.err = errInvalidIncrementingAdjustment
+ }
+ adj = 7
+ }
+
+ if x, ok := encodeColor1(c); ok {
+ e.buf = append(e.buf, adj|0x80, x)
+ return
+ }
+ if x, ok := encodeColor2(c); ok {
+ e.buf = append(e.buf, adj|0x88, x[0], x[1])
+ return
+ }
+ if x, ok := encodeColor3Direct(c); ok {
+ e.buf = append(e.buf, adj|0x90, x[0], x[1], x[2])
+ return
+ }
+ if x, ok := encodeColor4(c); ok {
+ e.buf = append(e.buf, adj|0x98, x[0], x[1], x[2], x[3])
+ return
+ }
+ if x, ok := encodeColor3Indirect(c); ok {
+ e.buf = append(e.buf, adj|0xa0, x[0], x[1], x[2])
+ return
+ }
+ panic("unreachable")
+}
+
+func (e *Encoder) SetNReg(adj uint8, incr bool, f float32) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ if adj > 6 {
+ e.err = errInvalidSelectorAdjustment
+ return
+ }
+ if incr {
+ if adj != 0 {
+ e.err = errInvalidIncrementingAdjustment
+ }
+ adj = 7
+ }
+
+ // Try three different encodings and pick the shortest.
+ b := buffer(e.scratch[0:0])
+ opcode, iBest, nBest := uint8(0xa8), 0, b.encodeReal(f)
+
+ b = buffer(e.scratch[4:4])
+ if n := b.encodeCoordinate(f); n < nBest {
+ opcode, iBest, nBest = 0xb0, 4, n
+ }
+
+ b = buffer(e.scratch[8:8])
+ if n := b.encodeZeroToOne(f); n < nBest {
+ opcode, iBest, nBest = 0xb8, 8, n
+ }
+
+ e.buf = append(e.buf, adj|opcode)
+ e.buf = append(e.buf, e.scratch[iBest:iBest+nBest]...)
+}
+
+func (e *Encoder) SetLOD(lod0, lod1 float32) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ e.lod0 = lod0
+ e.lod1 = lod1
+ e.buf = append(e.buf, 0xc7)
+ e.buf.encodeReal(lod0)
+ e.buf.encodeReal(lod1)
+}
+
+// SetGradient sets CREG[CSEL] to encode the gradient whose colors defined by
+// spread and stops. Its geometry is either linear or radial, depending on the
+// radial argument, and the given affine transformation matrix maps from
+// graphic coordinate space defined by the metadata's viewBox (e.g. from (-32,
+// -32) to (+32, +32)) to gradient coordinate space. Gradient coordinate space
+// is where a linear gradient ranges from x=0 to x=1, and a radial gradient has
+// center (0, 0) and radius 1.
+//
+// The colors of the n stops are encoded at CREG[cBase+0], CREG[cBase+1], ...,
+// CREG[cBase+n-1]. Similarly, the offsets of the n stops are encoded at
+// NREG[nBase+0], NREG[nBase+1], ..., NREG[nBase+n-1]. Additional parameters
+// are stored at NREG[nBase-4], NREG[nBase-3], NREG[nBase-2] and NREG[nBase-1].
+//
+// The CSEL and NSEL selector registers maintain the same values after the
+// method returns as they had when the method was called.
+//
+// See the package documentation for more details on the gradient encoding
+// format and the derivation of common transformation matrices.
+func (e *Encoder) SetGradient(cBase, nBase uint8, radial bool, transform f32.Aff3, spread GradientSpread, stops []GradientStop) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ if len(stops) > 64-len(transform) {
+ e.err = errTooManyGradientStops
+ return
+ }
+ if x, y := e.cSel, e.cSel+64; (cBase <= x && x < cBase+uint8(len(stops))) ||
+ (cBase <= y && y < cBase+uint8(len(stops))) {
+ e.err = errCSELUsedAsBothGradientAndStop
+ return
+ }
+
+ oldCSel := e.cSel
+ oldNSel := e.nSel
+ cBase &= 0x3f
+ nBase &= 0x3f
+ bFlags := uint8(0x80)
+ if radial {
+ bFlags = 0xc0
+ }
+ e.SetCReg(0, false, RGBAColor(color.RGBA{
+ R: uint8(len(stops)),
+ G: cBase | uint8(spread<<6),
+ B: nBase | bFlags,
+ A: 0x00,
+ }))
+ e.SetCSel(cBase)
+ e.SetNSel(nBase)
+ for i, v := range transform {
+ e.SetNReg(uint8(len(transform)-i), false, v)
+ }
+ for _, s := range stops {
+ r, g, b, a := s.Color.RGBA()
+ e.SetCReg(0, true, RGBAColor(color.RGBA{
+ R: uint8(r >> 8),
+ G: uint8(g >> 8),
+ B: uint8(b >> 8),
+ A: uint8(a >> 8),
+ }))
+ e.SetNReg(0, true, s.Offset)
+ }
+ e.SetCSel(oldCSel)
+ e.SetNSel(oldNSel)
+}
+
+// SetLinearGradient is like SetGradient with radial=false except that the
+// transformation matrix is implicitly defined by two boundary points (x1, y1)
+// and (x2, y2).
+func (e *Encoder) SetLinearGradient(cBase, nBase uint8, x1, y1, x2, y2 float32, spread GradientSpread, stops []GradientStop) {
+ // See the package documentation's appendix for a derivation of the
+ // transformation matrix.
+ dx, dy := x2-x1, y2-y1
+ d := dx*dx + dy*dy
+ ma := dx / d
+ mb := dy / d
+ e.SetGradient(cBase, nBase, false, f32.Aff3{
+ ma, mb, -ma*x1 - mb*y1,
+ 0, 0, 0,
+ }, spread, stops)
+}
+
+// SetCircularGradient is like SetGradient with radial=true except that the
+// transformation matrix is implicitly defined by a center (cx, cy) and a
+// radius vector (rx, ry) such that (cx+rx, cy+ry) is on the circle.
+func (e *Encoder) SetCircularGradient(cBase, nBase uint8, cx, cy, rx, ry float32, spread GradientSpread, stops []GradientStop) {
+ // See the package documentation's appendix for a derivation of the
+ // transformation matrix.
+ invR := float32(1 / math.Sqrt(float64(rx*rx+ry*ry)))
+ e.SetGradient(cBase, nBase, true, f32.Aff3{
+ invR, 0, -cx * invR,
+ 0, invR, -cy * invR,
+ }, spread, stops)
+}
+
+// SetEllipticalGradient is like SetGradient with radial=true except that the
+// transformation matrix is implicitly defined by a center (cx, cy) and two
+// axis vectors (rx, ry) and (sx, sy) such that (cx+rx, cy+ry) and (cx+sx,
+// cy+sy) are on the ellipse.
+func (e *Encoder) SetEllipticalGradient(cBase, nBase uint8, cx, cy, rx, ry, sx, sy float32, spread GradientSpread, stops []GradientStop) {
+ // Explicitly disable FMA in the floating-point calculations below
+ // to get consistent results on all platforms, and in turn produce
+ // a byte-identical encoding.
+ // See https://golang.org/ref/spec#Floating_point_operators and issue 43219.
+
+ // See the package documentation's appendix for a derivation of the
+ // transformation matrix.
+ invRSSR := 1 / (float32(rx*sy) - float32(sx*ry))
+
+ ma := +sy * invRSSR
+ mb := -sx * invRSSR
+ mc := -float32(ma*cx) - float32(mb*cy)
+ md := -ry * invRSSR
+ me := +rx * invRSSR
+ mf := -float32(md*cx) - float32(me*cy)
+
+ e.SetGradient(cBase, nBase, true, f32.Aff3{
+ ma, mb, mc,
+ md, me, mf,
+ }, spread, stops)
+}
+
+func (e *Encoder) StartPath(adj uint8, x, y float32) {
+ e.checkModeStyling()
+ if e.err != nil {
+ return
+ }
+ if adj > 6 {
+ e.err = errInvalidSelectorAdjustment
+ return
+ }
+ e.highResolutionCoordinates = e.HighResolutionCoordinates
+ e.buf = append(e.buf, uint8(0xc0+adj))
+ e.buf.encodeCoordinate(quantize(x, e.highResolutionCoordinates))
+ e.buf.encodeCoordinate(quantize(y, e.highResolutionCoordinates))
+ e.mode = modeDrawing
+}
+
+func (e *Encoder) AbsHLineTo(x float32) { e.draw('H', x, 0, 0, 0, 0, 0) }
+func (e *Encoder) RelHLineTo(x float32) { e.draw('h', x, 0, 0, 0, 0, 0) }
+func (e *Encoder) AbsVLineTo(y float32) { e.draw('V', y, 0, 0, 0, 0, 0) }
+func (e *Encoder) RelVLineTo(y float32) { e.draw('v', y, 0, 0, 0, 0, 0) }
+func (e *Encoder) AbsLineTo(x, y float32) { e.draw('L', x, y, 0, 0, 0, 0) }
+func (e *Encoder) RelLineTo(x, y float32) { e.draw('l', x, y, 0, 0, 0, 0) }
+func (e *Encoder) AbsSmoothQuadTo(x, y float32) { e.draw('T', x, y, 0, 0, 0, 0) }
+func (e *Encoder) RelSmoothQuadTo(x, y float32) { e.draw('t', x, y, 0, 0, 0, 0) }
+func (e *Encoder) AbsQuadTo(x1, y1, x, y float32) { e.draw('Q', x1, y1, x, y, 0, 0) }
+func (e *Encoder) RelQuadTo(x1, y1, x, y float32) { e.draw('q', x1, y1, x, y, 0, 0) }
+func (e *Encoder) AbsSmoothCubeTo(x2, y2, x, y float32) { e.draw('S', x2, y2, x, y, 0, 0) }
+func (e *Encoder) RelSmoothCubeTo(x2, y2, x, y float32) { e.draw('s', x2, y2, x, y, 0, 0) }
+func (e *Encoder) AbsCubeTo(x1, y1, x2, y2, x, y float32) { e.draw('C', x1, y1, x2, y2, x, y) }
+func (e *Encoder) RelCubeTo(x1, y1, x2, y2, x, y float32) { e.draw('c', x1, y1, x2, y2, x, y) }
+func (e *Encoder) ClosePathEndPath() { e.draw('Z', 0, 0, 0, 0, 0, 0) }
+func (e *Encoder) ClosePathAbsMoveTo(x, y float32) { e.draw('Y', x, y, 0, 0, 0, 0) }
+func (e *Encoder) ClosePathRelMoveTo(x, y float32) { e.draw('y', x, y, 0, 0, 0, 0) }
+
+func (e *Encoder) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ e.arcTo('A', rx, ry, xAxisRotation, largeArc, sweep, x, y)
+}
+
+func (e *Encoder) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ e.arcTo('a', rx, ry, xAxisRotation, largeArc, sweep, x, y)
+}
+
+func (e *Encoder) arcTo(drawOp byte, rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ flags := uint32(0)
+ if largeArc {
+ flags |= 0x01
+ }
+ if sweep {
+ flags |= 0x02
+ }
+ e.draw(drawOp, rx, ry, xAxisRotation, float32(flags), x, y)
+}
+
+func (e *Encoder) draw(drawOp byte, arg0, arg1, arg2, arg3, arg4, arg5 float32) {
+ if e.err != nil {
+ return
+ }
+ if e.mode != modeDrawing {
+ e.err = errDrawingOpsUsedInStylingMode
+ return
+ }
+ if e.drawOp != drawOp {
+ e.flushDrawOps()
+ }
+ e.drawOp = drawOp
+ switch drawOps[drawOp].nArgs {
+ case 0:
+ // No-op.
+ case 1:
+ e.drawArgs = append(e.drawArgs, arg0)
+ case 2:
+ e.drawArgs = append(e.drawArgs, arg0, arg1)
+ case 4:
+ e.drawArgs = append(e.drawArgs, arg0, arg1, arg2, arg3)
+ case 6:
+ e.drawArgs = append(e.drawArgs, arg0, arg1, arg2, arg3, arg4, arg5)
+ default:
+ panic("unreachable")
+ }
+
+ switch drawOp {
+ case 'Z':
+ e.mode = modeStyling
+ fallthrough
+ case 'Y', 'y':
+ e.flushDrawOps()
+ }
+}
+
+func (e *Encoder) flushDrawOps() {
+ if e.drawOp == 0x00 {
+ return
+ }
+
+ if op := drawOps[e.drawOp]; op.nArgs == 0 {
+ e.buf = append(e.buf, op.opcodeBase)
+ } else {
+ n := len(e.drawArgs) / int(op.nArgs)
+ for i := 0; n > 0; {
+ m := n
+ if m > int(op.maxRepCount) {
+ m = int(op.maxRepCount)
+ }
+ e.buf = append(e.buf, op.opcodeBase+uint8(m)-1)
+
+ switch e.drawOp {
+ default:
+ for j := m * int(op.nArgs); j > 0; j-- {
+ e.buf.encodeCoordinate(quantize(e.drawArgs[i], e.highResolutionCoordinates))
+ i++
+ }
+ case 'A', 'a':
+ for j := m; j > 0; j-- {
+ e.buf.encodeCoordinate(quantize(e.drawArgs[i+0], e.highResolutionCoordinates))
+ e.buf.encodeCoordinate(quantize(e.drawArgs[i+1], e.highResolutionCoordinates))
+ e.buf.encodeAngle(e.drawArgs[i+2])
+ e.buf.encodeNatural(uint32(e.drawArgs[i+3]))
+ e.buf.encodeCoordinate(quantize(e.drawArgs[i+4], e.highResolutionCoordinates))
+ e.buf.encodeCoordinate(quantize(e.drawArgs[i+5], e.highResolutionCoordinates))
+ i += 6
+ }
+ }
+
+ n -= m
+ }
+ }
+
+ e.drawOp = 0x00
+ e.drawArgs = e.drawArgs[:0]
+}
+
+func quantize(coord float32, highResolutionCoordinates bool) float32 {
+ if !highResolutionCoordinates && (-128 <= coord && coord < 128) {
+ x := math.Floor(float64(coord*64 + 0.5))
+ return float32(x) / 64
+ }
+ return coord
+}
+
+var drawOps = [256]struct {
+ opcodeBase byte
+ maxRepCount uint8
+ nArgs uint8
+}{
+ 'L': {0x00, 32, 2},
+ 'l': {0x20, 32, 2},
+ 'T': {0x40, 16, 2},
+ 't': {0x50, 16, 2},
+ 'Q': {0x60, 16, 4},
+ 'q': {0x70, 16, 4},
+ 'S': {0x80, 16, 4},
+ 's': {0x90, 16, 4},
+ 'C': {0xa0, 16, 6},
+ 'c': {0xb0, 16, 6},
+ 'A': {0xc0, 16, 6},
+ 'a': {0xd0, 16, 6},
+
+ // Z means close path and then end path.
+ 'Z': {0xe1, 1, 0},
+ // Y/y means close path and then open a new path (with a MoveTo/moveTo).
+ 'Y': {0xe2, 1, 2},
+ 'y': {0xe3, 1, 2},
+
+ 'H': {0xe6, 1, 1},
+ 'h': {0xe7, 1, 1},
+ 'V': {0xe8, 1, 1},
+ 'v': {0xe9, 1, 1},
+}
diff --git a/shiny/iconvg/encode_test.go b/shiny/iconvg/encode_test.go
new file mode 100644
index 0000000..57aa451
--- /dev/null
+++ b/shiny/iconvg/encode_test.go
@@ -0,0 +1,708 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "bytes"
+ "flag"
+ "image/color"
+ "math"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "testing"
+
+ "golang.org/x/image/math/f32"
+)
+
+// updateFlag controls whether to overwrite testdata files during tests.
+// This can be useful when adding new testdataTestCases.
+var updateFlag = flag.Bool("update", false, "Overwrite testdata files.")
+
+func testEncode(t *testing.T, e *Encoder, wantFilename string) {
+ got, err := e.Bytes()
+ if err != nil {
+ t.Fatalf("encoding: %v", err)
+ }
+ if *updateFlag {
+ if err := os.WriteFile(filepath.FromSlash(wantFilename), got, 0666); err != nil {
+ t.Fatalf("WriteFile: %v", err)
+ }
+ return
+ }
+ want, err := os.ReadFile(filepath.FromSlash(wantFilename))
+ if err != nil {
+ t.Fatalf("ReadFile: %v", err)
+ }
+ if !bytes.Equal(got, want) {
+ // The IconVG encoder is expected to be completely deterministic across all
+ // platforms and Go compilers, so check that we get exactly the right bytes.
+ //
+ // If we get slightly different bytes on some supported platform (for example,
+ // a new GOOS/GOARCH port, or a different but spec-compliant Go compiler) due
+ // to non-determinism in floating-point math, the encoder needs to be fixed.
+ //
+ // See golang.org/issue/43219#issuecomment-748531069.
+ t.Errorf("\ngot %d bytes (on GOOS=%s GOARCH=%s, using compiler %q):\n% x\nwant %d bytes:\n% x",
+ len(got), runtime.GOOS, runtime.GOARCH, runtime.Compiler, got, len(want), want)
+ gotDisasm, err1 := disassemble(got)
+ wantDisasm, err2 := disassemble(want)
+ if err1 == nil && err2 == nil {
+ diffLines(t, string(gotDisasm), string(wantDisasm))
+ }
+ }
+}
+
+func TestEncodeActionInfo(t *testing.T) {
+ for _, res := range []string{"lores", "hires"} {
+ var e Encoder
+ e.Reset(Metadata{
+ ViewBox: Rectangle{
+ Min: f32.Vec2{-24, -24},
+ Max: f32.Vec2{+24, +24},
+ },
+ Palette: DefaultPalette,
+ })
+ e.HighResolutionCoordinates = res == "hires"
+
+ e.StartPath(0, 0, -20)
+ e.AbsCubeTo(-11.05, -20, -20, -11.05, -20, 0)
+ e.RelSmoothCubeTo(8.95, 20, 20, 20)
+ e.RelSmoothCubeTo(20, -8.95, 20, -20)
+ e.AbsSmoothCubeTo(11.05, -20, 0, -20)
+ e.ClosePathRelMoveTo(2, 30)
+ e.RelHLineTo(-4)
+ e.AbsVLineTo(-2)
+ e.RelHLineTo(4)
+ e.RelVLineTo(12)
+ e.ClosePathRelMoveTo(0, -16)
+ e.RelHLineTo(-4)
+ e.RelVLineTo(-4)
+ e.RelHLineTo(4)
+ e.RelVLineTo(4)
+ e.ClosePathEndPath()
+
+ testEncode(t, &e, "testdata/action-info."+res+".ivg")
+ }
+}
+
+func TestEncodeArcs(t *testing.T) {
+ var e Encoder
+
+ e.SetCReg(1, false, RGBAColor(color.RGBA{0xff, 0x00, 0x00, 0xff}))
+ e.SetCReg(2, false, RGBAColor(color.RGBA{0xff, 0xff, 0x00, 0xff}))
+ e.SetCReg(3, false, RGBAColor(color.RGBA{0x00, 0x00, 0x00, 0xff}))
+ e.SetCReg(4, false, RGBAColor(color.RGBA{0x00, 0x00, 0x80, 0xff}))
+
+ e.StartPath(1, -10, 0)
+ e.RelHLineTo(-15)
+ e.RelArcTo(15, 15, 0, true, false, 15, -15)
+ e.ClosePathEndPath()
+
+ e.StartPath(2, -14, -4)
+ e.RelVLineTo(-15)
+ e.RelArcTo(15, 15, 0, false, false, -15, 15)
+ e.ClosePathEndPath()
+
+ const thirtyDegrees = 30.0 / 360
+ e.StartPath(3, -15, 30)
+ e.RelLineTo(5.0, -2.5)
+ e.RelArcTo(2.5, 2.5, -thirtyDegrees, false, true, 5.0, -2.5)
+ e.RelLineTo(5.0, -2.5)
+ e.RelArcTo(2.5, 5.0, -thirtyDegrees, false, true, 5.0, -2.5)
+ e.RelLineTo(5.0, -2.5)
+ e.RelArcTo(2.5, 7.5, -thirtyDegrees, false, true, 5.0, -2.5)
+ e.RelLineTo(5.0, -2.5)
+ e.RelArcTo(2.5, 10.0, -thirtyDegrees, false, true, 5.0, -2.5)
+ e.RelLineTo(5.0, -2.5)
+ e.AbsVLineTo(30)
+ e.ClosePathEndPath()
+
+ for largeArc := 0; largeArc <= 1; largeArc++ {
+ for sweep := 0; sweep <= 1; sweep++ {
+ e.StartPath(4, 10+8*float32(sweep), -28+8*float32(largeArc))
+ e.RelArcTo(6, 3, 0, largeArc != 0, sweep != 0, 6, 3)
+ e.ClosePathEndPath()
+ }
+ }
+
+ testEncode(t, &e, "testdata/arcs.ivg")
+}
+
+func TestEncodeBlank(t *testing.T) {
+ var e Encoder
+ testEncode(t, &e, "testdata/blank.ivg")
+}
+
+var cowbellGradients = []struct {
+ radial bool
+
+ // Linear gradient coefficients.
+ x1, y1 float32
+ x2, y2 float32
+ tx, ty float32
+
+ // Radial gradient coefficients.
+ cx, cy, r float32
+ transform f32.Aff3
+
+ stops []GradientStop
+}{{
+ // The 0th element is unused.
+}, {
+ radial: true,
+ cx: -102.14,
+ cy: 20.272,
+ r: 18.012,
+ transform: f32.Aff3{
+ .33050, -.50775, 65.204,
+ .17296, .97021, 16.495,
+ },
+ stops: []GradientStop{
+ {Offset: 0, Color: color.RGBA{0xed, 0xd4, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0xfc, 0xe9, 0x4f, 0xff}},
+ },
+}, {
+ radial: true,
+ cx: -97.856,
+ cy: 26.719,
+ r: 18.61,
+ transform: f32.Aff3{
+ .35718, -.11527, 51.072,
+ .044280, .92977, 7.6124,
+ },
+ stops: []GradientStop{
+ {Offset: 0, Color: color.RGBA{0xed, 0xd4, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0xfc, 0xe9, 0x4f, 0xff}},
+ },
+}, {
+ x1: -16.183,
+ y1: 35.723,
+ x2: -18.75,
+ y2: 29.808,
+ tx: 48.438,
+ ty: -.22321,
+ stops: []GradientStop{
+ {Offset: 0, Color: color.RGBA{0x39, 0x21, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0x0f, 0x08, 0x00, 0xff}},
+ },
+}}
+
+var cowbellSVGData = []struct {
+ rgba color.RGBA
+ gradient int
+ d string
+ transform *f32.Aff3
+}{{
+ gradient: 2,
+ d: "m5.6684 17.968l.265-4.407 13.453 19.78.301 8.304-14.019-23.677z",
+}, {
+ gradient: 1,
+ d: "m19.299 33.482l-13.619-19.688 3.8435-2.684.0922-2.1237 4.7023-2.26 2.99 1.1274 4.56-1.4252 20.719 16.272-23.288 10.782z",
+}, {
+ rgba: color.RGBA{0xfd * 127 / 255, 0xee * 127 / 255, 0x74 * 127 / 255, 127},
+ d: "m19.285 32.845l-13.593-19.079 3.995-2.833.1689-2.0377 1.9171-.8635 18.829 18.965-11.317 5.848z",
+}, {
+ rgba: color.RGBA{0xc4, 0xa0, 0x00, 0xff},
+ d: "m19.211 40.055c-.11-.67-.203-2.301-.205-3.624l-.003-2.406-2.492-3.769c-3.334-5.044-11.448-17.211-9.6752-14.744.3211.447 1.6961 2.119 2.1874 2.656.4914.536 1.3538 1.706 1.9158 2.6 2.276 3.615 8.232 12.056 8.402 12.056.1 0 10.4-5.325 11.294-5.678.894-.354 11.25-4.542 11.45-4.342.506.506 1.27 7.466.761 8.08-.392.473-5.06 3.672-10.256 6.121-5.195 2.45-11.984 4.269-12.594 4.269-.421 0-.639-.338-.785-1.219z",
+}, {
+ gradient: 3,
+ d: "m19.825 33.646c.422-.68 10.105-5.353 10.991-5.753s9.881-4.123 10.468-4.009c.512.099.844 6.017.545 6.703-.23.527-8.437 4.981-9.516 5.523-1.225.616-11.642 4.705-12.145 4.369-.553-.368-.707-6.245-.343-6.833z",
+}, {
+ rgba: color.RGBA{0x00, 0x00, 0x00, 0xff},
+ d: "m21.982 5.8789-4.865 1.457-2.553-1.1914-5.3355 2.5743l-.015625.29688-.097656 1.8672-4.1855 2.7383.36719 4.5996.054687.0957s3.2427 5.8034 6.584 11.654c1.6707 2.9255 3.3645 5.861 4.6934 8.0938.66442 1.1164 1.2366 2.0575 1.6719 2.7363.21761.33942.40065.6121.54883.81641.07409.10215.13968.18665.20312.25976.06345.07312.07886.13374.27148.22461.27031.12752.38076.06954.54102.04883.16025-.02072.34015-.05724.55078-.10938.42126-.10427.95998-.26728 1.584-.4707 1.248-.40685 2.8317-.97791 4.3926-1.5586 3.1217-1.1614 6.1504-2.3633 6.1504-2.3633l.02539-.0098.02539-.01367s2.5368-1.3591 5.1211-2.8027c1.2922-.72182 2.5947-1.4635 3.6055-2.0723.50539-.30438.93732-.57459 1.2637-.79688.16318-.11114.29954-.21136.41211-.30273.11258-.09138.19778-.13521.30273-.32617.16048-.292.13843-.48235.1543-.78906s.01387-.68208.002-1.1094c-.02384-.8546-.09113-1.9133-.17188-2.9473-.161-2.067-.373-4.04-.373-4.04l-.021-.211-20.907-16.348zm-.209 1.1055 20.163 15.766c.01984.1875.19779 1.8625.34961 3.8066.08004 1.025.14889 2.0726.17188 2.8965.01149.41192.01156.76817-.002 1.0293-.01351.26113-.09532.47241-.0332.35938.05869-.10679.01987-.0289-.05664.0332s-.19445.14831-.34375.25c-.29859.20338-.72024.46851-1.2168.76758-.99311.59813-2.291 1.3376-3.5781 2.0566-2.5646 1.4327-5.0671 2.7731-5.0859 2.7832-.03276.01301-3.0063 1.1937-6.0977 2.3438-1.5542.5782-3.1304 1.1443-4.3535 1.543-.61154.19936-1.1356.35758-1.5137.45117-.18066.04472-.32333.07255-.41992.08594-.02937-.03686-.05396-.06744-.0957-.125-.128-.176-.305-.441-.517-.771-.424-.661-.993-1.594-1.655-2.705-1.323-2.223-3.016-5.158-4.685-8.08-3.3124-5.8-6.4774-11.465-6.5276-11.555l-.3008-3.787 4.1134-2.692.109-2.0777 4.373-2.1133 2.469 1.1523 4.734-1.4179z",
+}}
+
+func inv(x *f32.Aff3) f32.Aff3 {
+ invDet := 1 / (x[0]*x[4] - x[1]*x[3])
+ return f32.Aff3{
+ +x[4] * invDet,
+ -x[1] * invDet,
+ (x[1]*x[5] - x[2]*x[4]) * invDet,
+ -x[3] * invDet,
+ +x[0] * invDet,
+ (x[2]*x[3] - x[0]*x[5]) * invDet,
+ }
+}
+
+func TestEncodeCowbell(t *testing.T) {
+ var e Encoder
+ e.Reset(Metadata{
+ ViewBox: Rectangle{
+ Min: f32.Vec2{0, 0},
+ Max: f32.Vec2{+48, +48},
+ },
+ Palette: DefaultPalette,
+ })
+
+ for _, data := range cowbellSVGData {
+ if data.rgba != (color.RGBA{}) {
+ e.SetCReg(0, false, RGBAColor(data.rgba))
+ } else if data.gradient != 0 {
+ g := cowbellGradients[data.gradient]
+ if g.radial {
+ iform := inv(&g.transform)
+ iform[2] -= g.cx
+ iform[5] -= g.cy
+ for i := range iform {
+ iform[i] /= g.r
+ }
+ e.SetGradient(10, 10, true, iform, GradientSpreadPad, g.stops)
+ } else {
+ x1 := g.x1 + g.tx
+ y1 := g.y1 + g.ty
+ x2 := g.x2 + g.tx
+ y2 := g.y2 + g.ty
+ e.SetLinearGradient(10, 10, x1, y1, x2, y2, GradientSpreadPad, g.stops)
+ }
+ }
+
+ if err := encodePathData(&e, data.d, 0, false); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ testEncode(t, &e, "testdata/cowbell.ivg")
+}
+
+func TestEncodeElliptical(t *testing.T) {
+ var e Encoder
+
+ const (
+ cx, cy = -20, -10
+ rx, ry = 0, 24
+ sx, sy = 30, 15
+ )
+
+ e.SetEllipticalGradient(10, 10, cx, cy, rx, ry, sx, sy, GradientSpreadReflect, []GradientStop{
+ {Offset: 0, Color: color.RGBA{0xc0, 0x00, 0x00, 0xff}},
+ {Offset: 1, Color: color.RGBA{0x00, 0x00, 0xc0, 0xff}},
+ })
+ e.StartPath(0, -32, -32)
+ e.AbsHLineTo(+32)
+ e.AbsVLineTo(+32)
+ e.AbsHLineTo(-32)
+ e.ClosePathEndPath()
+
+ e.SetCReg(0, false, RGBAColor(color.RGBA{0xff, 0xff, 0xff, 0xff}))
+ diamond := func(x, y float32) {
+ e.StartPath(0, x-1, y)
+ e.AbsLineTo(x, y-1)
+ e.AbsLineTo(x+1, y)
+ e.AbsLineTo(x, y+1)
+ e.ClosePathEndPath()
+ }
+ diamond(cx, cy)
+ diamond(cx+rx, cy+ry)
+ diamond(cx+sx, cy+sy)
+
+ testEncode(t, &e, "testdata/elliptical.ivg")
+}
+
+var faviconColors = []color.RGBA{
+ {0x76, 0xe1, 0xfe, 0xff},
+ {0x38, 0x4e, 0x54, 0xff},
+ {0xff, 0xff, 0xff, 0xff},
+ {0x17, 0x13, 0x11, 0xff},
+ {0x00, 0x00, 0x00, 0x54},
+ {0xff, 0xfc, 0xfb, 0xff},
+ {0xc3, 0x8c, 0x74, 0xff},
+ {0x23, 0x20, 0x1f, 0xff},
+}
+
+var faviconSVGData = []struct {
+ faviconColorsIndex int
+ d string
+}{{
+ faviconColorsIndex: 1,
+ d: "m16.092 1.002c-1.1057.01-2.2107.048844-3.3164.089844-2.3441.086758-4.511.88464-6.2832 2.1758a3.8208 3.5794 29.452 0 0 -.8947 -.6856 3.8208 3.5794 29.452 0 0 -5.0879 1.2383 3.8208 3.5794 29.452 0 0 1.5664 4.9961 3.8208 3.5794 29.452 0 0 .3593 .1758c-.2784.9536-.4355 1.9598-.4355 3.0078v20h28v-20c0-1.042-.152-2.0368-.418-2.9766a3.5794 3.8208 60.548 0 0 .43359 -.20703 3.5794 3.8208 60.548 0 0 1.5684 -4.9961 3.5794 3.8208 60.548 0 0 -5.0879 -1.2383 3.5794 3.8208 60.548 0 0 -.92969 .72461c-1.727-1.257-3.843-2.0521-6.1562-2.2148-1.1058-.078-2.2126-.098844-3.3184-.089844z",
+}, {
+ faviconColorsIndex: 0,
+ d: "m16 3c-4.835 0-7.9248 1.0791-9.7617 2.8906-.4777-.4599-1.2937-1.0166-1.6309-1.207-.9775-.5520-2.1879-.2576-2.7051.6582-.5171.9158-.1455 2.1063.8321 2.6582.2658.1501 1.2241.5845 1.7519.7441-.3281.9946-.4863 2.0829-.4863 3.2559v20h24c-.049-7.356 0-18 0-20 0-1.209-.166-2.3308-.516-3.3496.539-.2011 1.243-.5260 1.463-.6504.978-.5519 1.351-1.7424.834-2.6582s-1.729-1.2102-2.707-.6582c-.303.1711-.978.6356-1.463 1.0625-1.854-1.724-4.906-2.7461-9.611-2.7461z",
+}, {
+ faviconColorsIndex: 1,
+ d: "m3.0918 5.9219c-.060217.00947-.10772.020635-.14648.033203-.019384.00628-.035462.013581-.052734.021484-.00864.00395-.019118.00825-.03125.015625-.00607.00369-.011621.00781-.021484.015625-.00493.00391-.017342.015389-.017578.015625-.0002366.0002356-.025256.031048-.025391.03125a.19867 .19867 0 0 0 .26367 .28320c.0005595-.0002168.00207-.00128.00391-.00195a.19867 .19867 0 0 0 .00391 -.00195c.015939-.00517.045148-.013113.085937-.019531.081581-.012836.20657-.020179.36719.00391.1020.0152.2237.0503.3535.0976-.3277.0694-.5656.1862-.7227.3145-.1143.0933-.1881.1903-.2343.2695-.023099.0396-.039499.074216-.050781.10547-.00564.015626-.00989.029721-.013672.046875-.00189.00858-.00458.017085-.00586.03125-.0006392.00708-.0005029.014724 0 .027344.0002516.00631.00192.023197.00195.023437.0000373.0002412.0097.036937.00977.037109a.19867 .19867 0 0 0 .38477 -.039063 .19867 .19867 0 0 0 0 -.00195c.00312-.00751.00865-.015947.017578-.03125.0230-.0395.0660-.0977.1425-.1601.1530-.1250.4406-.2702.9863-.2871a.19930 .19930 0 0 0 .082031 -.019531c.12649.089206.25979.19587.39844.32422a.19867 .19867 0 1 0 .2696 -.2911c-.6099-.5646-1.1566-.7793-1.5605-.8398-.2020-.0303-.3679-.0229-.4883-.0039z",
+}, {
+ faviconColorsIndex: 1,
+ d: "m28.543 5.8203c-.12043-.018949-.28631-.026379-.48828.00391-.40394.060562-.94869.27524-1.5586.83984a.19867 .19867 0 1 0 .26953 .29102c.21354-.19768.40814-.33222.59180-.44141.51624.023399.79659.16181.94531.28320.07652.062461.11952.12063.14258.16016.0094.016037.01458.025855.01758.033203a.19867 .19867 0 0 0 .38476 .039063c.000062-.0001719.0097-.036868.0098-.037109.000037-.0002412.0017-.017125.002-.023437.000505-.012624.000639-.020258 0-.027344-.0013-.01417-.004-.022671-.0059-.03125-.0038-.017158-.008-.031248-.01367-.046875-.01128-.031254-.02768-.067825-.05078-.10742-.04624-.079195-.12003-.17424-.23437-.26758-.11891-.097066-.28260-.18832-.49609-.25781.01785-.00328.03961-.011119.05664-.013672.16062-.024082.28561-.016738.36719-.00391.03883.00611.06556.012409.08203.017578.000833.0002613.0031.0017.0039.00195a.19867 .19867 0 0 0 .271 -.2793c-.000135-.0002016-.02515-.031014-.02539-.03125-.000236-.0002356-.01265-.011717-.01758-.015625-.0099-.00782-.01737-.01194-.02344-.015625-.01213-.00737-.02066-.011673-.0293-.015625-.01727-.0079-.03336-.013247-.05273-.019531-.03877-.012568-.08822-.025682-.14844-.035156z",
+}, {
+ faviconColorsIndex: 2,
+ d: "m15.171 9.992a4.8316 4.8316 0 0 1 -4.832 4.832 4.8316 4.8316 0 0 1 -4.8311 -4.832 4.8316 4.8316 0 0 1 4.8311 -4.8316 4.8316 4.8316 0 0 1 4.832 4.8316z",
+}, {
+ faviconColorsIndex: 2,
+ d: "m25.829 9.992a4.6538 4.6538 0 0 1 -4.653 4.654 4.6538 4.6538 0 0 1 -4.654 -4.654 4.6538 4.6538 0 0 1 4.654 -4.6537 4.6538 4.6538 0 0 1 4.653 4.6537z",
+}, {
+ faviconColorsIndex: 3,
+ d: "m14.377 9.992a1.9631 1.9631 0 0 1 -1.963 1.963 1.9631 1.9631 0 0 1 -1.963 -1.963 1.9631 1.9631 0 0 1 1.963 -1.963 1.9631 1.9631 0 0 1 1.963 1.963z",
+}, {
+ faviconColorsIndex: 3,
+ d: "m25.073 9.992a1.9631 1.9631 0 0 1 -1.963 1.963 1.9631 1.9631 0 0 1 -1.963 -1.963 1.9631 1.9631 0 0 1 1.963 -1.963 1.9631 1.9631 0 0 1 1.963 1.963z",
+}, {
+ faviconColorsIndex: 4,
+ d: "m14.842 15.555h2.2156c.40215 0 .72590.3237.72590.7259v2.6545c0 .4021-.32375.7259-.72590.7259h-2.2156c-.40215 0-.72590-.3238-.72590-.7259v-2.6545c0-.4022.32375-.7259.72590-.7259z",
+}, {
+ faviconColorsIndex: 5,
+ d: "m14.842 14.863h2.2156c.40215 0 .72590.3238.72590.7259v2.6546c0 .4021-.32375.7259-.72590.7259h-2.2156c-.40215 0-.72590-.3238-.72590-.7259v-2.6546c0-.4021.32375-.7259.72590-.7259z",
+}, {
+ faviconColorsIndex: 4,
+ d: "m20 16.167c0 .838-.87123 1.2682-2.1448 1.1659-.02366 0-.04795-.6004-.25415-.5832-.50367.042-1.0959-.02-1.686-.02-.61294 0-1.2063.1826-1.6855.017-.11023-.038-.17830.5838-.26153.5816-1.2437-.033-2.0788-.3383-2.0788-1.1618 0-1.2118 1.8156-2.1941 4.0554-2.1941 2.2397 0 4.0554.9823 4.0554 2.1941z",
+}, {
+ faviconColorsIndex: 6,
+ d: "m19.977 15.338c0 .5685-.43366.8554-1.1381 1.0001-.29193.06-.63037.096-1.0037.1166-.56405.032-1.2078.031-1.8912.031-.67283 0-1.3072 0-1.8649-.029-.30627-.017-.58943-.043-.84316-.084-.81383-.1318-1.325-.417-1.325-1.0344 0-1.1601 1.8056-2.1006 4.033-2.1006s4.033.9405 4.033 2.1006z",
+}, {
+ faviconColorsIndex: 7,
+ d: "m18.025 13.488a2.0802 1.3437 0 0 1 -2.0802 1.3437 2.0802 1.3437 0 0 1 -2.0802 -1.3437 2.0802 1.3437 0 0 1 2.0802 -1.3437 2.0802 1.3437 0 0 1 2.0802 1.3437z",
+}}
+
+func TestEncodeFavicon(t *testing.T) {
+ // Set up a base color for theming the favicon, gopher blue by default.
+ pal := DefaultPalette
+ pal[0] = faviconColors[0] // color.RGBA{0x76, 0xe1, 0xfe, 0xff}
+
+ var e Encoder
+ e.Reset(Metadata{
+ ViewBox: DefaultViewBox,
+ Palette: pal,
+ })
+
+ // The favicon graphic also uses a dark version of that base color. blend
+ // is 75% dark (CReg[63]) and 25% the base color (pal[0]).
+ dark := color.RGBA{0x23, 0x1d, 0x1b, 0xff}
+ blend := BlendColor(0x40, 0xff, 0x80)
+
+ // First, set CReg[63] to dark, then set CReg[63] to the blend of that dark
+ // color with pal[0].
+ e.SetCReg(1, false, RGBAColor(dark))
+ e.SetCReg(1, false, blend)
+
+ // Check that, for the suggested palette, blend resolves to the
+ // (non-themable) SVG file's faviconColors[1].
+ got := blend.Resolve(&pal, &[64]color.RGBA{
+ 63: dark,
+ })
+ want := faviconColors[1]
+ if got != want {
+ t.Fatalf("Blend:\ngot %#02x\nwant %#02x", got, want)
+ }
+
+ // Set aside the remaining, non-themable colors.
+ remainingColors := faviconColors[2:]
+
+ seenFCI2 := false
+ for _, data := range faviconSVGData {
+ adj := uint8(data.faviconColorsIndex)
+ if adj >= 2 {
+ if !seenFCI2 {
+ seenFCI2 = true
+ for i, c := range remainingColors {
+ e.SetCReg(uint8(i), false, RGBAColor(c))
+ }
+ }
+ adj -= 2
+ }
+
+ if err := encodePathData(&e, data.d, adj, true); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ testEncode(t, &e, "testdata/favicon.ivg")
+}
+
+func encodePathData(e *Encoder, d string, adj uint8, normalizeTo64X64 bool) error {
+ var args [7]float32
+ prevN, prevVerb := 0, byte(0)
+ for first := true; d != "z"; first = false {
+ n, verb, implicit := 0, d[0], false
+ switch d[0] {
+ case 'H', 'h', 'V', 'v':
+ n = 1
+ case 'L', 'M', 'l', 'm':
+ n = 2
+ case 'S', 's':
+ n = 4
+ case 'C', 'c':
+ n = 6
+ case 'A', 'a':
+ n = 7
+ case 'z':
+ n = 0
+ default:
+ if prevVerb == '\x00' {
+ panic("unrecognized verb")
+ }
+ n, verb, implicit = prevN, prevVerb, true
+ }
+ prevN, prevVerb = n, verb
+ if prevVerb == 'M' {
+ prevVerb = 'L'
+ } else if prevVerb == 'm' {
+ prevVerb = 'l'
+ }
+ if !implicit {
+ d = d[1:]
+ }
+
+ for i := 0; i < n; i++ {
+ nDots := 0
+ if d[0] == '.' {
+ nDots = 1
+ }
+ j := 1
+ for ; ; j++ {
+ switch d[j] {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ continue
+ case '.':
+ nDots++
+ if nDots == 1 {
+ continue
+ }
+ }
+ break
+ }
+ f, err := strconv.ParseFloat(d[:j], 64)
+ if err != nil {
+ return err
+ }
+ args[i] = float32(f)
+ for ; d[j] == ' ' || d[j] == ','; j++ {
+ }
+ d = d[j:]
+ }
+
+ if normalizeTo64X64 {
+ // The original SVG is 32x32 units, with the top left being (0, 0).
+ // Normalize to 64x64 units, with the center being (0, 0).
+ if verb == 'A' {
+ args[0] = 2 * args[0]
+ args[1] = 2 * args[1]
+ args[2] /= 360
+ args[5] = 2*args[5] - 32
+ args[6] = 2*args[6] - 32
+ } else if verb == 'a' {
+ args[0] = 2 * args[0]
+ args[1] = 2 * args[1]
+ args[2] /= 360
+ args[5] = 2 * args[5]
+ args[6] = 2 * args[6]
+ } else if first || ('A' <= verb && verb <= 'Z') {
+ for i := range args {
+ args[i] = 2*args[i] - 32
+ }
+ } else {
+ for i := range args {
+ args[i] = 2 * args[i]
+ }
+ }
+ } else if verb == 'A' || verb == 'a' {
+ args[2] /= 360
+ }
+
+ if first {
+ first = false
+ e.StartPath(adj, args[0], args[1])
+ continue
+ }
+ switch verb {
+ case 'H':
+ e.AbsHLineTo(args[0])
+ case 'h':
+ e.RelHLineTo(args[0])
+ case 'V':
+ e.AbsVLineTo(args[0])
+ case 'v':
+ e.RelVLineTo(args[0])
+ case 'L':
+ e.AbsLineTo(args[0], args[1])
+ case 'l':
+ e.RelLineTo(args[0], args[1])
+ case 'm':
+ e.ClosePathRelMoveTo(args[0], args[1])
+ case 'S':
+ e.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
+ case 's':
+ e.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
+ case 'C':
+ e.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ case 'c':
+ e.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ case 'A':
+ e.AbsArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
+ case 'a':
+ e.RelArcTo(args[0], args[1], args[2], args[3] != 0, args[4] != 0, args[5], args[6])
+ case 'z':
+ // No-op.
+ default:
+ panic("unrecognized verb")
+ }
+ }
+ e.ClosePathEndPath()
+ return nil
+}
+
+func TestEncodeGradient(t *testing.T) {
+ rgb := []GradientStop{
+ {Offset: 0.00, Color: color.RGBA{0xff, 0x00, 0x00, 0xff}},
+ {Offset: 0.25, Color: color.RGBA{0x00, 0xff, 0x00, 0xff}},
+ {Offset: 0.50, Color: color.RGBA{0x00, 0x00, 0xff, 0xff}},
+ {Offset: 1.00, Color: color.RGBA{0x00, 0x00, 0x00, 0xff}},
+ }
+ cmy := []GradientStop{
+ {Offset: 0.00, Color: color.RGBA{0x00, 0xff, 0xff, 0xff}},
+ {Offset: 0.25, Color: color.RGBA{0xff, 0xff, 0xff, 0xff}},
+ {Offset: 0.50, Color: color.RGBA{0xff, 0x00, 0xff, 0xff}},
+ {Offset: 0.75, Color: color.RGBA{0x00, 0x00, 0x00, 0x00}},
+ {Offset: 1.00, Color: color.RGBA{0xff, 0xff, 0x00, 0xff}},
+ }
+
+ var e Encoder
+
+ e.SetLinearGradient(10, 10, -12, -30, +12, -18, GradientSpreadNone, rgb)
+ e.StartPath(0, -30, -30)
+ e.AbsHLineTo(+30)
+ e.AbsVLineTo(-18)
+ e.AbsHLineTo(-30)
+ e.ClosePathEndPath()
+
+ e.SetLinearGradient(10, 10, -12, -14, +12, -2, GradientSpreadPad, cmy)
+ e.StartPath(0, -30, -14)
+ e.AbsHLineTo(+30)
+ e.AbsVLineTo(-2)
+ e.AbsHLineTo(-30)
+ e.ClosePathEndPath()
+
+ e.SetCircularGradient(10, 10, -8, 8, 0, 16, GradientSpreadReflect, rgb)
+ e.StartPath(0, -30, +2)
+ e.AbsHLineTo(+30)
+ e.AbsVLineTo(+14)
+ e.AbsHLineTo(-30)
+ e.ClosePathEndPath()
+
+ e.SetCircularGradient(10, 10, -8, 24, 0, 16, GradientSpreadRepeat, cmy)
+ e.StartPath(0, -30, +18)
+ e.AbsHLineTo(+30)
+ e.AbsVLineTo(+30)
+ e.AbsHLineTo(-30)
+ e.ClosePathEndPath()
+
+ testEncode(t, &e, "testdata/gradient.ivg")
+}
+
+func TestEncodeLODPolygon(t *testing.T) {
+ var e Encoder
+
+ poly := func(n int) {
+ const r = 28
+ angle := 2 * math.Pi / float64(n)
+ e.StartPath(0, r, 0)
+ for i := 1; i < n; i++ {
+ e.AbsLineTo(
+ float32(r*math.Cos(angle*float64(i))),
+ float32(r*math.Sin(angle*float64(i))),
+ )
+ }
+ e.ClosePathEndPath()
+ }
+
+ e.StartPath(0, -28, -20)
+ e.AbsVLineTo(-28)
+ e.AbsHLineTo(-20)
+ e.ClosePathEndPath()
+
+ e.SetLOD(0, 80)
+ poly(3)
+
+ e.SetLOD(80, positiveInfinity)
+ poly(5)
+
+ e.SetLOD(0, positiveInfinity)
+ e.StartPath(0, +28, +20)
+ e.AbsVLineTo(+28)
+ e.AbsHLineTo(+20)
+ e.ClosePathEndPath()
+
+ testEncode(t, &e, "testdata/lod-polygon.ivg")
+}
+
+var video005PrimitiveSVGData = []struct {
+ r, g, b uint32
+ x0, y0 int
+ x1, y1 int
+ x2, y2 int
+}{
+ {0x17, 0x06, 0x05, 162, 207, 271, 186, 195, -16},
+ {0xe9, 0xf5, 0xf8, -16, 179, 140, -11, 16, -8},
+ {0x00, 0x04, 0x27, 97, 96, 221, 21, 214, 111},
+ {0x89, 0xd9, 0xff, 262, -6, 271, 104, 164, -16},
+ {0x94, 0xbd, 0xc5, 204, 104, 164, 207, 59, 104},
+ {0xd4, 0x81, 0x3d, -16, 36, 123, 195, -16, 194},
+ {0x00, 0x00, 0x00, 164, 19, 95, 77, 138, 13},
+ {0x39, 0x11, 0x19, 50, 143, 115, 185, -4, 165},
+ {0x00, 0x3d, 0x81, 86, 109, 53, 76, 90, 24},
+ {0xfc, 0xc6, 0x9c, 31, 161, 80, 105, -16, 28},
+ {0x9e, 0xdd, 0xff, 201, -7, 31, -16, 2, 60},
+ {0x01, 0x20, 0x39, 132, 85, 240, -5, 173, 130},
+ {0xfd, 0xbc, 0x8f, 193, 127, 231, 94, 250, 124},
+ {0x43, 0x06, 0x00, 251, 207, 237, 83, 271, 97},
+ {0x80, 0xbf, 0xee, 117, 134, 88, 177, 90, 28},
+ {0x00, 0x00, 0x00, 127, 38, 172, 68, 223, 55},
+ {0x19, 0x0e, 0x16, 201, 204, 161, 101, 271, 192},
+ {0xf6, 0xaa, 0x71, 201, 164, 226, 141, 261, 152},
+ {0xe0, 0x36, 0x00, -16, -2, 29, -16, -6, 58},
+ {0xff, 0xe4, 0xba, 146, 45, 118, 75, 148, 76},
+ {0x00, 0x00, 0x12, 118, 44, 107, 109, 100, 51},
+ {0xbd, 0xd5, 0xe4, 271, 41, 253, -16, 211, 89},
+ {0x52, 0x00, 0x00, 87, 127, 83, 150, 55, 111},
+ {0x00, 0xb3, 0xa1, 124, 185, 135, 207, 194, 176},
+ {0x22, 0x00, 0x00, 59, 151, 33, 124, 52, 169},
+ {0xbe, 0xcb, 0xcb, 149, 42, 183, -16, 178, 47},
+ {0xff, 0xd4, 0xb1, 211, 119, 184, 100, 182, 124},
+ {0xff, 0xe1, 0x39, 73, 207, 140, 180, -13, 187},
+ {0xa7, 0xb0, 0xad, 122, 181, 200, 182, 93, 82},
+ {0x00, 0x00, 0x00, 271, 168, 170, 185, 221, 207},
+}
+
+func TestEncodeVideo005Primitive(t *testing.T) {
+ // The division by 4 is because the SVG width is 256 units and the IconVG
+ // width is 64 (from -32 to +32).
+ //
+ // The subtraction by 0.5 is because the SVG file contains the line:
+ // <g transform="translate(0.5 0.5)">
+ scaleX := func(i int) float32 { return float32(i)/4 - (32 - 0.5/4) }
+ scaleY := func(i int) float32 { return float32(i)/4 - (24 - 0.5/4) }
+
+ var e Encoder
+ e.Reset(Metadata{
+ ViewBox: Rectangle{
+ Min: f32.Vec2{-32, -24},
+ Max: f32.Vec2{+32, +24},
+ },
+ Palette: DefaultPalette,
+ })
+
+ e.SetCReg(0, false, RGBAColor(color.RGBA{0x7c, 0x7e, 0x7c, 0xff}))
+ e.StartPath(0, -32, -24)
+ e.AbsHLineTo(+32)
+ e.AbsVLineTo(+24)
+ e.AbsHLineTo(-32)
+ e.ClosePathEndPath()
+
+ for _, v := range video005PrimitiveSVGData {
+ e.SetCReg(0, false, RGBAColor(color.RGBA{
+ uint8(v.r * 128 / 255),
+ uint8(v.g * 128 / 255),
+ uint8(v.b * 128 / 255),
+ 128,
+ }))
+ e.StartPath(0, scaleX(v.x0), scaleY(v.y0))
+ e.AbsLineTo(scaleX(v.x1), scaleY(v.y1))
+ e.AbsLineTo(scaleX(v.x2), scaleY(v.y2))
+ e.ClosePathEndPath()
+ }
+
+ testEncode(t, &e, "testdata/video-005.primitive.ivg")
+}
diff --git a/shiny/iconvg/example_test.go b/shiny/iconvg/example_test.go
new file mode 100644
index 0000000..7796625
--- /dev/null
+++ b/shiny/iconvg/example_test.go
@@ -0,0 +1,67 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg_test
+
+import (
+ "image"
+ "image/draw"
+ "log"
+ "os"
+ "path/filepath"
+
+ "golang.org/x/exp/shiny/iconvg"
+)
+
+func Example() {
+ ivgData, err := os.ReadFile(filepath.FromSlash("testdata/action-info.lores.ivg"))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ const width = 24
+ dst := image.NewAlpha(image.Rect(0, 0, width, width))
+ var z iconvg.Rasterizer
+ z.SetDstImage(dst, dst.Bounds(), draw.Src)
+ if err := iconvg.Decode(&z, ivgData, nil); err != nil {
+ log.Fatal(err)
+ }
+
+ const asciiArt = ".++8"
+ buf := make([]byte, 0, width*(width+1))
+ for y := 0; y < width; y++ {
+ for x := 0; x < width; x++ {
+ a := dst.AlphaAt(x, y).A
+ buf = append(buf, asciiArt[a>>6])
+ }
+ buf = append(buf, '\n')
+ }
+ os.Stdout.Write(buf)
+
+ // Output:
+ // ........................
+ // ........................
+ // ........++8888++........
+ // ......+8888888888+......
+ // .....+888888888888+.....
+ // ....+88888888888888+....
+ // ...+8888888888888888+...
+ // ...88888888..88888888...
+ // ..+88888888..88888888+..
+ // ..+888888888888888888+..
+ // ..88888888888888888888..
+ // ..888888888..888888888..
+ // ..888888888..888888888..
+ // ..888888888..888888888..
+ // ..+88888888..88888888+..
+ // ..+88888888..88888888+..
+ // ...88888888..88888888...
+ // ...+8888888888888888+...
+ // ....+88888888888888+....
+ // .....+888888888888+.....
+ // ......+8888888888+......
+ // ........++8888++........
+ // ........................
+ // ........................
+}
diff --git a/shiny/iconvg/iconvg.go b/shiny/iconvg/iconvg.go
new file mode 100644
index 0000000..99eaac2
--- /dev/null
+++ b/shiny/iconvg/iconvg.go
@@ -0,0 +1,163 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "image/color"
+ "math"
+
+ "golang.org/x/image/math/f32"
+)
+
+const magic = "\x89IVG"
+
+var magicBytes = []byte(magic)
+
+var (
+ negativeInfinity = math.Float32frombits(0xff800000)
+ positiveInfinity = math.Float32frombits(0x7f800000)
+)
+
+func isNaNOrInfinity(f float32) bool {
+ return math.Float32bits(f)&0x7f800000 == 0x7f800000
+}
+
+const (
+ // File Format Version 0.
+ midViewBox = 0
+ midSuggestedPalette = 1
+
+ // File Format Version 1.
+ ffv1MIDViewBox = 8
+ ffv1MIDSuggestedPalette = 16
+)
+
+var gradientShapeNames = [2]string{
+ "linear",
+ "radial",
+}
+
+var gradientSpreadNames = [4]string{
+ "none",
+ "pad",
+ "reflect",
+ "repeat",
+}
+
+// GradientSpread is how to spread a gradient past its nominal bounds (from
+// offset being 0.0 to offset being 1.0).
+type GradientSpread uint8
+
+const (
+ GradientSpreadNone GradientSpread = 0
+ GradientSpreadPad GradientSpread = 1
+ GradientSpreadReflect GradientSpread = 2
+ GradientSpreadRepeat GradientSpread = 3
+)
+
+// GradientStop is a color/offset gradient stop.
+type GradientStop struct {
+ Offset float32
+ Color color.Color
+}
+
+// Rectangle is defined by its minimum and maximum coordinates.
+type Rectangle struct {
+ Min, Max f32.Vec2
+}
+
+// AspectRatio returns the Rectangle's aspect ratio. An IconVG graphic is
+// scalable; these dimensions do not necessarily map 1:1 to pixels.
+func (r *Rectangle) AspectRatio() (dx, dy float32) {
+ return r.Max[0] - r.Min[0], r.Max[1] - r.Min[1]
+}
+
+// Palette is an IconVG palette.
+type Palette [64]color.RGBA
+
+// Metadata is an IconVG's metadata.
+type Metadata struct {
+ ViewBox Rectangle
+
+ // Palette is a 64 color palette. When encoding, it is the suggested
+ // palette to place within the IconVG graphic. When decoding, it is either
+ // the optional palette passed to Decode, or if no optional palette was
+ // given, the suggested palette within the IconVG graphic.
+ Palette Palette
+}
+
+// DefaultViewBox is the default ViewBox. Its values should not be modified.
+var DefaultViewBox = Rectangle{
+ Min: f32.Vec2{-32, -32},
+ Max: f32.Vec2{+32, +32},
+}
+
+// DefaultPalette is the default Palette. Its values should not be modified.
+var DefaultPalette = Palette{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+}
diff --git a/shiny/iconvg/internal/gradient/gradient.go b/shiny/iconvg/internal/gradient/gradient.go
new file mode 100644
index 0000000..92446e7
--- /dev/null
+++ b/shiny/iconvg/internal/gradient/gradient.go
@@ -0,0 +1,242 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package gradient provides linear and radial gradient images.
+package gradient
+
+import (
+ "image"
+ "image/color"
+ "math"
+
+ "golang.org/x/image/math/f64"
+)
+
+// TODO: gamma correction / non-linear color interpolation?
+
+// TODO: move this out of an internal directory, either under
+// golang.org/x/image or under the standard library's image, so that
+// golang.org/x/image/{draw,vector} and possibly image/draw can type switch on
+// the gradient.Gradient type and provide fast path code.
+//
+// Doing so requires coming up with a stable API that we'd be happy to support
+// in the long term. This would probably include an easier way to create
+// linear, circular and elliptical gradients, without having to explicitly
+// calculate the f64.Aff3 matrix.
+
+// Shape is the gradient shape.
+type Shape uint8
+
+const (
+ ShapeLinear Shape = iota
+ ShapeRadial
+)
+
+// Spread is the gradient spread, or how to spread a gradient past its nominal
+// bounds (from offset being 0.0 to offset being 1.0).
+type Spread uint8
+
+const (
+ // SpreadNone means that offsets outside of the [0, 1] range map to
+ // transparent black.
+ SpreadNone Spread = iota
+ // SpreadPad means that offsets below 0 and above 1 map to the colors that
+ // 0 and 1 would map to.
+ SpreadPad
+ // SpreadReflect means that the offset mapping is reflected start-to-end,
+ // end-to-start, start-to-end, etc.
+ SpreadReflect
+ // SpreadRepeat means that the offset mapping is repeated start-to-end,
+ // start-to-end, start-to-end, etc.
+ SpreadRepeat
+)
+
+// Clamp clamps x to the range [0, 1]. If x is outside that range, it is
+// converted to a value in that range according to s's semantics. It returns -1
+// if s is SpreadNone and x is outside the range [0, 1].
+func (s Spread) Clamp(x float64) float64 {
+ if x >= 0 {
+ if x <= 1 {
+ return x
+ }
+ switch s {
+ case SpreadPad:
+ return 1
+ case SpreadReflect:
+ if int(x)&1 == 0 {
+ return x - math.Floor(x)
+ }
+ return math.Ceil(x) - x
+ case SpreadRepeat:
+ return x - math.Floor(x)
+ }
+ return -1
+ }
+ switch s {
+ case SpreadPad:
+ return 0
+ case SpreadReflect:
+ x = -x
+ if int(x)&1 == 0 {
+ return x - math.Floor(x)
+ }
+ return math.Ceil(x) - x
+ case SpreadRepeat:
+ return x - math.Floor(x)
+ }
+ return -1
+}
+
+// Stop is an offset and color.
+type Stop struct {
+ Offset float64
+ RGBA64 color.RGBA64
+}
+
+// Range is the range between two stops.
+type Range struct {
+ Offset0 float64
+ Offset1 float64
+ Width float64
+ R0 float64
+ R1 float64
+ G0 float64
+ G1 float64
+ B0 float64
+ B1 float64
+ A0 float64
+ A1 float64
+}
+
+// MakeRange returns the range between two stops.
+func MakeRange(s0, s1 Stop) Range {
+ return Range{
+ Offset0: s0.Offset,
+ Offset1: s1.Offset,
+ Width: s1.Offset - s0.Offset,
+ R0: float64(s0.RGBA64.R),
+ R1: float64(s1.RGBA64.R),
+ G0: float64(s0.RGBA64.G),
+ G1: float64(s1.RGBA64.G),
+ B0: float64(s0.RGBA64.B),
+ B1: float64(s1.RGBA64.B),
+ A0: float64(s0.RGBA64.A),
+ A1: float64(s1.RGBA64.A),
+ }
+}
+
+// AppendRanges appends to a the ranges defined by a's implicit final stop (if
+// any exist) and stops.
+func AppendRanges(a []Range, stops []Stop) []Range {
+ if len(stops) == 0 {
+ return nil
+ }
+ if len(a) != 0 {
+ z := a[len(a)-1]
+ a = append(a, MakeRange(Stop{
+ Offset: z.Offset1,
+ RGBA64: color.RGBA64{
+ R: uint16(z.R1),
+ G: uint16(z.G1),
+ B: uint16(z.B1),
+ A: uint16(z.A1),
+ },
+ }, stops[0]))
+ }
+ for i := 0; i < len(stops)-1; i++ {
+ a = append(a, MakeRange(stops[i], stops[i+1]))
+ }
+ return a
+}
+
+// Gradient is a very large image.Image (the same size as an image.Uniform)
+// whose colors form a gradient.
+type Gradient struct {
+ Shape Shape
+ Spread Spread
+
+ // Pix2Grad transforms coordinates from pixel space (the arguments to the
+ // Image.At method) to gradient space. Gradient space is where a linear
+ // gradient ranges from x == 0 to x == 1, and a radial gradient has center
+ // (0, 0) and radius 1.
+ //
+ // This is an affine transform, so it can represent elliptical gradients in
+ // pixel space, including non-axis-aligned ellipses.
+ //
+ // For a linear gradient, the bottom row is ignored.
+ Pix2Grad f64.Aff3
+
+ Ranges []Range
+
+ // First and Last are the first and last stop's colors.
+ First, Last color.RGBA64
+}
+
+// Init initializes g to a gradient whose geometry is defined by shape and
+// pix2Grad and whose colors are defined by spread and stops.
+func (g *Gradient) Init(shape Shape, spread Spread, pix2Grad f64.Aff3, stops []Stop) {
+ g.Shape = shape
+ g.Spread = spread
+ g.Pix2Grad = pix2Grad
+ g.Ranges = AppendRanges(g.Ranges[:0], stops)
+ if len(stops) == 0 {
+ g.First = color.RGBA64{}
+ g.Last = color.RGBA64{}
+ } else {
+ g.First = stops[0].RGBA64
+ g.Last = stops[len(stops)-1].RGBA64
+ }
+}
+
+// ColorModel satisfies the image.Image interface.
+func (g *Gradient) ColorModel() color.Model {
+ return color.RGBA64Model
+}
+
+// Bounds satisfies the image.Image interface.
+func (g *Gradient) Bounds() image.Rectangle {
+ return image.Rectangle{
+ Min: image.Point{-1e9, -1e9},
+ Max: image.Point{+1e9, +1e9},
+ }
+}
+
+// At satisfies the image.Image interface.
+func (g *Gradient) At(x, y int) color.Color {
+ if len(g.Ranges) == 0 {
+ return color.RGBA64{}
+ }
+
+ px := float64(x) + 0.5
+ py := float64(y) + 0.5
+
+ offset := 0.0
+ if g.Shape == ShapeLinear {
+ offset = g.Spread.Clamp(g.Pix2Grad[0]*px + g.Pix2Grad[1]*py + g.Pix2Grad[2])
+ } else {
+ gx := g.Pix2Grad[0]*px + g.Pix2Grad[1]*py + g.Pix2Grad[2]
+ gy := g.Pix2Grad[3]*px + g.Pix2Grad[4]*py + g.Pix2Grad[5]
+ offset = g.Spread.Clamp(math.Sqrt(gx*gx + gy*gy))
+ }
+ if !(offset >= 0) {
+ return color.RGBA64{}
+ }
+
+ if offset < g.Ranges[0].Offset0 {
+ return g.First
+ }
+ for _, r := range g.Ranges {
+ if r.Offset0 <= offset && offset <= r.Offset1 {
+ t := (offset - r.Offset0) / r.Width
+ s := 1 - t
+ return color.RGBA64{
+ uint16(s*r.R0 + t*r.R1),
+ uint16(s*r.G0 + t*r.G1),
+ uint16(s*r.B0 + t*r.B1),
+ uint16(s*r.A0 + t*r.A1),
+ }
+ }
+ }
+ return g.Last
+}
diff --git a/shiny/iconvg/rasterizer.go b/shiny/iconvg/rasterizer.go
new file mode 100644
index 0000000..ec99992
--- /dev/null
+++ b/shiny/iconvg/rasterizer.go
@@ -0,0 +1,595 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "math"
+
+ "golang.org/x/exp/shiny/iconvg/internal/gradient"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/image/vector"
+)
+
+const (
+ smoothTypeNone = iota
+ smoothTypeQuad
+ smoothTypeCube
+)
+
+// Rasterizer is a Destination that draws an IconVG graphic onto a raster
+// image.
+//
+// The zero value is usable, in that it has no raster image to draw onto, so
+// that calling Decode with this Destination is a no-op (other than checking
+// the encoded form for errors in the byte code). Call SetDstImage to change
+// the raster image, before calling Decode or between calls to Decode.
+type Rasterizer struct {
+ z vector.Rasterizer
+
+ dst draw.Image
+ r image.Rectangle
+ drawOp draw.Op
+
+ // scale and bias transforms the metadata.ViewBox rectangle to the (0, 0) -
+ // (r.Dx(), r.Dy()) rectangle.
+ scaleX float32
+ biasX float32
+ scaleY float32
+ biasY float32
+
+ metadata Metadata
+
+ lod0 float32
+ lod1 float32
+ cSel uint8
+ nSel uint8
+
+ disabled bool
+
+ firstStartPath bool
+ prevSmoothType uint8
+ prevSmoothPointX float32
+ prevSmoothPointY float32
+
+ fill image.Image
+ flatColor color.RGBA
+ flatImage image.Uniform
+ gradient gradient.Gradient
+
+ cReg [64]color.RGBA
+ nReg [64]float32
+ stops [64]gradient.Stop
+}
+
+// SetDstImage sets the Rasterizer to draw onto a destination image, given by
+// dst and r, with the given compositing operator.
+//
+// The IconVG graphic (which does not have a fixed size in pixels) will be
+// scaled in the X and Y dimensions to fit the rectangle r. The scaling factors
+// may differ in the two dimensions.
+func (z *Rasterizer) SetDstImage(dst draw.Image, r image.Rectangle, drawOp draw.Op) {
+ z.dst = dst
+ if r.Empty() {
+ r = image.Rectangle{}
+ }
+ z.r = r
+ z.drawOp = drawOp
+ z.recalcTransform()
+}
+
+// Reset resets the Rasterizer for the given Metadata.
+func (z *Rasterizer) Reset(m Metadata) {
+ z.metadata = m
+ z.lod0 = 0
+ z.lod1 = positiveInfinity
+ z.cSel = 0
+ z.nSel = 0
+ z.firstStartPath = true
+ z.prevSmoothType = smoothTypeNone
+ z.prevSmoothPointX = 0
+ z.prevSmoothPointY = 0
+ z.cReg = m.Palette
+ z.nReg = [64]float32{}
+ z.recalcTransform()
+}
+
+func (z *Rasterizer) recalcTransform() {
+ z.scaleX = float32(z.r.Dx()) / (z.metadata.ViewBox.Max[0] - z.metadata.ViewBox.Min[0])
+ z.biasX = -z.metadata.ViewBox.Min[0]
+ z.scaleY = float32(z.r.Dy()) / (z.metadata.ViewBox.Max[1] - z.metadata.ViewBox.Min[1])
+ z.biasY = -z.metadata.ViewBox.Min[1]
+}
+
+func (z *Rasterizer) SetCSel(cSel uint8) { z.cSel = cSel & 0x3f }
+func (z *Rasterizer) SetNSel(nSel uint8) { z.nSel = nSel & 0x3f }
+
+func (z *Rasterizer) SetCReg(adj uint8, incr bool, c Color) {
+ z.cReg[(z.cSel-adj)&0x3f] = c.Resolve(&z.metadata.Palette, &z.cReg)
+ if incr {
+ z.cSel++
+ }
+}
+
+func (z *Rasterizer) SetNReg(adj uint8, incr bool, f float32) {
+ z.nReg[(z.nSel-adj)&0x3f] = f
+ if incr {
+ z.nSel++
+ }
+}
+
+func (z *Rasterizer) SetLOD(lod0, lod1 float32) {
+ z.lod0, z.lod1 = lod0, lod1
+}
+
+func (z *Rasterizer) unabsX(x float32) float32 { return x/z.scaleX - z.biasX }
+func (z *Rasterizer) unabsY(y float32) float32 { return y/z.scaleY - z.biasY }
+
+func (z *Rasterizer) absX(x float32) float32 { return z.scaleX * (x + z.biasX) }
+func (z *Rasterizer) absY(y float32) float32 { return z.scaleY * (y + z.biasY) }
+func (z *Rasterizer) relX(x float32) float32 { return z.scaleX * x }
+func (z *Rasterizer) relY(y float32) float32 { return z.scaleY * y }
+
+func (z *Rasterizer) absVec2(x, y float32) (zx, zy float32) {
+ return z.absX(x), z.absY(y)
+}
+
+func (z *Rasterizer) relVec2(x, y float32) (zx, zy float32) {
+ px, py := z.z.Pen()
+ return px + z.relX(x), py + z.relY(y)
+}
+
+// implicitSmoothPoint returns the implicit control point for smooth-quadratic
+// and smooth-cubic BΓ©zier curves.
+//
+// https://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands says, "The first
+// control point is assumed to be the reflection of the second control point on
+// the previous command relative to the current point. (If there is no previous
+// command or if the previous command was not [a quadratic or cubic command],
+// assume the first control point is coincident with the current point.)"
+func (z *Rasterizer) implicitSmoothPoint(thisSmoothType uint8) (zx, zy float32) {
+ px, py := z.z.Pen()
+ if z.prevSmoothType != thisSmoothType {
+ return px, py
+ }
+ return 2*px - z.prevSmoothPointX, 2*py - z.prevSmoothPointY
+}
+
+func (z *Rasterizer) initGradient(rgba color.RGBA) (ok bool) {
+ nStops := int(rgba.R & 0x3f)
+ cBase := int(rgba.G & 0x3f)
+ nBase := int(rgba.B & 0x3f)
+ prevN := negativeInfinity
+ for i := 0; i < nStops; i++ {
+ c := z.cReg[(cBase+i)&0x3f]
+ if !validAlphaPremulColor(c) {
+ return false
+ }
+ n := z.nReg[(nBase+i)&0x3f]
+ if !(0 <= n && n <= 1) || !(n > prevN) {
+ return false
+ }
+ prevN = n
+ z.stops[i] = gradient.Stop{
+ Offset: float64(n),
+ RGBA64: color.RGBA64{
+ R: uint16(c.R) * 0x101,
+ G: uint16(c.G) * 0x101,
+ B: uint16(c.B) * 0x101,
+ A: uint16(c.A) * 0x101,
+ },
+ }
+ }
+
+ // The affine transformation matrix in the IconVG graphic, stored in 6
+ // contiguous NREG registers, goes from graphic coordinate space (i.e. the
+ // metadata viewBox) to the gradient coordinate space. We need it to start
+ // in pixel space, not graphic coordinate space.
+
+ invZSX := 1 / float64(z.scaleX)
+ invZSY := 1 / float64(z.scaleY)
+ zBX := float64(z.biasX)
+ zBY := float64(z.biasY)
+
+ a := float64(z.nReg[(nBase-6)&0x3f])
+ b := float64(z.nReg[(nBase-5)&0x3f])
+ c := float64(z.nReg[(nBase-4)&0x3f])
+ d := float64(z.nReg[(nBase-3)&0x3f])
+ e := float64(z.nReg[(nBase-2)&0x3f])
+ f := float64(z.nReg[(nBase-1)&0x3f])
+
+ pix2Grad := f64.Aff3{
+ a * invZSX,
+ b * invZSY,
+ c - a*zBX - b*zBY,
+ d * invZSX,
+ e * invZSY,
+ f - d*zBX - e*zBY,
+ }
+
+ shape := gradient.ShapeLinear
+ if (rgba.B>>6)&0x01 != 0 {
+ shape = gradient.ShapeRadial
+ }
+ z.gradient.Init(
+ shape,
+ gradient.Spread(rgba.G>>6),
+ pix2Grad,
+ z.stops[:nStops],
+ )
+
+ return true
+}
+
+func (z *Rasterizer) StartPath(adj uint8, x, y float32) {
+ z.flatColor = z.cReg[(z.cSel-adj)&0x3f]
+ if validAlphaPremulColor(z.flatColor) {
+ z.flatImage.C = &z.flatColor
+ z.fill = &z.flatImage
+ z.disabled = z.flatColor.A == 0
+ } else if z.flatColor.A == 0x00 && z.flatColor.B&0x80 != 0 {
+ z.fill = &z.gradient
+ z.disabled = !z.initGradient(z.flatColor)
+ } else {
+ z.fill = nil
+ z.disabled = true
+ }
+
+ width, height := z.r.Dx(), z.r.Dy()
+ h := float32(height)
+ z.disabled = z.disabled || !(z.lod0 <= h && h < z.lod1)
+ if z.disabled {
+ return
+ }
+
+ z.z.Reset(width, height)
+ if z.firstStartPath {
+ z.firstStartPath = false
+ z.z.DrawOp = z.drawOp
+ }
+ z.prevSmoothType = smoothTypeNone
+ z.z.MoveTo(z.absVec2(x, y))
+}
+
+func (z *Rasterizer) ClosePathEndPath() {
+ if z.disabled {
+ return
+ }
+ z.z.ClosePath()
+ if z.dst == nil {
+ return
+ }
+ z.z.Draw(z.dst, z.r, z.fill, image.Point{})
+}
+
+func (z *Rasterizer) ClosePathAbsMoveTo(x, y float32) {
+ if z.disabled {
+ return
+ }
+ z.prevSmoothType = smoothTypeNone
+ z.z.ClosePath()
+ z.z.MoveTo(z.absVec2(x, y))
+}
+
+func (z *Rasterizer) ClosePathRelMoveTo(x, y float32) {
+ if z.disabled {
+ return
+ }
+ z.prevSmoothType = smoothTypeNone
+ z.z.ClosePath()
+ z.z.MoveTo(z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsHLineTo(x float32) {
+ if z.disabled {
+ return
+ }
+ _, py := z.z.Pen()
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(z.absX(x), py)
+}
+
+func (z *Rasterizer) RelHLineTo(x float32) {
+ if z.disabled {
+ return
+ }
+ px, py := z.z.Pen()
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(px+z.relX(x), py)
+}
+
+func (z *Rasterizer) AbsVLineTo(y float32) {
+ if z.disabled {
+ return
+ }
+ px, _ := z.z.Pen()
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(px, z.absY(y))
+}
+
+func (z *Rasterizer) RelVLineTo(y float32) {
+ if z.disabled {
+ return
+ }
+ px, py := z.z.Pen()
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(px, py+z.relY(y))
+}
+
+func (z *Rasterizer) AbsLineTo(x, y float32) {
+ if z.disabled {
+ return
+ }
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(z.absVec2(x, y))
+}
+
+func (z *Rasterizer) RelLineTo(x, y float32) {
+ if z.disabled {
+ return
+ }
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsSmoothQuadTo(x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 := z.implicitSmoothPoint(smoothTypeQuad)
+ x, y = z.absVec2(x, y)
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
+ z.z.QuadTo(x1, y1, x, y)
+}
+
+func (z *Rasterizer) RelSmoothQuadTo(x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 := z.implicitSmoothPoint(smoothTypeQuad)
+ x, y = z.relVec2(x, y)
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
+ z.z.QuadTo(x1, y1, x, y)
+}
+
+func (z *Rasterizer) AbsQuadTo(x1, y1, x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 = z.absVec2(x1, y1)
+ x, y = z.absVec2(x, y)
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
+ z.z.QuadTo(x1, y1, x, y)
+}
+
+func (z *Rasterizer) RelQuadTo(x1, y1, x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 = z.relVec2(x1, y1)
+ x, y = z.relVec2(x, y)
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
+ z.z.QuadTo(x1, y1, x, y)
+}
+
+func (z *Rasterizer) AbsSmoothCubeTo(x2, y2, x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 := z.implicitSmoothPoint(smoothTypeCube)
+ x2, y2 = z.absVec2(x2, y2)
+ x, y = z.absVec2(x, y)
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
+ z.z.CubeTo(x1, y1, x2, y2, x, y)
+}
+
+func (z *Rasterizer) RelSmoothCubeTo(x2, y2, x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 := z.implicitSmoothPoint(smoothTypeCube)
+ x2, y2 = z.relVec2(x2, y2)
+ x, y = z.relVec2(x, y)
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
+ z.z.CubeTo(x1, y1, x2, y2, x, y)
+}
+
+func (z *Rasterizer) AbsCubeTo(x1, y1, x2, y2, x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 = z.absVec2(x1, y1)
+ x2, y2 = z.absVec2(x2, y2)
+ x, y = z.absVec2(x, y)
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
+ z.z.CubeTo(x1, y1, x2, y2, x, y)
+}
+
+func (z *Rasterizer) RelCubeTo(x1, y1, x2, y2, x, y float32) {
+ if z.disabled {
+ return
+ }
+ x1, y1 = z.relVec2(x1, y1)
+ x2, y2 = z.relVec2(x2, y2)
+ x, y = z.relVec2(x, y)
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
+ z.z.CubeTo(x1, y1, x2, y2, x, y)
+}
+
+func (z *Rasterizer) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ if z.disabled {
+ return
+ }
+ z.prevSmoothType = smoothTypeNone
+
+ // We follow the "Conversion from endpoint to center parameterization"
+ // algorithm as per
+ // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+
+ // There seems to be a bug in the spec's "implementation notes".
+ //
+ // Actual implementations, such as
+ // - https://git.gnome.org/browse/librsvg/tree/rsvg-path.c
+ // - http://svn.apache.org/repos/asf/xmlgraphics/batik/branches/svg11/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java
+ // - https://java.net/projects/svgsalamander/sources/svn/content/trunk/svg-core/src/main/java/com/kitfox/svg/pathcmd/Arc.java
+ // - https://github.com/millermedeiros/SVGParser/blob/master/com/millermedeiros/geom/SVGArc.as
+ // do something slightly different (marked with a †).
+
+ // (†) The Abs isn't part of the spec. Neither is checking that Rx and Ry
+ // are non-zero (and non-NaN).
+ Rx := math.Abs(float64(rx))
+ Ry := math.Abs(float64(ry))
+ if !(Rx > 0 && Ry > 0) {
+ z.z.LineTo(x, y)
+ return
+ }
+
+ // We work in IconVG coordinates (e.g. from -32 to +32 by default), rather
+ // than destination image coordinates (e.g. the width of the dst image),
+ // since the rx and ry radii also need to be scaled, but their scaling
+ // factors can be different, and aren't trivial to calculate due to
+ // xAxisRotation.
+ //
+ // We convert back to destination image coordinates via absX and absY calls
+ // later, during arcSegmentTo.
+ penX, penY := z.z.Pen()
+ x1 := float64(z.unabsX(penX))
+ y1 := float64(z.unabsY(penY))
+ x2 := float64(x)
+ y2 := float64(y)
+
+ phi := 2 * math.Pi * float64(xAxisRotation)
+
+ // Step 1: Compute (x1β€², y1β€²)
+ halfDx := (x1 - x2) / 2
+ halfDy := (y1 - y2) / 2
+ cosPhi := math.Cos(phi)
+ sinPhi := math.Sin(phi)
+ x1Prime := +cosPhi*halfDx + sinPhi*halfDy
+ y1Prime := -sinPhi*halfDx + cosPhi*halfDy
+
+ // Step 2: Compute (cxβ€², cyβ€²)
+ rxSq := Rx * Rx
+ rySq := Ry * Ry
+ x1PrimeSq := x1Prime * x1Prime
+ y1PrimeSq := y1Prime * y1Prime
+
+ // (†) Check that the radii are large enough.
+ radiiCheck := x1PrimeSq/rxSq + y1PrimeSq/rySq
+ if radiiCheck > 1 {
+ c := math.Sqrt(radiiCheck)
+ Rx *= c
+ Ry *= c
+ rxSq = Rx * Rx
+ rySq = Ry * Ry
+ }
+
+ denom := rxSq*y1PrimeSq + rySq*x1PrimeSq
+ step2 := 0.0
+ if a := rxSq*rySq/denom - 1; a > 0 {
+ step2 = math.Sqrt(a)
+ }
+ if largeArc == sweep {
+ step2 = -step2
+ }
+ cxPrime := +step2 * Rx * y1Prime / Ry
+ cyPrime := -step2 * Ry * x1Prime / Rx
+
+ // Step 3: Compute (cx, cy) from (cxβ€², cyβ€²)
+ cx := +cosPhi*cxPrime - sinPhi*cyPrime + (x1+x2)/2
+ cy := +sinPhi*cxPrime + cosPhi*cyPrime + (y1+y2)/2
+
+ // Step 4: Compute ΞΈ1 and Δθ
+ ax := (+x1Prime - cxPrime) / Rx
+ ay := (+y1Prime - cyPrime) / Ry
+ bx := (-x1Prime - cxPrime) / Rx
+ by := (-y1Prime - cyPrime) / Ry
+ theta1 := angle(1, 0, ax, ay)
+ deltaTheta := angle(ax, ay, bx, by)
+ if sweep {
+ if deltaTheta < 0 {
+ deltaTheta += 2 * math.Pi
+ }
+ } else {
+ if deltaTheta > 0 {
+ deltaTheta -= 2 * math.Pi
+ }
+ }
+
+ // This ends the
+ // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+ // algorithm. What follows below is specific to this implementation.
+
+ // We approximate an arc by one or more cubic BΓ©zier curves.
+ n := int(math.Ceil(math.Abs(deltaTheta) / (math.Pi/2 + 0.001)))
+ for i := 0; i < n; i++ {
+ z.arcSegmentTo(cx, cy,
+ theta1+deltaTheta*float64(i+0)/float64(n),
+ theta1+deltaTheta*float64(i+1)/float64(n),
+ Rx, Ry, cosPhi, sinPhi,
+ )
+ }
+}
+
+// arcSegmentTo approximates an arc by a cubic BΓ©zier curve. The mathematical
+// formulae for the control points are the same as that used by librsvg.
+func (z *Rasterizer) arcSegmentTo(cx, cy, theta1, theta2, rx, ry, cosPhi, sinPhi float64) {
+ halfDeltaTheta := (theta2 - theta1) * 0.5
+ q := math.Sin(halfDeltaTheta * 0.5)
+ t := (8 * q * q) / (3 * math.Sin(halfDeltaTheta))
+ cos1 := math.Cos(theta1)
+ sin1 := math.Sin(theta1)
+ cos2 := math.Cos(theta2)
+ sin2 := math.Sin(theta2)
+ x1 := rx * (+cos1 - t*sin1)
+ y1 := ry * (+sin1 + t*cos1)
+ x2 := rx * (+cos2 + t*sin2)
+ y2 := ry * (+sin2 - t*cos2)
+ x3 := rx * (+cos2)
+ y3 := ry * (+sin2)
+ z.z.CubeTo(
+ z.absX(float32(cx+cosPhi*x1-sinPhi*y1)),
+ z.absY(float32(cy+sinPhi*x1+cosPhi*y1)),
+ z.absX(float32(cx+cosPhi*x2-sinPhi*y2)),
+ z.absY(float32(cy+sinPhi*x2+cosPhi*y2)),
+ z.absX(float32(cx+cosPhi*x3-sinPhi*y3)),
+ z.absY(float32(cy+sinPhi*x3+cosPhi*y3)),
+ )
+}
+
+func (z *Rasterizer) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ ax, ay := z.relVec2(x, y)
+ z.AbsArcTo(rx, ry, xAxisRotation, largeArc, sweep, z.unabsX(ax), z.unabsY(ay))
+}
+
+// angle returns the angle between the u and v vectors.
+func angle(ux, uy, vx, vy float64) float64 {
+ uNorm := math.Sqrt(ux*ux + uy*uy)
+ vNorm := math.Sqrt(vx*vx + vy*vy)
+ norm := uNorm * vNorm
+ cos := (ux*vx + uy*vy) / norm
+ ret := 0.0
+ if cos <= -1 {
+ ret = math.Pi
+ } else if cos >= +1 {
+ ret = 0
+ } else {
+ ret = math.Acos(cos)
+ }
+ if ux*vy < uy*vx {
+ return -ret
+ }
+ return +ret
+}
diff --git a/shiny/iconvg/testdata/README b/shiny/iconvg/testdata/README
new file mode 100644
index 0000000..ce53d8c
--- /dev/null
+++ b/shiny/iconvg/testdata/README
@@ -0,0 +1,91 @@
+action-info.svg comes from the Material Design icon set. See
+action/svg/production/ic_info_48px.svg in the
+github.com/google/material-design-icons repository at the tag 3.0.2.
+
+action-info.{lo,hi}res.ivg are low- and high-resolution IconVG versions of that
+SVG file. Low resolution means that coordinates are quantized to 1/64th of a
+unit; the graphic's size is 48 by 48 units. High resolution means that
+coordinates are represented by all but the 2 least significant bits of a
+float32. Each low resolution coordinate is encoded in either 1 or 2 bytes. Each
+high resolution coordinate is encoded in either 1, 2 or 4 bytes.
+
+action-info.{lo,hi}res.ivg.disassembly are disassemblies of those IconVG files.
+
+action-info.{lo,hi}res.png are renderings of those IconVG files.
+
+
+
+arcs.ivg is inspired by the two examples at
+https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
+
+arcs.ivg.disassembly is a disassembly of that IconVG file.
+
+arcs.png is a rendering of that IconVG file.
+
+
+
+blank.ivg is a blank, square graphic.
+
+blank.ivg.disassembly is a disassembly of that IconVG file.
+
+blank.png is a rendering of that IconVG file.
+
+
+
+cowbell.svg is an original artwork by nigeltao@golang.org.
+
+cowbell.ivg is an IconVG version of that SVG file.
+
+cowbell.ivg.disassembly is a disassembly of that IconVG file.
+
+cowbell.png is a rendering of that IconVG file.
+
+
+
+elliptical.ivg was created manually.
+
+elliptical.ivg.disassembly is a disassembly of that IconVG file.
+
+elliptical.png is a rendering of that IconVG file.
+
+
+
+favicon.svg is based on doc/gopher/favicon.svg from the Go 1.7 release, after
+using Inkscape to convert strokes and circles to paths, and saving it as an
+"Optimized SVG".
+
+favicon.ivg is an IconVG version of that SVG file.
+
+favicon.ivg.disassembly is a disassembly of that IconVG file.
+
+favicon.png and favicon.pink.png are renderings of that IconVG file.
+
+
+
+gradient.ivg was created manually.
+
+gradient.ivg.disassembly is a disassembly of that IconVG file.
+
+gradient.png is a rendering of that IconVG file.
+
+
+
+lod-polygon.ivg was created manually.
+
+lod-polygon.ivg.disassembly is a disassembly of that IconVG file.
+
+lod-polygon.png and lod-polygon.64.png are renderings of that IconVG file.
+
+
+
+video-005.jpeg comes from an old version of the Go repository. See
+https://codereview.appspot.com/5758047/
+
+video-005.primitive.svg was based on running github.com/fogleman/primitive on a
+256x192 scaled version of video-005.jpeg.
+
+video-005.primitive.ivg is an IconVG version of that SVG file.
+
+video-005.primitive.ivg.disassembly is a disassembly of that IconVG file.
+
+video-005.primitive.png is a rendering of that IconVG file.
diff --git a/shiny/iconvg/testdata/action-info.hires.ivg b/shiny/iconvg/testdata/action-info.hires.ivg
new file mode 100644
index 0000000..9532529
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.hires.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/action-info.hires.ivg.disassembly b/shiny/iconvg/testdata/action-info.hires.ivg.disassembly
new file mode 100644
index 0000000..7bf6afb
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.hires.ivg.disassembly
@@ -0,0 +1,56 @@
+89 49 56 47 IconVG Magic identifier
+02 Number of metadata chunks: 1
+0a Metadata chunk length: 5
+00 Metadata Identifier: 0 (viewBox)
+50 -24
+50 -24
+b0 +24
+b0 +24
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+80 +0
+58 -20
+a0 C (absolute cubeTo), 1 reps
+cf cc 30 c1 -11.049999
+58 -20
+58 -20
+cf cc 30 c1 -11.049999
+58 -20
+80 +0
+91 s (relative smooth cubeTo), 2 reps
+37 33 0f 41 +8.950001
+a8 +20
+a8 +20
+a8 +20
+ s (relative smooth cubeTo), implicit
+a8 +20
+37 33 0f c1 -8.950001
+a8 +20
+58 -20
+80 S (absolute smooth cubeTo), 1 reps
+cf cc 30 41 +11.049999
+58 -20
+80 +0
+58 -20
+e3 z (closePath); m (relative moveTo)
+84 +2
+bc +30
+e7 h (relative horizontal lineTo)
+78 -4
+e8 V (absolute vertical lineTo)
+7c -2
+e7 h (relative horizontal lineTo)
+88 +4
+e9 v (relative vertical lineTo)
+98 +12
+e3 z (closePath); m (relative moveTo)
+80 +0
+60 -16
+e7 h (relative horizontal lineTo)
+78 -4
+e9 v (relative vertical lineTo)
+78 -4
+e7 h (relative horizontal lineTo)
+88 +4
+e9 v (relative vertical lineTo)
+88 +4
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/action-info.hires.png b/shiny/iconvg/testdata/action-info.hires.png
new file mode 100644
index 0000000..da09a3b
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.hires.png
Binary files differ
diff --git a/shiny/iconvg/testdata/action-info.lores.ivg b/shiny/iconvg/testdata/action-info.lores.ivg
new file mode 100644
index 0000000..73589fe
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.lores.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/action-info.lores.ivg.disassembly b/shiny/iconvg/testdata/action-info.lores.ivg.disassembly
new file mode 100644
index 0000000..0624e7c
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.lores.ivg.disassembly
@@ -0,0 +1,56 @@
+89 49 56 47 IconVG Magic identifier
+02 Number of metadata chunks: 1
+0a Metadata chunk length: 5
+00 Metadata Identifier: 0 (viewBox)
+50 -24
+50 -24
+b0 +24
+b0 +24
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+80 +0
+58 -20
+a0 C (absolute cubeTo), 1 reps
+f5 74 -11.046875
+58 -20
+58 -20
+f5 74 -11.046875
+58 -20
+80 +0
+91 s (relative smooth cubeTo), 2 reps
+f5 88 +8.953125
+a8 +20
+a8 +20
+a8 +20
+ s (relative smooth cubeTo), implicit
+a8 +20
+0d 77 -8.953125
+a8 +20
+58 -20
+80 S (absolute smooth cubeTo), 1 reps
+0d 8b +11.046875
+58 -20
+80 +0
+58 -20
+e3 z (closePath); m (relative moveTo)
+84 +2
+bc +30
+e7 h (relative horizontal lineTo)
+78 -4
+e8 V (absolute vertical lineTo)
+7c -2
+e7 h (relative horizontal lineTo)
+88 +4
+e9 v (relative vertical lineTo)
+98 +12
+e3 z (closePath); m (relative moveTo)
+80 +0
+60 -16
+e7 h (relative horizontal lineTo)
+78 -4
+e9 v (relative vertical lineTo)
+78 -4
+e7 h (relative horizontal lineTo)
+88 +4
+e9 v (relative vertical lineTo)
+88 +4
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/action-info.lores.png b/shiny/iconvg/testdata/action-info.lores.png
new file mode 100644
index 0000000..9811aaf
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.lores.png
Binary files differ
diff --git a/shiny/iconvg/testdata/action-info.svg b/shiny/iconvg/testdata/action-info.svg
new file mode 100644
index 0000000..22f40f9
--- /dev/null
+++ b/shiny/iconvg/testdata/action-info.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z"/></svg> \ No newline at end of file
diff --git a/shiny/iconvg/testdata/arcs.ivg b/shiny/iconvg/testdata/arcs.ivg
new file mode 100644
index 0000000..f03a704
--- /dev/null
+++ b/shiny/iconvg/testdata/arcs.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/arcs.ivg.disassembly b/shiny/iconvg/testdata/arcs.ivg.disassembly
new file mode 100644
index 0000000..1c2eb05
--- /dev/null
+++ b/shiny/iconvg/testdata/arcs.ivg.disassembly
@@ -0,0 +1,129 @@
+89 49 56 47 IconVG Magic identifier
+00 Number of metadata chunks: 0
+81 Set CREG[CSEL-1] to a 1 byte color
+64 RGBA ff0000ff
+82 Set CREG[CSEL-2] to a 1 byte color
+78 RGBA ffff00ff
+83 Set CREG[CSEL-3] to a 1 byte color
+00 RGBA 000000ff
+84 Set CREG[CSEL-4] to a 1 byte color
+02 RGBA 000080ff
+c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
+6c -10
+80 +0
+e7 h (relative horizontal lineTo)
+62 -15
+d0 a (relative arcTo), 1 reps
+9e +15
+9e +15
+00 0 Γ— 360 degrees (0 degrees)
+02 0x1 (largeArc=1, sweep=0)
+9e +15
+62 -15
+e1 z (closePath); end path
+c2 Start path, filled with CREG[CSEL-2]; M (absolute moveTo)
+64 -14
+78 -4
+e9 v (relative vertical lineTo)
+62 -15
+d0 a (relative arcTo), 1 reps
+9e +15
+9e +15
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+62 -15
+9e +15
+e1 z (closePath); end path
+c3 Start path, filled with CREG[CSEL-3]; M (absolute moveTo)
+62 -15
+bc +30
+20 l (relative lineTo), 1 reps
+8a +5
+81 7d -2.5
+d0 a (relative arcTo), 1 reps
+81 82 +2.5
+81 82 +2.5
+dc 0.9166667 Γ— 360 degrees (330 degrees)
+04 0x2 (largeArc=0, sweep=1)
+8a +5
+81 7d -2.5
+20 l (relative lineTo), 1 reps
+8a +5
+81 7d -2.5
+d0 a (relative arcTo), 1 reps
+81 82 +2.5
+8a +5
+dc 0.9166667 Γ— 360 degrees (330 degrees)
+04 0x2 (largeArc=0, sweep=1)
+8a +5
+81 7d -2.5
+20 l (relative lineTo), 1 reps
+8a +5
+81 7d -2.5
+d0 a (relative arcTo), 1 reps
+81 82 +2.5
+81 87 +7.5
+dc 0.9166667 Γ— 360 degrees (330 degrees)
+04 0x2 (largeArc=0, sweep=1)
+8a +5
+81 7d -2.5
+20 l (relative lineTo), 1 reps
+8a +5
+81 7d -2.5
+d0 a (relative arcTo), 1 reps
+81 82 +2.5
+94 +10
+dc 0.9166667 Γ— 360 degrees (330 degrees)
+04 0x2 (largeArc=0, sweep=1)
+8a +5
+81 7d -2.5
+20 l (relative lineTo), 1 reps
+8a +5
+81 7d -2.5
+e8 V (absolute vertical lineTo)
+bc +30
+e1 z (closePath); end path
+c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
+94 +10
+48 -28
+d0 a (relative arcTo), 1 reps
+8c +6
+86 +3
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+8c +6
+86 +3
+e1 z (closePath); end path
+c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
+a4 +18
+48 -28
+d0 a (relative arcTo), 1 reps
+8c +6
+86 +3
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+8c +6
+86 +3
+e1 z (closePath); end path
+c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
+94 +10
+58 -20
+d0 a (relative arcTo), 1 reps
+8c +6
+86 +3
+00 0 Γ— 360 degrees (0 degrees)
+02 0x1 (largeArc=1, sweep=0)
+8c +6
+86 +3
+e1 z (closePath); end path
+c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
+a4 +18
+58 -20
+d0 a (relative arcTo), 1 reps
+8c +6
+86 +3
+00 0 Γ— 360 degrees (0 degrees)
+06 0x3 (largeArc=1, sweep=1)
+8c +6
+86 +3
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/arcs.png b/shiny/iconvg/testdata/arcs.png
new file mode 100644
index 0000000..8f38992
--- /dev/null
+++ b/shiny/iconvg/testdata/arcs.png
Binary files differ
diff --git a/shiny/iconvg/testdata/blank.ivg b/shiny/iconvg/testdata/blank.ivg
new file mode 100644
index 0000000..e5be490
--- /dev/null
+++ b/shiny/iconvg/testdata/blank.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/blank.ivg.disassembly b/shiny/iconvg/testdata/blank.ivg.disassembly
new file mode 100644
index 0000000..10d2645
--- /dev/null
+++ b/shiny/iconvg/testdata/blank.ivg.disassembly
@@ -0,0 +1,2 @@
+89 49 56 47 IconVG Magic identifier
+00 Number of metadata chunks: 0
diff --git a/shiny/iconvg/testdata/blank.png b/shiny/iconvg/testdata/blank.png
new file mode 100644
index 0000000..5bae722
--- /dev/null
+++ b/shiny/iconvg/testdata/blank.png
Binary files differ
diff --git a/shiny/iconvg/testdata/cowbell.ivg b/shiny/iconvg/testdata/cowbell.ivg
new file mode 100644
index 0000000..f98ce1f
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/cowbell.ivg.disassembly b/shiny/iconvg/testdata/cowbell.ivg.disassembly
new file mode 100644
index 0000000..a97bfd6
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.ivg.disassembly
@@ -0,0 +1,611 @@
+89 49 56 47 IconVG Magic identifier
+02 Number of metadata chunks: 1
+0a Metadata chunk length: 5
+00 Metadata Identifier: 0 (viewBox)
+80 +0
+80 +0
+e0 +48
+e0 +48
+98 Set CREG[CSEL-0] to a 4 byte color
+02 4a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+4b b8 17 3e 0.14816391
+ad Set NREG[NSEL-5] to a real number
+63 7a 96 3c 0.0183689
+ac Set NREG[NSEL-4] to a real number
+03 b6 1c c0 -2.4486084
+ab Set NREG[NSEL-3] to a real number
+33 38 e7 bb -0.0070562586
+aa Set NREG[NSEL-2] to a real number
+73 23 69 3d 0.05691856
+a9 Set NREG[NSEL-1] to a real number
+3b 1b c1 bf -1.5086432
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+ed d4 00 RGBA edd400ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+fc e9 4f RGBA fce94fff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+ad 85 +5.671875
+f9 91 +17.96875
+23 l (relative lineTo), 4 reps
+45 80 +0.265625
+99 7b -4.40625
+ l (relative lineTo), implicit
+75 8d +13.453125
+c9 93 +19.78125
+ l (relative lineTo), implicit
+4d 80 +0.296875
+4d 88 +8.296875
+ l (relative lineTo), implicit
+fd 71 -14.015625
+55 68 -23.671875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+02 4a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+57 08 07 3e 0.1318677
+ad Set NREG[NSEL-5] to a real number
+03 56 8d 3d 0.06901169
+ac Set NREG[NSEL-4] to a real number
+93 1c 82 c0 -4.0659866
+ab Set NREG[NSEL-3] to a real number
+2b 94 c0 bc -0.023508146
+aa Set NREG[NSEL-2] to a real number
+87 fe 37 3d 0.04492046
+a9 Set NREG[NSEL-1] to a real number
+e3 ce aa be -0.33360958
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+ed d4 00 RGBA edd400ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+fc e9 4f RGBA fce94fff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+4d 93 +19.296875
+7d a1 +33.484375
+27 l (relative lineTo), 8 reps
+61 72 -13.625
+51 6c -19.6875
+ l (relative lineTo), implicit
+d9 83 +3.84375
+51 7d -2.6875
+ l (relative lineTo), implicit
+19 80 +0.09375
+e1 7d -2.125
+ l (relative lineTo), implicit
+b5 84 +4.703125
+bd 7d -2.265625
+ l (relative lineTo), implicit
+fd 82 +2.984375
+21 81 +1.125
+ l (relative lineTo), implicit
+91 84 +4.5625
+95 7e -1.421875
+ l (relative lineTo), implicit
+b9 94 +20.71875
+45 90 +16.265625
+ l (relative lineTo), implicit
+b9 68 -23.28125
+c9 8a +10.78125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+7e 76 39 7f RGBA 7e76397f
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+49 93 +19.28125
+d9 a0 +32.84375
+25 l (relative lineTo), 6 reps
+69 72 -13.59375
+ed 6c -19.078125
+ l (relative lineTo), implicit
+88 +4
+2d 7d -2.828125
+ l (relative lineTo), implicit
+2d 80 +0.171875
+f9 7d -2.03125
+ l (relative lineTo), implicit
+ed 81 +1.921875
+25 7f -0.859375
+ l (relative lineTo), implicit
+d5 92 +18.828125
+f9 92 +18.96875
+ l (relative lineTo), implicit
+b1 74 -11.3125
+d9 85 +5.84375
+e1 z (closePath); end path
+90 Set CREG[CSEL-0] to a 3 byte (direct) color
+c4 a0 00 RGBA c4a000ff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+39 93 +19.21875
+11 a8 +40.0625
+b0 c (relative cubeTo), 1 reps
+e5 7f -0.109375
+55 7f -0.671875
+cd 7f -0.203125
+b5 7d -2.296875
+cd 7f -0.203125
+61 7c -3.625
+21 l (relative lineTo), 2 reps
+80 +0
+99 7d -2.40625
+ l (relative lineTo), implicit
+85 7d -2.484375
+3d 7c -3.765625
+b9 c (relative cubeTo), 10 reps
+ad 7c -3.328125
+f5 7a -5.046875
+8d 74 -11.453125
+c9 6e -17.21875
+55 76 -9.671875
+41 71 -14.75
+ c (relative cubeTo), implicit
+55 80 +0.328125
+75 80 +0.453125
+b5 81 +1.703125
+21 82 +2.125
+31 82 +2.1875
+a9 82 +2.65625
+ c (relative cubeTo), implicit
+7d 80 +0.484375
+89 80 +0.53125
+5d 81 +1.359375
+b5 81 +1.703125
+ed 81 +1.921875
+99 82 +2.59375
+ c (relative cubeTo), implicit
+49 82 +2.28125
+9d 83 +3.609375
+3d 88 +8.234375
+11 8c +12.0625
+69 88 +8.40625
+11 8c +12.0625
+ c (relative cubeTo), implicit
+19 80 +0.09375
+80 +0
+69 8a +10.40625
+ad 7a -5.328125
+4d 8b +11.296875
+55 7a -5.671875
+ c (relative cubeTo), implicit
+e5 80 +0.890625
+a5 7f -0.359375
+41 8b +11.25
+75 7b -4.546875
+75 8b +11.453125
+a9 7b -4.34375
+ c (relative cubeTo), implicit
+81 80 +0.5
+81 80 +0.5
+45 81 +1.265625
+79 87 +7.46875
+c5 80 +0.765625
+15 88 +8.078125
+ c (relative cubeTo), implicit
+9d 7f -0.390625
+79 80 +0.46875
+f1 7a -5.0625
+ad 83 +3.671875
+c1 75 -10.25
+21 86 +6.125
+ c (relative cubeTo), implicit
+d1 7a -5.1875
+75 82 +2.453125
+05 74 -11.984375
+45 84 +4.265625
+69 73 -12.59375
+45 84 +4.265625
+ c (relative cubeTo), implicit
+95 7f -0.421875
+80 +0
+5d 7f -0.640625
+a9 7f -0.34375
+39 7f -0.78125
+c9 7e -1.21875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+02 4a 8a 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, linear, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+6b e4 7c bd -0.061741263
+ad Set NREG[NSEL-5] to a real number
+83 ae 11 be -0.14226723
+ac Set NREG[NSEL-4] to a real number
+6b 57 e1 40 7.0419197
+ab Set NREG[NSEL-3] to a real number
+00 0
+aa Set NREG[NSEL-2] to a real number
+00 0
+a9 Set NREG[NSEL-1] to a real number
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+39 21 00 RGBA 392100ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+97 Set CREG[CSEL-0] to a 3 byte (direct) color; CSEL++
+0f 08 00 RGBA 0f0800ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+d5 93 +19.828125
+a5 a1 +33.640625
+b0 c (relative cubeTo), 1 reps
+6d 80 +0.421875
+51 7f -0.6875
+1d 8a +10.109375
+a5 7a -5.359375
+fd 8a +10.984375
+41 7a -5.75
+90 s (relative smooth cubeTo), 1 reps
+e1 89 +9.875
+e1 7b -4.125
+79 8a +10.46875
+fd 7b -4.015625
+b3 c (relative cubeTo), 4 reps
+85 80 +0.515625
+19 80 +0.09375
+d9 80 +0.84375
+05 86 +6.015625
+8d 80 +0.546875
+b5 86 +6.703125
+ c (relative cubeTo), implicit
+c5 7f -0.234375
+89 80 +0.53125
+91 77 -8.4375
+fd 84 +4.984375
+7d 76 -9.515625
+85 85 +5.515625
+ c (relative cubeTo), implicit
+c9 7e -1.21875
+9d 80 +0.609375
+5d 74 -11.640625
+b5 84 +4.703125
+dd 73 -12.140625
+61 84 +4.375
+ c (relative cubeTo), implicit
+75 7f -0.546875
+a1 7f -0.375
+4d 7f -0.703125
+c1 79 -6.25
+a9 7f -0.34375
+2d 79 -6.828125
+e1 z (closePath); end path
+80 Set CREG[CSEL-0] to a 1 byte color
+00 RGBA 000000ff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+fd 95 +21.984375
+e1 85 +5.875
+27 l (relative lineTo), 8 reps
+25 7b -4.859375
+75 81 +1.453125
+ l (relative lineTo), implicit
+75 7d -2.546875
+d1 7e -1.1875
+ l (relative lineTo), implicit
+ad 7a -5.328125
+95 82 +2.578125
+ l (relative lineTo), implicit
+fd 7f -0.015625
+4d 80 +0.296875
+ l (relative lineTo), implicit
+e9 7f -0.09375
+e1 81 +1.875
+ l (relative lineTo), implicit
+d1 7b -4.1875
+bd 82 +2.734375
+ l (relative lineTo), implicit
+61 80 +0.375
+99 84 +4.59375
+ l (relative lineTo), implicit
+0d 80 +0.046875
+19 80 +0.09375
+90 s (relative smooth cubeTo), 1 reps
+41 83 +3.25
+cd 85 +5.796875
+95 86 +6.578125
+a9 8b +11.65625
+b9 c (relative cubeTo), 10 reps
+ad 81 +1.671875
+ed 82 +2.921875
+5d 83 +3.359375
+dd 85 +5.859375
+b1 84 +4.6875
+19 88 +8.09375
+ c (relative cubeTo), implicit
+ad 80 +0.671875
+1d 81 +1.109375
+3d 81 +1.234375
+11 82 +2.0625
+ad 81 +1.671875
+bd 82 +2.734375
+ c (relative cubeTo), implicit
+39 80 +0.21875
+59 80 +0.34375
+69 80 +0.40625
+9d 80 +0.609375
+8d 80 +0.546875
+d1 80 +0.8125
+ c (relative cubeTo), implicit
+15 80 +0.078125
+1d 80 +0.109375
+25 80 +0.140625
+31 80 +0.1875
+35 80 +0.203125
+45 80 +0.265625
+ c (relative cubeTo), implicit
+11 80 +0.0625
+15 80 +0.078125
+15 80 +0.078125
+25 80 +0.140625
+45 80 +0.265625
+39 80 +0.21875
+ c (relative cubeTo), implicit
+45 80 +0.265625
+21 80 +0.125
+61 80 +0.375
+11 80 +0.0625
+8d 80 +0.546875
+0d 80 +0.046875
+ c (relative cubeTo), implicit
+29 80 +0.15625
+fd 7f -0.015625
+59 80 +0.34375
+f1 7f -0.0625
+8d 80 +0.546875
+e5 7f -0.109375
+ c (relative cubeTo), implicit
+6d 80 +0.421875
+e5 7f -0.109375
+f5 80 +0.953125
+bd 7f -0.265625
+95 81 +1.578125
+89 7f -0.46875
+ c (relative cubeTo), implicit
+41 81 +1.25
+99 7f -0.40625
+d5 82 +2.828125
+05 7f -0.984375
+65 84 +4.390625
+71 7e -1.5625
+ c (relative cubeTo), implicit
+21 83 +3.125
+d9 7e -1.15625
+29 86 +6.15625
+a5 7d -2.359375
+29 86 +6.15625
+a5 7d -2.359375
+21 l (relative lineTo), 2 reps
+09 80 +0.03125
+fd 7f -0.015625
+ l (relative lineTo), implicit
+09 80 +0.03125
+fd 7f -0.015625
+90 s (relative smooth cubeTo), 1 reps
+89 82 +2.53125
+a5 7e -1.359375
+21 85 +5.125
+35 7d -2.796875
+b4 c (relative cubeTo), 5 reps
+4d 81 +1.296875
+49 7f -0.71875
+99 82 +2.59375
+89 7e -1.46875
+9d 83 +3.609375
+ed 7d -2.078125
+ c (relative cubeTo), implicit
+81 80 +0.5
+b5 7f -0.296875
+f1 80 +0.9375
+6d 7f -0.578125
+45 81 +1.265625
+35 7f -0.796875
+ c (relative cubeTo), implicit
+29 80 +0.15625
+e5 7f -0.109375
+4d 80 +0.296875
+c9 7f -0.21875
+69 80 +0.40625
+b5 7f -0.296875
+ c (relative cubeTo), implicit
+1d 80 +0.109375
+e9 7f -0.09375
+35 80 +0.203125
+dd 7f -0.140625
+4d 80 +0.296875
+ad 7f -0.328125
+ c (relative cubeTo), implicit
+29 80 +0.15625
+b5 7f -0.296875
+25 80 +0.140625
+85 7f -0.484375
+29 80 +0.15625
+39 7f -0.78125
+90 s (relative smooth cubeTo), 1 reps
+05 80 +0.015625
+51 7f -0.6875
+80 +0
+e5 7e -1.109375
+b1 c (relative cubeTo), 2 reps
+f9 7f -0.03125
+25 7f -0.859375
+e9 7f -0.09375
+19 7e -1.90625
+d5 7f -0.171875
+0d 7d -2.953125
+ c (relative cubeTo), implicit
+d9 7f -0.15625
+f1 7d -2.0625
+a1 7f -0.375
+f5 7b -4.046875
+a1 7f -0.375
+f5 7b -4.046875
+21 l (relative lineTo), 2 reps
+fd 7f -0.015625
+c9 7f -0.21875
+ l (relative lineTo), implicit
+19 6b -20.90625
+a9 6f -16.34375
+e3 z (closePath); m (relative moveTo)
+cd 7f -0.203125
+1d 81 +1.109375
+20 l (relative lineTo), 1 reps
+29 94 +20.15625
+c5 8f +15.765625
+b4 c (relative cubeTo), 5 reps
+05 80 +0.015625
+31 80 +0.1875
+35 80 +0.203125
+dd 81 +1.859375
+59 80 +0.34375
+d1 83 +3.8125
+ c (relative cubeTo), implicit
+15 80 +0.078125
+09 81 +1.03125
+29 80 +0.15625
+15 82 +2.078125
+2d 80 +0.171875
+e5 82 +2.890625
+ c (relative cubeTo), implicit
+05 80 +0.015625
+69 80 +0.40625
+05 80 +0.015625
+c5 80 +0.765625
+80 +0
+09 81 +1.03125
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+45 80 +0.265625
+e9 7f -0.09375
+79 80 +0.46875
+f9 7f -0.03125
+5d 80 +0.359375
+ c (relative cubeTo), implicit
+11 80 +0.0625
+e5 7f -0.109375
+05 80 +0.015625
+f9 7f -0.03125
+f1 7f -0.0625
+09 80 +0.03125
+90 s (relative smooth cubeTo), 1 reps
+d1 7f -0.1875
+25 80 +0.140625
+a9 7f -0.34375
+41 80 +0.25
+bb c (relative cubeTo), 12 reps
+b5 7f -0.296875
+35 80 +0.203125
+49 7f -0.71875
+79 80 +0.46875
+c9 7e -1.21875
+c5 80 +0.765625
+ c (relative cubeTo), implicit
+7e -1
+99 80 +0.59375
+b5 7d -2.296875
+59 81 +1.34375
+6d 7c -3.578125
+11 82 +2.0625
+ c (relative cubeTo), implicit
+71 7d -2.5625
+71 81 +1.4375
+f1 7a -5.0625
+c5 82 +2.765625
+ed 7a -5.078125
+c9 82 +2.78125
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+05 80 +0.015625
+7a -3
+31 81 +1.1875
+e9 79 -6.09375
+59 82 +2.34375
+ c (relative cubeTo), implicit
+75 7e -1.546875
+95 80 +0.578125
+e1 7c -3.125
+25 81 +1.140625
+a5 7b -4.359375
+8d 81 +1.546875
+ c (relative cubeTo), implicit
+65 7f -0.609375
+35 80 +0.203125
+dd 7e -1.140625
+5d 80 +0.359375
+7d 7e -1.515625
+75 80 +0.453125
+ c (relative cubeTo), implicit
+d1 7f -0.1875
+0d 80 +0.046875
+ad 7f -0.328125
+15 80 +0.078125
+95 7f -0.421875
+19 80 +0.09375
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+f9 7f -0.03125
+f5 7f -0.046875
+f1 7f -0.0625
+e9 7f -0.09375
+e1 7f -0.125
+ c (relative cubeTo), implicit
+e1 7f -0.125
+d5 7f -0.171875
+b1 7f -0.3125
+91 7f -0.4375
+7d 7f -0.515625
+3d 7f -0.765625
+ c (relative cubeTo), implicit
+95 7f -0.421875
+59 7f -0.65625
+7e -1
+69 7e -1.59375
+59 7e -1.65625
+4d 7d -2.703125
+ c (relative cubeTo), implicit
+ad 7e -1.328125
+c9 7d -2.21875
+fd 7c -3.015625
+d9 7a -5.15625
+51 7b -4.6875
+ed 77 -8.078125
+ c (relative cubeTo), implicit
+b1 7c -3.3125
+35 7a -5.796875
+85 79 -6.484375
+89 74 -11.46875
+79 79 -6.53125
+71 74 -11.5625
+25 l (relative lineTo), 6 reps
+b5 7f -0.296875
+39 7c -3.78125
+ l (relative lineTo), implicit
+1d 84 +4.109375
+51 7d -2.6875
+ l (relative lineTo), implicit
+1d 80 +0.109375
+ed 7d -2.078125
+ l (relative lineTo), implicit
+61 84 +4.375
+e5 7d -2.109375
+ l (relative lineTo), implicit
+79 82 +2.46875
+29 81 +1.15625
+ l (relative lineTo), implicit
+bd 84 +4.734375
+95 7e -1.421875
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/cowbell.png b/shiny/iconvg/testdata/cowbell.png
new file mode 100644
index 0000000..e962ff1
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.png
Binary files differ
diff --git a/shiny/iconvg/testdata/cowbell.svg b/shiny/iconvg/testdata/cowbell.svg
new file mode 100644
index 0000000..34ec496
--- /dev/null
+++ b/shiny/iconvg/testdata/cowbell.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg1921" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="256" width="256" viewBox="0 0 48 48" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+ <radialGradient id="radialGradient3107" gradientUnits="userSpaceOnUse" cy="20.272" cx="-102.14" gradientTransform="matrix(.33050 .17296 -.50775 .97021 65.204 16.495)" r="18.012">
+ <stop id="stop6387" stop-color="#edd400" offset="0"/>
+ <stop id="stop6389" stop-color="#fce94f" offset="1"/>
+ </radialGradient>
+ <radialGradient id="radialGradient3991" gradientUnits="userSpaceOnUse" cy="26.719" cx="-97.856" gradientTransform="matrix(.35718 .044280 -.11527 .92977 51.072 7.6124)" r="18.61">
+ <stop id="stop3995" stop-color="#edd400" offset="0"/>
+ <stop id="stop3997" stop-color="#fce94f" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient5756" x1="-16.183" gradientUnits="userSpaceOnUse" y1="35.723" gradientTransform="translate(48.438 -.22321)" x2="-18.75" y2="29.808">
+ <stop id="stop5752" stop-color="#392100" offset="0"/>
+ <stop id="stop5754" stop-color="#0f0800" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1">
+ <path id="path1463" fill="url(#radialGradient3991)" d="m5.6684 17.968l.265-4.407 13.453 19.78.301 8.304-14.019-23.677z"/>
+ <path id="path1451" fill="url(#radialGradient3107)" d="m19.299 33.482l-13.619-19.688 3.8435-2.684.0922-2.1237 4.7023-2.26 2.99 1.1274 4.56-1.4252 20.719 16.272-23.288 10.782z"/>
+ <path id="path4875" fill-opacity=".49804" fill="#fdee74" d="m19.285 32.845l-13.593-19.079 3.995-2.833.1689-2.0377 1.9171-.8635 18.829 18.965-11.317 5.848z"/>
+ <path id="path1441" fill="#c4a000" d="m19.211 40.055c-.11-.67-.203-2.301-.205-3.624l-.003-2.406-2.492-3.769c-3.334-5.044-11.448-17.211-9.6752-14.744.3211.447 1.6961 2.119 2.1874 2.656.4914.536 1.3538 1.706 1.9158 2.6 2.276 3.615 8.232 12.056 8.402 12.056.1 0 10.4-5.325 11.294-5.678.894-.354 11.25-4.542 11.45-4.342.506.506 1.27 7.466.761 8.08-.392.473-5.06 3.672-10.256 6.121-5.195 2.45-11.984 4.269-12.594 4.269-.421 0-.639-.338-.785-1.219z"/>
+ <path id="path1433" fill="url(#linearGradient5756)" d="m19.825 33.646c.422-.68 10.105-5.353 10.991-5.753s9.881-4.123 10.468-4.009c.512.099.844 6.017.545 6.703-.23.527-8.437 4.981-9.516 5.523-1.225.616-11.642 4.705-12.145 4.369-.553-.368-.707-6.245-.343-6.833z"/>
+ <path id="path1350" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none;text-decoration-style:solid" fill-rule="evenodd" d="m21.982 5.8789-4.865 1.457-2.553-1.1914-5.3355 2.5743l-.015625.29688-.097656 1.8672-4.1855 2.7383.36719 4.5996.054687.0957s3.2427 5.8034 6.584 11.654c1.6707 2.9255 3.3645 5.861 4.6934 8.0938.66442 1.1164 1.2366 2.0575 1.6719 2.7363.21761.33942.40065.6121.54883.81641.07409.10215.13968.18665.20312.25976.06345.07312.07886.13374.27148.22461.27031.12752.38076.06954.54102.04883.16025-.02072.34015-.05724.55078-.10938.42126-.10427.95998-.26728 1.584-.4707 1.248-.40685 2.8317-.97791 4.3926-1.5586 3.1217-1.1614 6.1504-2.3633 6.1504-2.3633l.02539-.0098.02539-.01367s2.5368-1.3591 5.1211-2.8027c1.2922-.72182 2.5947-1.4635 3.6055-2.0723.50539-.30438.93732-.57459 1.2637-.79688.16318-.11114.29954-.21136.41211-.30273.11258-.09138.19778-.13521.30273-.32617.16048-.292.13843-.48235.1543-.78906s.01387-.68208.002-1.1094c-.02384-.8546-.09113-1.9133-.17188-2.9473-.161-2.067-.373-4.04-.373-4.04l-.021-.211-20.907-16.348zm-.209 1.1055 20.163 15.766c.01984.1875.19779 1.8625.34961 3.8066.08004 1.025.14889 2.0726.17188 2.8965.01149.41192.01156.76817-.002 1.0293-.01351.26113-.09532.47241-.0332.35938.05869-.10679.01987-.0289-.05664.0332s-.19445.14831-.34375.25c-.29859.20338-.72024.46851-1.2168.76758-.99311.59813-2.291 1.3376-3.5781 2.0566-2.5646 1.4327-5.0671 2.7731-5.0859 2.7832-.03276.01301-3.0063 1.1937-6.0977 2.3438-1.5542.5782-3.1304 1.1443-4.3535 1.543-.61154.19936-1.1356.35758-1.5137.45117-.18066.04472-.32333.07255-.41992.08594-.02937-.03686-.05396-.06744-.0957-.125-.128-.176-.305-.441-.517-.771-.424-.661-.993-1.594-1.655-2.705-1.323-2.223-3.016-5.158-4.685-8.08-3.3124-5.8-6.4774-11.465-6.5276-11.555l-.3008-3.787 4.1134-2.692.109-2.0777 4.373-2.1133 2.469 1.1523 4.734-1.4179z"/>
+ </g>
+</svg>
diff --git a/shiny/iconvg/testdata/elliptical.ivg b/shiny/iconvg/testdata/elliptical.ivg
new file mode 100644
index 0000000..0ee0321
--- /dev/null
+++ b/shiny/iconvg/testdata/elliptical.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/elliptical.ivg.disassembly b/shiny/iconvg/testdata/elliptical.ivg.disassembly
new file mode 100644
index 0000000..504194b
--- /dev/null
+++ b/shiny/iconvg/testdata/elliptical.ivg.disassembly
@@ -0,0 +1,79 @@
+89 49 56 47 IconVG Magic identifier
+00 Number of metadata chunks: 0
+98 Set CREG[CSEL-0] to a 4 byte color
+02 8a ca 00 gradient (NSTOPS=2, CBASE=10, NBASE=10, radial, reflect)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+af aa aa bc -0.020833336
+bd Set NREG[NSEL-5] to a zero-to-one number
+0a 0.041666668
+ac Set NREG[NSEL-4] to a real number
+00 0
+ab Set NREG[NSEL-3] to a real number
+8b 88 08 3d 0.03333333
+aa Set NREG[NSEL-2] to a real number
+00 0
+b9 Set NREG[NSEL-1] to a zero-to-one number
+a0 0.6666667
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+4b RGBA c00000ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+03 RGBA 0000c0ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+40 -32
+40 -32
+e6 H (absolute horizontal lineTo)
+c0 +32
+e8 V (absolute vertical lineTo)
+c0 +32
+e6 H (absolute horizontal lineTo)
+40 -32
+e1 z (closePath); end path
+80 Set CREG[CSEL-0] to a 1 byte color
+7c RGBA ffffffff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+56 -21
+6c -10
+02 L (absolute lineTo), 3 reps
+58 -20
+6a -11
+ L (absolute lineTo), implicit
+5a -19
+6c -10
+ L (absolute lineTo), implicit
+58 -20
+6e -9
+e1 z (closePath); end path
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+56 -21
+9c +14
+02 L (absolute lineTo), 3 reps
+58 -20
+9a +13
+ L (absolute lineTo), implicit
+5a -19
+9c +14
+ L (absolute lineTo), implicit
+58 -20
+9e +15
+e1 z (closePath); end path
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+92 +9
+8a +5
+02 L (absolute lineTo), 3 reps
+94 +10
+88 +4
+ L (absolute lineTo), implicit
+96 +11
+8a +5
+ L (absolute lineTo), implicit
+94 +10
+8c +6
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/elliptical.png b/shiny/iconvg/testdata/elliptical.png
new file mode 100644
index 0000000..f67a08f
--- /dev/null
+++ b/shiny/iconvg/testdata/elliptical.png
Binary files differ
diff --git a/shiny/iconvg/testdata/favicon.ivg b/shiny/iconvg/testdata/favicon.ivg
new file mode 100644
index 0000000..68ad0e9
--- /dev/null
+++ b/shiny/iconvg/testdata/favicon.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/favicon.ivg.disassembly b/shiny/iconvg/testdata/favicon.ivg.disassembly
new file mode 100644
index 0000000..c655193
--- /dev/null
+++ b/shiny/iconvg/testdata/favicon.ivg.disassembly
@@ -0,0 +1,994 @@
+89 49 56 47 IconVG Magic identifier
+02 Number of metadata chunks: 1
+0a Metadata chunk length: 5
+02 Metadata Identifier: 1 (suggested palette)
+80 1 palette colors, 3 bytes per color
+76 e1 fe RGBA 76e1feff
+91 Set CREG[CSEL-1] to a 3 byte (direct) color
+23 1d 1b RGBA 231d1bff
+a1 Set CREG[CSEL-1] to a 3 byte (indirect) color
+40 blend 191:64 c0:c1
+ff c0: CREG[63]
+80 c1: customPalette[0]
+c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
+31 80 +0.1875
+44 -30
+b1 c (relative cubeTo), 2 reps
+c9 7d -2.21875
+05 80 +0.015625
+95 7b -4.421875
+19 80 +0.09375
+61 79 -6.625
+31 80 +0.1875
+ c (relative cubeTo), implicit
+51 7b -4.6875
+2d 80 +0.171875
+fd 76 -9.015625
+c5 81 +1.765625
+71 73 -12.5625
+5d 84 +4.359375
+d3 a (relative arcTo), 4 reps
+a5 87 +7.640625
+29 87 +7.15625
+97 8c a7 3d 0.0818111 Γ— 360 degrees (29.451996 degrees)
+00 0x0 (largeArc=0, sweep=0)
+35 7e -1.796875
+a1 7e -1.375
+ a (relative arcTo), implicit
+a5 87 +7.640625
+29 87 +7.15625
+97 8c a7 3d 0.0818111 Γ— 360 degrees (29.451996 degrees)
+00 0x0 (largeArc=0, sweep=0)
+d5 75 -10.171875
+7d 82 +2.484375
+ a (relative arcTo), implicit
+a5 87 +7.640625
+29 87 +7.15625
+97 8c a7 3d 0.0818111 Γ— 360 degrees (29.451996 degrees)
+00 0x0 (largeArc=0, sweep=0)
+21 83 +3.125
+94 +10
+ a (relative arcTo), implicit
+a5 87 +7.640625
+29 87 +7.15625
+97 8c a7 3d 0.0818111 Γ— 360 degrees (29.451996 degrees)
+00 0x0 (largeArc=0, sweep=0)
+b9 80 +0.71875
+5d 80 +0.359375
+b0 c (relative cubeTo), 1 reps
+71 7f -0.5625
+e9 81 +1.90625
+21 7f -0.875
+ed 83 +3.921875
+21 7f -0.875
+05 86 +6.015625
+e9 v (relative vertical lineTo)
+d0 +40
+e7 h (relative horizontal lineTo)
+f0 +56
+e9 v (relative vertical lineTo)
+30 -40
+b0 c (relative cubeTo), 1 reps
+80 +0
+ed 7d -2.078125
+b5 7f -0.296875
+ed 7b -4.078125
+29 7f -0.84375
+0d 7a -5.953125
+d3 a (relative arcTo), 4 reps
+29 87 +7.15625
+a5 87 +7.640625
+b7 39 2c 3e 0.16818887 Γ— 360 degrees (60.547993 degrees)
+00 0x0 (largeArc=0, sweep=0)
+dd 80 +0.859375
+99 7f -0.40625
+ a (relative arcTo), implicit
+29 87 +7.15625
+a5 87 +7.640625
+b7 39 2c 3e 0.16818887 Γ— 360 degrees (60.547993 degrees)
+00 0x0 (largeArc=0, sweep=0)
+25 83 +3.140625
+6c -10
+ a (relative arcTo), implicit
+29 87 +7.15625
+a5 87 +7.640625
+b7 39 2c 3e 0.16818887 Γ— 360 degrees (60.547993 degrees)
+00 0x0 (largeArc=0, sweep=0)
+d5 75 -10.171875
+85 7d -2.484375
+ a (relative arcTo), implicit
+29 87 +7.15625
+a5 87 +7.640625
+b7 39 2c 3e 0.16818887 Γ— 360 degrees (60.547993 degrees)
+00 0x0 (largeArc=0, sweep=0)
+25 7e -1.859375
+75 81 +1.453125
+b1 c (relative cubeTo), 2 reps
+8d 7c -3.453125
+7d 7d -2.515625
+51 78 -7.6875
+e5 7b -4.109375
+b1 73 -12.3125
+95 7b -4.421875
+ c (relative cubeTo), implicit
+c9 7d -2.21875
+d9 7f -0.15625
+95 7b -4.421875
+cd 7f -0.203125
+5d 79 -6.640625
+d1 7f -0.1875
+e1 z (closePath); end path
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+80 +0
+4c -26
+b5 c (relative cubeTo), 6 reps
+55 76 -9.671875
+80 +0
+29 70 -15.84375
+29 82 +2.15625
+7d 6c -19.515625
+c9 85 +5.78125
+ c (relative cubeTo), implicit
+0d 7f -0.953125
+15 7f -0.921875
+69 7d -2.59375
+f9 7d -2.03125
+bd 7c -3.265625
+99 7d -2.40625
+ c (relative cubeTo), implicit
+0d 7e -1.953125
+e5 7e -1.109375
+a1 7b -4.375
+7d 7f -0.515625
+99 7a -5.40625
+51 81 +1.3125
+ c (relative cubeTo), implicit
+f9 7e -1.03125
+d5 81 +1.828125
+b5 7f -0.296875
+39 84 +4.21875
+ad 81 +1.671875
+51 85 +5.3125
+ c (relative cubeTo), implicit
+89 80 +0.53125
+4d 80 +0.296875
+75 82 +2.453125
+2d 81 +1.171875
+81 83 +3.5
+7d 81 +1.484375
+ c (relative cubeTo), implicit
+59 7f -0.65625
+fd 81 +1.984375
+09 7f -0.96875
+2d 84 +4.171875
+09 7f -0.96875
+85 86 +6.515625
+e9 v (relative vertical lineTo)
+d0 +40
+e7 h (relative horizontal lineTo)
+e0 +48
+b3 c (relative cubeTo), 4 reps
+e9 7f -0.09375
+49 71 -14.71875
+80 +0
+38 -36
+80 +0
+30 -40
+ c (relative cubeTo), implicit
+80 +0
+95 7d -2.421875
+ad 7f -0.328125
+59 7b -4.65625
+f9 7e -1.03125
+4d 79 -6.703125
+ c (relative cubeTo), implicit
+15 81 +1.078125
+99 7f -0.40625
+7d 82 +2.484375
+f5 7e -1.046875
+ed 82 +2.921875
+b5 7e -1.296875
+ c (relative cubeTo), implicit
+f5 81 +1.953125
+e5 7e -1.109375
+b5 82 +2.703125
+85 7c -3.484375
+ad 81 +1.671875
+b1 7a -5.3125
+90 s (relative smooth cubeTo), 1 reps
+8d 7c -3.453125
+95 7d -2.421875
+99 7a -5.40625
+b1 7e -1.3125
+b1 c (relative cubeTo), 2 reps
+65 7f -0.609375
+59 80 +0.34375
+0d 7e -1.953125
+45 81 +1.265625
+15 7d -2.921875
+21 82 +2.125
+ c (relative cubeTo), implicit
+4d 7c -3.703125
+8d 7c -3.453125
+31 76 -9.8125
+81 7a -5.5
+c9 6c -19.21875
+81 7a -5.5
+e1 z (closePath); end path
+c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
+31 66 -25.8125
+d9 6b -20.15625
+b5 c (relative cubeTo), 6 reps
+e1 7f -0.125
+05 80 +0.015625
+c9 7f -0.21875
+0d 80 +0.046875
+b5 7f -0.296875
+11 80 +0.0625
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+05 80 +0.015625
+ed 7f -0.078125
+09 80 +0.03125
+e5 7f -0.109375
+0d 80 +0.046875
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+05 80 +0.015625
+f9 7f -0.03125
+05 80 +0.015625
+f1 7f -0.0625
+09 80 +0.03125
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+80 +0
+fd 7f -0.015625
+05 80 +0.015625
+f5 7f -0.046875
+09 80 +0.03125
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+05 80 +0.015625
+f9 7f -0.03125
+09 80 +0.03125
+f9 7f -0.03125
+09 80 +0.03125
+ c (relative cubeTo), implicit
+80 +0
+80 +0
+f5 7f -0.046875
+11 80 +0.0625
+f5 7f -0.046875
+11 80 +0.0625
+d0 a (relative arcTo), 1 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+89 80 +0.53125
+91 80 +0.5625
+b0 c (relative cubeTo), 1 reps
+80 +0
+80 +0
+80 +0
+80 +0
+05 80 +0.015625
+80 +0
+d0 a (relative arcTo), 1 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+05 80 +0.015625
+80 +0
+ba c (relative cubeTo), 11 reps
+09 80 +0.03125
+fd 7f -0.015625
+19 80 +0.09375
+f9 7f -0.03125
+2d 80 +0.171875
+f9 7f -0.03125
+ c (relative cubeTo), implicit
+29 80 +0.15625
+f9 7f -0.03125
+69 80 +0.40625
+f5 7f -0.046875
+bd 80 +0.734375
+05 80 +0.015625
+ c (relative cubeTo), implicit
+35 80 +0.203125
+09 80 +0.03125
+75 80 +0.453125
+19 80 +0.09375
+b5 80 +0.703125
+31 80 +0.1875
+ c (relative cubeTo), implicit
+59 7f -0.65625
+25 80 +0.140625
+e1 7e -1.125
+61 80 +0.375
+8d 7e -1.453125
+a1 80 +0.625
+ c (relative cubeTo), implicit
+c5 7f -0.234375
+31 80 +0.1875
+a1 7f -0.375
+61 80 +0.375
+89 7f -0.46875
+89 80 +0.53125
+ c (relative cubeTo), implicit
+f5 7f -0.046875
+15 80 +0.078125
+ed 7f -0.078125
+25 80 +0.140625
+e9 7f -0.09375
+39 80 +0.21875
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+09 80 +0.03125
+fd 7f -0.015625
+11 80 +0.0625
+f9 7f -0.03125
+19 80 +0.09375
+ c (relative cubeTo), implicit
+80 +0
+05 80 +0.015625
+fd 7f -0.015625
+09 80 +0.03125
+fd 7f -0.015625
+11 80 +0.0625
+ c (relative cubeTo), implicit
+80 +0
+05 80 +0.015625
+80 +0
+09 80 +0.03125
+80 +0
+11 80 +0.0625
+ c (relative cubeTo), implicit
+80 +0
+05 80 +0.015625
+80 +0
+0d 80 +0.046875
+80 +0
+0d 80 +0.046875
+ c (relative cubeTo), implicit
+80 +0
+80 +0
+05 80 +0.015625
+15 80 +0.078125
+05 80 +0.015625
+15 80 +0.078125
+d1 a (relative arcTo), 2 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+c5 80 +0.765625
+ed 7f -0.078125
+ a (relative arcTo), implicit
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+80 +0
+80 +0
+b2 c (relative cubeTo), 3 reps
+80 +0
+fd 7f -0.015625
+05 80 +0.015625
+f9 7f -0.03125
+09 80 +0.03125
+f1 7f -0.0625
+ c (relative cubeTo), implicit
+0d 80 +0.046875
+ed 7f -0.078125
+21 80 +0.125
+cd 7f -0.203125
+49 80 +0.28125
+b1 7f -0.3125
+ c (relative cubeTo), implicit
+51 80 +0.3125
+c1 7f -0.25
+e1 80 +0.875
+75 7f -0.546875
+f9 81 +1.96875
+6d 7f -0.578125
+d0 a (relative arcTo), 1 reps
+69 80 +0.40625
+69 80 +0.40625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+29 80 +0.15625
+f9 7f -0.03125
+b0 c (relative cubeTo), 1 reps
+41 80 +0.25
+2d 80 +0.171875
+85 80 +0.515625
+65 80 +0.390625
+cd 80 +0.796875
+a9 80 +0.65625
+d0 a (relative arcTo), 1 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+02 0x1 (largeArc=1, sweep=0)
+8d 80 +0.546875
+6d 7f -0.578125
+b1 c (relative cubeTo), 2 reps
+c9 7e -1.21875
+e1 7e -1.125
+b1 7d -2.3125
+71 7e -1.5625
+e1 7c -3.125
+55 7e -1.671875
+ c (relative cubeTo), implicit
+99 7f -0.40625
+f1 7f -0.0625
+45 7f -0.734375
+f5 7f -0.046875
+05 7f -0.984375
+80 +0
+e1 z (closePath); end path
+c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
+19 99 +25.09375
+a5 6b -20.359375
+b1 c (relative cubeTo), 2 reps
+c5 7f -0.234375
+f9 7f -0.03125
+6d 7f -0.578125
+f5 7f -0.046875
+09 7f -0.96875
+05 80 +0.015625
+ c (relative cubeTo), implicit
+31 7f -0.8125
+21 80 +0.125
+1d 7e -1.890625
+8d 80 +0.546875
+e1 7c -3.125
+ad 81 +1.671875
+d0 a (relative arcTo), 1 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+02 0x1 (largeArc=1, sweep=0)
+89 80 +0.53125
+95 80 +0.578125
+b3 c (relative cubeTo), 4 reps
+6d 80 +0.421875
+9d 7f -0.390625
+d1 80 +0.8125
+55 7f -0.671875
+31 81 +1.1875
+1d 7f -0.890625
+ c (relative cubeTo), implicit
+09 81 +1.03125
+0d 80 +0.046875
+99 81 +1.59375
+55 80 +0.328125
+e5 81 +1.890625
+91 80 +0.5625
+ c (relative cubeTo), implicit
+29 80 +0.15625
+21 80 +0.125
+3d 80 +0.234375
+3d 80 +0.234375
+49 80 +0.28125
+55 80 +0.328125
+ c (relative cubeTo), implicit
+05 80 +0.015625
+09 80 +0.03125
+09 80 +0.03125
+0d 80 +0.046875
+09 80 +0.03125
+11 80 +0.0625
+d0 a (relative arcTo), 1 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+c5 80 +0.765625
+15 80 +0.078125
+bb c (relative cubeTo), 12 reps
+80 +0
+80 +0
+05 80 +0.015625
+ed 7f -0.078125
+05 80 +0.015625
+ed 7f -0.078125
+ c (relative cubeTo), implicit
+80 +0
+80 +0
+80 +0
+f9 7f -0.03125
+80 +0
+f5 7f -0.046875
+ c (relative cubeTo), implicit
+80 +0
+f9 7f -0.03125
+80 +0
+f5 7f -0.046875
+80 +0
+f1 7f -0.0625
+ c (relative cubeTo), implicit
+80 +0
+f9 7f -0.03125
+fd 7f -0.015625
+f5 7f -0.046875
+fd 7f -0.015625
+f1 7f -0.0625
+ c (relative cubeTo), implicit
+80 +0
+f9 7f -0.03125
+fd 7f -0.015625
+f1 7f -0.0625
+f9 7f -0.03125
+e9 7f -0.09375
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+f1 7f -0.0625
+f1 7f -0.0625
+dd 7f -0.140625
+e9 7f -0.09375
+c9 7f -0.21875
+ c (relative cubeTo), implicit
+e9 7f -0.09375
+d9 7f -0.15625
+c5 7f -0.234375
+a9 7f -0.34375
+89 7f -0.46875
+79 7f -0.53125
+ c (relative cubeTo), implicit
+c5 7f -0.234375
+d1 7f -0.1875
+71 7f -0.5625
+a1 7f -0.375
+05 7f -0.984375
+7d 7f -0.515625
+ c (relative cubeTo), implicit
+09 80 +0.03125
+80 +0
+15 80 +0.078125
+fd 7f -0.015625
+1d 80 +0.109375
+f9 7f -0.03125
+ c (relative cubeTo), implicit
+55 80 +0.328125
+f5 7f -0.046875
+95 80 +0.578125
+f9 7f -0.03125
+bd 80 +0.734375
+fd 7f -0.015625
+ c (relative cubeTo), implicit
+15 80 +0.078125
+05 80 +0.015625
+21 80 +0.125
+09 80 +0.03125
+29 80 +0.15625
+09 80 +0.03125
+ c (relative cubeTo), implicit
+80 +0
+80 +0
+80 +0
+80 +0
+80 +0
+80 +0
+d0 a (relative arcTo), 1 reps
+65 80 +0.390625
+65 80 +0.390625
+00 0 Γ— 360 degrees (0 degrees)
+00 0x0 (largeArc=0, sweep=0)
+8d 80 +0.546875
+71 7f -0.5625
+b5 c (relative cubeTo), 6 reps
+80 +0
+80 +0
+f5 7f -0.046875
+f1 7f -0.0625
+f5 7f -0.046875
+f1 7f -0.0625
+ c (relative cubeTo), implicit
+80 +0
+80 +0
+f9 7f -0.03125
+fd 7f -0.015625
+f9 7f -0.03125
+f9 7f -0.03125
+ c (relative cubeTo), implicit
+fd 7f -0.015625
+fd 7f -0.015625
+f9 7f -0.03125
+f9 7f -0.03125
+f5 7f -0.046875
+f9 7f -0.03125
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+fd 7f -0.015625
+f5 7f -0.046875
+fd 7f -0.015625
+f1 7f -0.0625
+f9 7f -0.03125
+ c (relative cubeTo), implicit
+f9 7f -0.03125
+fd 7f -0.015625
+f1 7f -0.0625
+f9 7f -0.03125
+e5 7f -0.109375
+f9 7f -0.03125
+ c (relative cubeTo), implicit
+ed 7f -0.078125
+f9 7f -0.03125
+d5 7f -0.171875
+f5 7f -0.046875
+b5 7f -0.296875
+f1 7f -0.0625
+e1 z (closePath); end path
+80 Set CREG[CSEL-0] to a 1 byte color
+7c RGBA ffffffff
+91 Set CREG[CSEL-1] to a 3 byte (direct) color
+17 13 11 RGBA 171311ff
+9a Set CREG[CSEL-2] to a 4 byte color
+00 00 00 54 RGBA 00000054
+93 Set CREG[CSEL-3] to a 3 byte (direct) color
+ff fc fb RGBA fffcfbff
+94 Set CREG[CSEL-4] to a 3 byte (direct) color
+c3 8c 74 RGBA c38c74ff
+95 Set CREG[CSEL-5] to a 3 byte (direct) color
+23 20 1f RGBA 23201fff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+59 7e -1.65625
+fd 73 -12.015625
+d3 a (relative arcTo), 4 reps
+a9 89 +9.65625
+a9 89 +9.65625
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+59 76 -9.65625
+a9 89 +9.65625
+ a (relative arcTo), implicit
+a9 89 +9.65625
+a9 89 +9.65625
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+59 76 -9.65625
+59 76 -9.65625
+ a (relative arcTo), implicit
+a9 89 +9.65625
+a9 89 +9.65625
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+a9 89 +9.65625
+59 76 -9.65625
+ a (relative arcTo), implicit
+a9 89 +9.65625
+a9 89 +9.65625
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+a9 89 +9.65625
+a9 89 +9.65625
+e1 z (closePath); end path
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a9 93 +19.65625
+fd 73 -12.015625
+d3 a (relative arcTo), 4 reps
+51 89 +9.3125
+51 89 +9.3125
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+b1 76 -9.3125
+51 89 +9.3125
+ a (relative arcTo), implicit
+51 89 +9.3125
+51 89 +9.3125
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+b1 76 -9.3125
+b1 76 -9.3125
+ a (relative arcTo), implicit
+51 89 +9.3125
+51 89 +9.3125
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+51 89 +9.3125
+b1 76 -9.3125
+ a (relative arcTo), implicit
+51 89 +9.3125
+51 89 +9.3125
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+51 89 +9.3125
+51 89 +9.3125
+e1 z (closePath); end path
+c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
+c1 7c -3.25
+fd 73 -12.015625
+d3 a (relative arcTo), 4 reps
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+15 7c -3.921875
+ed 83 +3.921875
+ a (relative arcTo), implicit
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+15 7c -3.921875
+15 7c -3.921875
+ a (relative arcTo), implicit
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+ed 83 +3.921875
+15 7c -3.921875
+ a (relative arcTo), implicit
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+ed 83 +3.921875
+ed 83 +3.921875
+e1 z (closePath); end path
+c1 Start path, filled with CREG[CSEL-1]; M (absolute moveTo)
+25 92 +18.140625
+fd 73 -12.015625
+d3 a (relative arcTo), 4 reps
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+15 7c -3.921875
+ed 83 +3.921875
+ a (relative arcTo), implicit
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+15 7c -3.921875
+15 7c -3.921875
+ a (relative arcTo), implicit
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+ed 83 +3.921875
+15 7c -3.921875
+ a (relative arcTo), implicit
+ed 83 +3.921875
+ed 83 +3.921875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+ed 83 +3.921875
+ed 83 +3.921875
+e1 z (closePath); end path
+c2 Start path, filled with CREG[CSEL-2]; M (absolute moveTo)
+b1 7d -2.3125
+1d 7f -0.890625
+e7 h (relative horizontal lineTo)
+71 84 +4.4375
+b0 c (relative cubeTo), 1 reps
+cd 80 +0.796875
+80 +0
+75 81 +1.453125
+a5 80 +0.640625
+75 81 +1.453125
+75 81 +1.453125
+e9 v (relative vertical lineTo)
+51 85 +5.3125
+b0 c (relative cubeTo), 1 reps
+80 +0
+cd 80 +0.796875
+5d 7f -0.640625
+75 81 +1.453125
+8d 7e -1.453125
+75 81 +1.453125
+e7 h (relative horizontal lineTo)
+91 7b -4.4375
+b0 c (relative cubeTo), 1 reps
+35 7f -0.796875
+80 +0
+8d 7e -1.453125
+5d 7f -0.640625
+8d 7e -1.453125
+8d 7e -1.453125
+e9 v (relative vertical lineTo)
+b1 7a -5.3125
+b0 c (relative cubeTo), 1 reps
+80 +0
+35 7f -0.796875
+a5 80 +0.640625
+8d 7e -1.453125
+75 81 +1.453125
+8d 7e -1.453125
+e1 z (closePath); end path
+c3 Start path, filled with CREG[CSEL-3]; M (absolute moveTo)
+b1 7d -2.3125
+b9 7d -2.28125
+e7 h (relative horizontal lineTo)
+71 84 +4.4375
+b0 c (relative cubeTo), 1 reps
+cd 80 +0.796875
+80 +0
+75 81 +1.453125
+a5 80 +0.640625
+75 81 +1.453125
+75 81 +1.453125
+e9 v (relative vertical lineTo)
+51 85 +5.3125
+b0 c (relative cubeTo), 1 reps
+80 +0
+cd 80 +0.796875
+5d 7f -0.640625
+75 81 +1.453125
+8d 7e -1.453125
+75 81 +1.453125
+e7 h (relative horizontal lineTo)
+91 7b -4.4375
+b0 c (relative cubeTo), 1 reps
+35 7f -0.796875
+80 +0
+8d 7e -1.453125
+5d 7f -0.640625
+8d 7e -1.453125
+8d 7e -1.453125
+e9 v (relative vertical lineTo)
+b1 7a -5.3125
+b0 c (relative cubeTo), 1 reps
+80 +0
+35 7f -0.796875
+a5 80 +0.640625
+8d 7e -1.453125
+75 81 +1.453125
+8d 7e -1.453125
+e1 z (closePath); end path
+c2 Start path, filled with CREG[CSEL-2]; M (absolute moveTo)
+90 +8
+55 80 +0.328125
+b7 c (relative cubeTo), 8 reps
+80 +0
+ad 81 +1.671875
+41 7e -1.75
+89 82 +2.53125
+b5 7b -4.296875
+55 82 +2.328125
+ c (relative cubeTo), implicit
+f5 7f -0.046875
+80 +0
+e9 7f -0.09375
+cd 7e -1.203125
+7d 7f -0.515625
+d5 7e -1.171875
+ c (relative cubeTo), implicit
+7e -1
+15 80 +0.078125
+d1 7d -2.1875
+f5 7f -0.046875
+a1 7c -3.375
+f5 7f -0.046875
+ c (relative cubeTo), implicit
+c9 7e -1.21875
+80 +0
+99 7d -2.40625
+5d 80 +0.359375
+a1 7c -3.375
+09 80 +0.03125
+ c (relative cubeTo), implicit
+c9 7f -0.21875
+ed 7f -0.078125
+a5 7f -0.359375
+2d 81 +1.171875
+7d 7f -0.515625
+29 81 +1.15625
+ c (relative cubeTo), implicit
+85 7d -2.484375
+f1 7f -0.0625
+d9 7b -4.15625
+55 7f -0.671875
+d9 7b -4.15625
+ad 7d -2.328125
+ c (relative cubeTo), implicit
+80 +0
+95 7d -2.421875
+a1 83 +3.625
+9d 7b -4.390625
+1d 88 +8.109375
+9d 7b -4.390625
+ c (relative cubeTo), implicit
+7d 84 +4.484375
+80 +0
+1d 88 +8.109375
+f9 81 +1.96875
+1d 88 +8.109375
+65 84 +4.390625
+e1 z (closePath); end path
+c4 Start path, filled with CREG[CSEL-4]; M (absolute moveTo)
+f5 87 +7.953125
+ad 7e -1.328125
+b6 c (relative cubeTo), 7 reps
+80 +0
+25 81 +1.140625
+21 7f -0.875
+b5 81 +1.703125
+b9 7d -2.28125
+84 +2
+ c (relative cubeTo), implicit
+6d 7f -0.578125
+21 80 +0.125
+bd 7e -1.265625
+31 80 +0.1875
+7c -2
+3d 80 +0.234375
+ c (relative cubeTo), implicit
+e1 7e -1.125
+11 80 +0.0625
+95 7d -2.421875
+11 80 +0.0625
+39 7c -3.78125
+11 80 +0.0625
+ c (relative cubeTo), implicit
+a9 7e -1.34375
+80 +0
+65 7d -2.609375
+80 +0
+45 7c -3.734375
+f1 7f -0.0625
+ c (relative cubeTo), implicit
+65 7f -0.609375
+f9 7f -0.03125
+d5 7e -1.171875
+e9 7f -0.09375
+51 7e -1.6875
+d5 7f -0.171875
+ c (relative cubeTo), implicit
+61 7e -1.625
+bd 7f -0.265625
+59 7d -2.65625
+2d 7f -0.828125
+59 7d -2.65625
+f1 7d -2.0625
+ c (relative cubeTo), implicit
+80 +0
+b1 7d -2.3125
+9d 83 +3.609375
+cd 7b -4.203125
+11 88 +8.0625
+cd 7b -4.203125
+90 s (relative smooth cubeTo), 1 reps
+11 88 +8.0625
+e1 81 +1.875
+11 88 +8.0625
+35 84 +4.203125
+e1 z (closePath); end path
+c5 Start path, filled with CREG[CSEL-5]; M (absolute moveTo)
+0d 84 +4.046875
+f9 7a -5.03125
+d3 a (relative arcTo), 4 reps
+29 84 +4.15625
+b1 82 +2.6875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+d9 7b -4.15625
+b1 82 +2.6875
+ a (relative arcTo), implicit
+29 84 +4.15625
+b1 82 +2.6875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+d9 7b -4.15625
+51 7d -2.6875
+ a (relative arcTo), implicit
+29 84 +4.15625
+b1 82 +2.6875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+29 84 +4.15625
+51 7d -2.6875
+ a (relative arcTo), implicit
+29 84 +4.15625
+b1 82 +2.6875
+00 0 Γ— 360 degrees (0 degrees)
+04 0x2 (largeArc=0, sweep=1)
+29 84 +4.15625
+b1 82 +2.6875
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/favicon.pink.png b/shiny/iconvg/testdata/favicon.pink.png
new file mode 100644
index 0000000..862c538
--- /dev/null
+++ b/shiny/iconvg/testdata/favicon.pink.png
Binary files differ
diff --git a/shiny/iconvg/testdata/favicon.png b/shiny/iconvg/testdata/favicon.png
new file mode 100644
index 0000000..442e237
--- /dev/null
+++ b/shiny/iconvg/testdata/favicon.png
Binary files differ
diff --git a/shiny/iconvg/testdata/favicon.svg b/shiny/iconvg/testdata/favicon.svg
new file mode 100644
index 0000000..81339cf
--- /dev/null
+++ b/shiny/iconvg/testdata/favicon.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg id="svg4416" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 32 32.000001">
+ <path id="Z00" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m16.092 1.002c-1.1057.01-2.2107.048844-3.3164.089844-2.3441.086758-4.511.88464-6.2832 2.1758a3.8208 3.5794 29.452 0 0 -.8947 -.6856 3.8208 3.5794 29.452 0 0 -5.0879 1.2383 3.8208 3.5794 29.452 0 0 1.5664 4.9961 3.8208 3.5794 29.452 0 0 .3593 .1758c-.2784.9536-.4355 1.9598-.4355 3.0078v20h28v-20c0-1.042-.152-2.0368-.418-2.9766a3.5794 3.8208 60.548 0 0 .43359 -.20703 3.5794 3.8208 60.548 0 0 1.5684 -4.9961 3.5794 3.8208 60.548 0 0 -5.0879 -1.2383 3.5794 3.8208 60.548 0 0 -.92969 .72461c-1.727-1.257-3.843-2.0521-6.1562-2.2148-1.1058-.078-2.2126-.098844-3.3184-.089844z" fill="#384e54"/>
+ <path id="Z01" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m16 3c-4.835 0-7.9248 1.0791-9.7617 2.8906-.4777-.4599-1.2937-1.0166-1.6309-1.207-.9775-.5520-2.1879-.2576-2.7051.6582-.5171.9158-.1455 2.1063.8321 2.6582.2658.1501 1.2241.5845 1.7519.7441-.3281.9946-.4863 2.0829-.4863 3.2559v20h24c-.049-7.356 0-18 0-20 0-1.209-.166-2.3308-.516-3.3496.539-.2011 1.243-.5260 1.463-.6504.978-.5519 1.351-1.7424.834-2.6582s-1.729-1.2102-2.707-.6582c-.303.1711-.978.6356-1.463 1.0625-1.854-1.724-4.906-2.7461-9.611-2.7461z" fill="#76e1fe"/>
+ <path id="Z02" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m3.0918 5.9219c-.060217.00947-.10772.020635-.14648.033203-.019384.00628-.035462.013581-.052734.021484-.00864.00395-.019118.00825-.03125.015625-.00607.00369-.011621.00781-.021484.015625-.00493.00391-.017342.015389-.017578.015625-.0002366.0002356-.025256.031048-.025391.03125a.19867 .19867 0 0 0 .26367 .28320c.0005595-.0002168.00207-.00128.00391-.00195a.19867 .19867 0 0 0 .00391 -.00195c.015939-.00517.045148-.013113.085937-.019531.081581-.012836.20657-.020179.36719.00391.1020.0152.2237.0503.3535.0976-.3277.0694-.5656.1862-.7227.3145-.1143.0933-.1881.1903-.2343.2695-.023099.0396-.039499.074216-.050781.10547-.00564.015626-.00989.029721-.013672.046875-.00189.00858-.00458.017085-.00586.03125-.0006392.00708-.0005029.014724 0 .027344.0002516.00631.00192.023197.00195.023437.0000373.0002412.0097.036937.00977.037109a.19867 .19867 0 0 0 .38477 -.039063 .19867 .19867 0 0 0 0 -.00195c.00312-.00751.00865-.015947.017578-.03125.0230-.0395.0660-.0977.1425-.1601.1530-.1250.4406-.2702.9863-.2871a.19930 .19930 0 0 0 .082031 -.019531c.12649.089206.25979.19587.39844.32422a.19867 .19867 0 1 0 .2696 -.2911c-.6099-.5646-1.1566-.7793-1.5605-.8398-.2020-.0303-.3679-.0229-.4883-.0039z" fill-rule="evenodd" fill="#384e54"/>
+ <path id="Z03" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m28.543 5.8203c-.12043-.018949-.28631-.026379-.48828.00391-.40394.060562-.94869.27524-1.5586.83984a.19867 .19867 0 1 0 .26953 .29102c.21354-.19768.40814-.33222.59180-.44141.51624.023399.79659.16181.94531.28320.07652.062461.11952.12063.14258.16016.0094.016037.01458.025855.01758.033203a.19867 .19867 0 0 0 .38476 .039063c.000062-.0001719.0097-.036868.0098-.037109.000037-.0002412.0017-.017125.002-.023437.000505-.012624.000639-.020258 0-.027344-.0013-.01417-.004-.022671-.0059-.03125-.0038-.017158-.008-.031248-.01367-.046875-.01128-.031254-.02768-.067825-.05078-.10742-.04624-.079195-.12003-.17424-.23437-.26758-.11891-.097066-.28260-.18832-.49609-.25781.01785-.00328.03961-.011119.05664-.013672.16062-.024082.28561-.016738.36719-.00391.03883.00611.06556.012409.08203.017578.000833.0002613.0031.0017.0039.00195a.19867 .19867 0 0 0 .271 -.2793c-.000135-.0002016-.02515-.031014-.02539-.03125-.000236-.0002356-.01265-.011717-.01758-.015625-.0099-.00782-.01737-.01194-.02344-.015625-.01213-.00737-.02066-.011673-.0293-.015625-.01727-.0079-.03336-.013247-.05273-.019531-.03877-.012568-.08822-.025682-.14844-.035156z" fill-rule="evenodd" fill="#384e54"/>
+ <path id="Z04" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m15.171 9.992a4.8316 4.8316 0 0 1 -4.832 4.832 4.8316 4.8316 0 0 1 -4.8311 -4.832 4.8316 4.8316 0 0 1 4.8311 -4.8316 4.8316 4.8316 0 0 1 4.832 4.8316z" fill="#fff"/>
+ <path id="Z05" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m25.829 9.992a4.6538 4.6538 0 0 1 -4.653 4.654 4.6538 4.6538 0 0 1 -4.654 -4.654 4.6538 4.6538 0 0 1 4.654 -4.6537 4.6538 4.6538 0 0 1 4.653 4.6537z" fill="#fff"/>
+ <path id="Z06" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m14.377 9.992a1.9631 1.9631 0 0 1 -1.963 1.963 1.9631 1.9631 0 0 1 -1.963 -1.963 1.9631 1.9631 0 0 1 1.963 -1.963 1.9631 1.9631 0 0 1 1.963 1.963z" fill="#171311"/>
+ <path id="Z07" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m25.073 9.992a1.9631 1.9631 0 0 1 -1.963 1.963 1.9631 1.9631 0 0 1 -1.963 -1.963 1.9631 1.9631 0 0 1 1.963 -1.963 1.9631 1.9631 0 0 1 1.963 1.963z" fill="#171311"/>
+ <path id="Z08" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m14.842 15.555h2.2156c.40215 0 .72590.3237.72590.7259v2.6545c0 .4021-.32375.7259-.72590.7259h-2.2156c-.40215 0-.72590-.3238-.72590-.7259v-2.6545c0-.4022.32375-.7259.72590-.7259z" fill-opacity=".32941"/>
+ <path id="Z09" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m14.842 14.863h2.2156c.40215 0 .72590.3238.72590.7259v2.6546c0 .4021-.32375.7259-.72590.7259h-2.2156c-.40215 0-.72590-.3238-.72590-.7259v-2.6546c0-.4021.32375-.7259.72590-.7259z" fill="#fffcfb"/>
+ <path id="Z10" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m20 16.167c0 .838-.87123 1.2682-2.1448 1.1659-.02366 0-.04795-.6004-.25415-.5832-.50367.042-1.0959-.02-1.686-.02-.61294 0-1.2063.1826-1.6855.017-.11023-.038-.17830.5838-.26153.5816-1.2437-.033-2.0788-.3383-2.0788-1.1618 0-1.2118 1.8156-2.1941 4.0554-2.1941 2.2397 0 4.0554.9823 4.0554 2.1941z" fill-opacity=".32941"/>
+ <path id="Z11" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m19.977 15.338c0 .5685-.43366.8554-1.1381 1.0001-.29193.06-.63037.096-1.0037.1166-.56405.032-1.2078.031-1.8912.031-.67283 0-1.3072 0-1.8649-.029-.30627-.017-.58943-.043-.84316-.084-.81383-.1318-1.325-.417-1.325-1.0344 0-1.1601 1.8056-2.1006 4.033-2.1006s4.033.9405 4.033 2.1006z" fill="#c38c74"/>
+ <path id="Z12" style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" d="m18.025 13.488a2.0802 1.3437 0 0 1 -2.0802 1.3437 2.0802 1.3437 0 0 1 -2.0802 -1.3437 2.0802 1.3437 0 0 1 2.0802 -1.3437 2.0802 1.3437 0 0 1 2.0802 1.3437z" fill="#23201f"/>
+</svg>
diff --git a/shiny/iconvg/testdata/gradient.ivg b/shiny/iconvg/testdata/gradient.ivg
new file mode 100644
index 0000000..865067b
--- /dev/null
+++ b/shiny/iconvg/testdata/gradient.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/gradient.ivg.disassembly b/shiny/iconvg/testdata/gradient.ivg.disassembly
new file mode 100644
index 0000000..162cc57
--- /dev/null
+++ b/shiny/iconvg/testdata/gradient.ivg.disassembly
@@ -0,0 +1,186 @@
+89 49 56 47 IconVG Magic identifier
+00 Number of metadata chunks: 0
+98 Set CREG[CSEL-0] to a 4 byte color
+04 0a 8a 00 gradient (NSTOPS=4, CBASE=10, NBASE=10, linear, none)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+8b 88 08 3d 0.03333333
+ad Set NREG[NSEL-5] to a real number
+8b 88 88 3c 0.016666666
+ac Set NREG[NSEL-4] to a real number
+6b 66 66 3f 0.9000001
+ab Set NREG[NSEL-3] to a real number
+00 0
+aa Set NREG[NSEL-2] to a real number
+00 0
+a9 Set NREG[NSEL-1] to a real number
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+64 RGBA ff0000ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+14 RGBA 00ff00ff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+3c 0.25
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+04 RGBA 0000ffff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+78 0.5
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+00 RGBA 000000ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+44 -30
+44 -30
+e6 H (absolute horizontal lineTo)
+bc +30
+e8 V (absolute vertical lineTo)
+5c -18
+e6 H (absolute horizontal lineTo)
+44 -30
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+05 4a 8a 00 gradient (NSTOPS=5, CBASE=10, NBASE=10, linear, pad)
+0a Set CSEL = 10
+4a Set NSEL = 10
+ae Set NREG[NSEL-6] to a real number
+8b 88 08 3d 0.03333333
+ad Set NREG[NSEL-5] to a real number
+8b 88 88 3c 0.016666666
+ac Set NREG[NSEL-4] to a real number
+27 22 22 3f 0.63333344
+ab Set NREG[NSEL-3] to a real number
+00 0
+aa Set NREG[NSEL-2] to a real number
+00 0
+a9 Set NREG[NSEL-1] to a real number
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+18 RGBA 00ffffff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+7c RGBA ffffffff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+3c 0.25
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+68 RGBA ff00ffff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+78 0.5
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+7f RGBA 00000000
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+b4 0.75
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+78 RGBA ffff00ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+44 -30
+64 -14
+e6 H (absolute horizontal lineTo)
+bc +30
+e8 V (absolute vertical lineTo)
+7c -2
+e6 H (absolute horizontal lineTo)
+44 -30
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+04 8a ca 00 gradient (NSTOPS=4, CBASE=10, NBASE=10, radial, reflect)
+0a Set CSEL = 10
+4a Set NSEL = 10
+b6 Set NREG[NSEL-6] to a coordinate number
+11 80 0.0625
+ad Set NREG[NSEL-5] to a real number
+00 0
+bc Set NREG[NSEL-4] to a zero-to-one number
+78 0.5
+ab Set NREG[NSEL-3] to a real number
+00 0
+b2 Set NREG[NSEL-2] to a coordinate number
+11 80 0.0625
+b1 Set NREG[NSEL-1] to a coordinate number
+81 7f -0.5
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+64 RGBA ff0000ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+14 RGBA 00ff00ff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+3c 0.25
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+04 RGBA 0000ffff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+78 0.5
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+00 RGBA 000000ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+44 -30
+84 +2
+e6 H (absolute horizontal lineTo)
+bc +30
+e8 V (absolute vertical lineTo)
+9c +14
+e6 H (absolute horizontal lineTo)
+44 -30
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+05 ca ca 00 gradient (NSTOPS=5, CBASE=10, NBASE=10, radial, repeat)
+0a Set CSEL = 10
+4a Set NSEL = 10
+b6 Set NREG[NSEL-6] to a coordinate number
+11 80 0.0625
+ad Set NREG[NSEL-5] to a real number
+00 0
+bc Set NREG[NSEL-4] to a zero-to-one number
+78 0.5
+ab Set NREG[NSEL-3] to a real number
+00 0
+b2 Set NREG[NSEL-2] to a coordinate number
+11 80 0.0625
+b1 Set NREG[NSEL-1] to a coordinate number
+81 7e -1.5
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+18 RGBA 00ffffff
+af Set NREG[NSEL-0] to a real number; NSEL++
+00 0
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+7c RGBA ffffffff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+3c 0.25
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+68 RGBA ff00ffff
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+78 0.5
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+7f RGBA 00000000
+bf Set NREG[NSEL-0] to a zero-to-one number; NSEL++
+b4 0.75
+87 Set CREG[CSEL-0] to a 1 byte color; CSEL++
+78 RGBA ffff00ff
+af Set NREG[NSEL-0] to a real number; NSEL++
+02 1
+00 Set CSEL = 0
+40 Set NSEL = 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+44 -30
+a4 +18
+e6 H (absolute horizontal lineTo)
+bc +30
+e8 V (absolute vertical lineTo)
+bc +30
+e6 H (absolute horizontal lineTo)
+44 -30
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/gradient.png b/shiny/iconvg/testdata/gradient.png
new file mode 100644
index 0000000..7b4f028
--- /dev/null
+++ b/shiny/iconvg/testdata/gradient.png
Binary files differ
diff --git a/shiny/iconvg/testdata/lod-polygon.64.png b/shiny/iconvg/testdata/lod-polygon.64.png
new file mode 100644
index 0000000..7a7b6c7
--- /dev/null
+++ b/shiny/iconvg/testdata/lod-polygon.64.png
Binary files differ
diff --git a/shiny/iconvg/testdata/lod-polygon.ivg b/shiny/iconvg/testdata/lod-polygon.ivg
new file mode 100644
index 0000000..a128b5d
--- /dev/null
+++ b/shiny/iconvg/testdata/lod-polygon.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/lod-polygon.ivg.disassembly b/shiny/iconvg/testdata/lod-polygon.ivg.disassembly
new file mode 100644
index 0000000..a61bb71
--- /dev/null
+++ b/shiny/iconvg/testdata/lod-polygon.ivg.disassembly
@@ -0,0 +1,53 @@
+89 49 56 47 IconVG Magic identifier
+00 Number of metadata chunks: 0
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+48 -28
+58 -20
+e8 V (absolute vertical lineTo)
+48 -28
+e6 H (absolute horizontal lineTo)
+58 -20
+e1 z (closePath); end path
+c7 Set LOD
+00 +0
+a0 +80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+b8 +28
+80 +0
+01 L (absolute lineTo), 2 reps
+64 -14
+41 98 +24.25
+ L (absolute lineTo), implicit
+64 -14
+c1 67 -24.25
+e1 z (closePath); end path
+c7 Set LOD
+a0 +80
+03 00 80 7f +Inf
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+b8 +28
+80 +0
+03 L (absolute lineTo), 4 reps
+a9 88 +8.65625
+a1 9a +26.625
+ L (absolute lineTo), implicit
+59 69 -22.65625
+75 90 +16.453125
+ L (absolute lineTo), implicit
+59 69 -22.65625
+8d 6f -16.453125
+ L (absolute lineTo), implicit
+a9 88 +8.65625
+61 65 -26.625
+e1 z (closePath); end path
+c7 Set LOD
+00 +0
+03 00 80 7f +Inf
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+b8 +28
+a8 +20
+e8 V (absolute vertical lineTo)
+b8 +28
+e6 H (absolute horizontal lineTo)
+a8 +20
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/lod-polygon.png b/shiny/iconvg/testdata/lod-polygon.png
new file mode 100644
index 0000000..a095712
--- /dev/null
+++ b/shiny/iconvg/testdata/lod-polygon.png
Binary files differ
diff --git a/shiny/iconvg/testdata/video-005.jpeg b/shiny/iconvg/testdata/video-005.jpeg
new file mode 100644
index 0000000..3237158
--- /dev/null
+++ b/shiny/iconvg/testdata/video-005.jpeg
Binary files differ
diff --git a/shiny/iconvg/testdata/video-005.primitive.ivg b/shiny/iconvg/testdata/video-005.primitive.ivg
new file mode 100644
index 0000000..098c928
--- /dev/null
+++ b/shiny/iconvg/testdata/video-005.primitive.ivg
Binary files differ
diff --git a/shiny/iconvg/testdata/video-005.primitive.ivg.disassembly b/shiny/iconvg/testdata/video-005.primitive.ivg.disassembly
new file mode 100644
index 0000000..39c2521
--- /dev/null
+++ b/shiny/iconvg/testdata/video-005.primitive.ivg.disassembly
@@ -0,0 +1,380 @@
+89 49 56 47 IconVG Magic identifier
+02 Number of metadata chunks: 1
+0a Metadata chunk length: 5
+00 Metadata Identifier: 0 (viewBox)
+40 -32
+50 -24
+c0 +32
+b0 +24
+90 Set CREG[CSEL-0] to a 3 byte (direct) color
+7c 7e 7c RGBA 7c7e7cff
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+40 -32
+50 -24
+e6 H (absolute horizontal lineTo)
+c0 +32
+e8 V (absolute vertical lineTo)
+b0 +24
+e6 H (absolute horizontal lineTo)
+40 -32
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+0b 03 02 80 RGBA 0b030280
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 88 +8.625
+e1 9b +27.875
+01 L (absolute lineTo), 2 reps
+e1 a3 +35.875
+a1 96 +22.625
+ L (absolute lineTo), implicit
+e1 90 +16.875
+21 64 -27.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+74 7a 7c 80 RGBA 747a7c80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 5c -35.875
+e1 94 +20.875
+01 L (absolute lineTo), 2 reps
+21 83 +3.125
+61 65 -26.625
+ L (absolute lineTo), implicit
+21 64 -27.875
+21 66 -25.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 02 13 80 RGBA 00021380
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 78 -7.625
+21 80 +0.125
+01 L (absolute lineTo), 2 reps
+61 97 +23.375
+61 6d -18.625
+ L (absolute lineTo), implicit
+a1 95 +21.625
+e1 83 +3.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+44 6c 80 80 RGBA 446c8080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 a1 +33.625
+a1 66 -25.375
+01 L (absolute lineTo), 2 reps
+e1 a3 +35.875
+21 82 +2.125
+ L (absolute lineTo), implicit
+21 89 +9.125
+21 64 -27.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+4a 5e 62 80 RGBA 4a5e6280
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 93 +19.125
+21 82 +2.125
+01 L (absolute lineTo), 2 reps
+21 89 +9.125
+e1 9b +27.875
+ L (absolute lineTo), implicit
+e1 6e -17.125
+21 82 +2.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+6a 40 1e 80 RGBA 6a401e80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 5c -35.875
+21 71 -14.875
+01 L (absolute lineTo), 2 reps
+e1 7e -1.125
+e1 98 +24.875
+ L (absolute lineTo), implicit
+21 5c -35.875
+a1 98 +24.625
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 00 00 80 RGBA 00000080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 89 +9.125
+e1 6c -19.125
+01 L (absolute lineTo), 2 reps
+e1 77 -8.125
+61 7b -4.625
+ L (absolute lineTo), implicit
+a1 82 +2.625
+61 6b -20.625
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+1c 08 0c 80 RGBA 1c080c80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 6c -19.375
+e1 8b +11.875
+01 L (absolute lineTo), 2 reps
+e1 7c -3.125
+61 96 +22.375
+ L (absolute lineTo), implicit
+21 5f -32.875
+61 91 +17.375
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 1e 40 80 RGBA 001e4080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 75 -10.375
+61 83 +3.375
+01 L (absolute lineTo), 2 reps
+61 6d -18.625
+21 7b -4.875
+ L (absolute lineTo), implicit
+a1 76 -9.375
+21 6e -17.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+7e 63 4e 80 RGBA 7e634e80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 67 -24.125
+61 90 +16.375
+01 L (absolute lineTo), 2 reps
+21 74 -11.875
+61 82 +2.375
+ L (absolute lineTo), implicit
+21 5c -35.875
+21 6f -16.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+4f 6e 80 80 RGBA 4f6e8080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 92 +18.375
+61 66 -25.625
+01 L (absolute lineTo), 2 reps
+e1 67 -24.125
+21 64 -27.875
+ L (absolute lineTo), implicit
+a1 60 -31.375
+21 77 -8.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 10 1c 80 RGBA 00101c80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 81 +1.125
+61 7d -2.625
+01 L (absolute lineTo), 2 reps
+21 9c +28.125
+e1 66 -25.125
+ L (absolute lineTo), implicit
+61 8b +11.375
+a1 88 +8.625
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+7e 5e 47 80 RGBA 7e5e4780
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 90 +16.375
+e1 87 +7.875
+01 L (absolute lineTo), 2 reps
+e1 99 +25.875
+a1 7f -0.375
+ L (absolute lineTo), implicit
+a1 9e +30.625
+21 87 +7.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+21 03 00 80 RGBA 21030080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 9e +30.875
+e1 9b +27.875
+01 L (absolute lineTo), 2 reps
+61 9b +27.375
+e1 7c -3.125
+ L (absolute lineTo), implicit
+e1 a3 +35.875
+61 80 +0.375
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+40 5f 77 80 RGBA 405f7780
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 7d -2.625
+a1 89 +9.625
+01 L (absolute lineTo), 2 reps
+21 76 -9.875
+61 94 +20.375
+ L (absolute lineTo), implicit
+a1 76 -9.375
+21 6f -16.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 00 00 80 RGBA 00000080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 7f -0.125
+a1 71 -14.375
+01 L (absolute lineTo), 2 reps
+21 8b +11.125
+21 79 -6.875
+ L (absolute lineTo), implicit
+e1 97 +23.875
+e1 75 -10.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+0c 07 0b 80 RGBA 0c070b80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 92 +18.375
+21 9b +27.125
+01 L (absolute lineTo), 2 reps
+61 88 +8.375
+61 81 +1.375
+ L (absolute lineTo), implicit
+e1 a3 +35.875
+21 98 +24.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+7b 55 38 80 RGBA 7b553880
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 92 +18.375
+21 91 +17.125
+01 L (absolute lineTo), 2 reps
+a1 98 +24.625
+61 8b +11.375
+ L (absolute lineTo), implicit
+61 a1 +33.375
+21 8e +14.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+70 1b 00 80 RGBA 701b0080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 5c -35.875
+a1 67 -24.375
+01 L (absolute lineTo), 2 reps
+61 67 -24.625
+21 64 -27.875
+ L (absolute lineTo), implicit
+a1 5e -33.375
+a1 76 -9.375
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+80 72 5d 80 RGBA 80725d80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 84 +4.625
+61 73 -12.625
+01 L (absolute lineTo), 2 reps
+a1 7d -2.375
+e1 7a -5.125
+ L (absolute lineTo), implicit
+21 85 +5.125
+21 7b -4.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 00 09 80 RGBA 00000980
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 7d -2.375
+21 73 -12.875
+01 L (absolute lineTo), 2 reps
+e1 7a -5.125
+61 83 +3.375
+ L (absolute lineTo), implicit
+21 79 -6.875
+e1 74 -11.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+5e 6a 72 80 RGBA 5e6a7280
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 a3 +35.875
+61 72 -13.625
+01 L (absolute lineTo), 2 reps
+61 9f +31.375
+21 64 -27.875
+ L (absolute lineTo), implicit
+e1 94 +20.875
+61 7e -1.625
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+29 00 00 80 RGBA 29000080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 75 -10.125
+e1 87 +7.875
+01 L (absolute lineTo), 2 reps
+e1 74 -11.125
+a1 8d +13.625
+ L (absolute lineTo), implicit
+e1 6d -18.125
+e1 83 +3.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 59 50 80 RGBA 00595080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+21 7f -0.875
+61 96 +22.375
+01 L (absolute lineTo), 2 reps
+e1 81 +1.875
+e1 9b +27.875
+ L (absolute lineTo), implicit
+a1 90 +16.625
+21 94 +20.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+11 00 00 80 RGBA 11000080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 6e -17.125
+e1 8d +13.875
+01 L (absolute lineTo), 2 reps
+61 68 -23.625
+21 87 +7.125
+ L (absolute lineTo), implicit
+21 6d -18.875
+61 92 +18.375
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+5f 65 65 80 RGBA 5f656580
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 85 +5.375
+a1 72 -13.375
+01 L (absolute lineTo), 2 reps
+e1 8d +13.875
+21 64 -27.875
+ L (absolute lineTo), implicit
+a1 8c +12.625
+e1 73 -12.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+80 6a 58 80 RGBA 806a5880
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 94 +20.875
+e1 85 +5.875
+01 L (absolute lineTo), 2 reps
+21 8e +14.125
+21 81 +1.125
+ L (absolute lineTo), implicit
+a1 8d +13.625
+21 87 +7.125
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+80 70 1c 80 RGBA 80701c80
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+61 72 -13.625
+e1 9b +27.875
+01 L (absolute lineTo), 2 reps
+21 83 +3.125
+21 95 +21.125
+ L (absolute lineTo), implicit
+e1 5c -35.125
+e1 96 +22.875
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+53 58 56 80 RGBA 53585680
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+a1 7e -1.375
+61 95 +21.375
+01 L (absolute lineTo), 2 reps
+21 92 +18.125
+a1 95 +21.625
+ L (absolute lineTo), implicit
+61 77 -8.625
+a1 7c -3.375
+e1 z (closePath); end path
+98 Set CREG[CSEL-0] to a 4 byte color
+00 00 00 80 RGBA 00000080
+c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)
+e1 a3 +35.875
+21 92 +18.125
+01 L (absolute lineTo), 2 reps
+a1 8a +10.625
+61 96 +22.375
+ L (absolute lineTo), implicit
+61 97 +23.375
+e1 9b +27.875
+e1 z (closePath); end path
diff --git a/shiny/iconvg/testdata/video-005.primitive.png b/shiny/iconvg/testdata/video-005.primitive.png
new file mode 100644
index 0000000..35694ce
--- /dev/null
+++ b/shiny/iconvg/testdata/video-005.primitive.png
Binary files differ
diff --git a/shiny/iconvg/testdata/video-005.primitive.svg b/shiny/iconvg/testdata/video-005.primitive.svg
new file mode 100644
index 0000000..53be42c
--- /dev/null
+++ b/shiny/iconvg/testdata/video-005.primitive.svg
@@ -0,0 +1,35 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="256" height="192">
+<rect x="0" y="0" width="256" height="192" fill="#7c7e7c" />
+<g transform="translate(0.5 0.5)">
+<polygon fill="#170605" fill-opacity="0.501961" points="162,207 271,186 195,-16" />
+<polygon fill="#e9f5f8" fill-opacity="0.501961" points="-16,179 140,-11 16,-8" />
+<polygon fill="#000427" fill-opacity="0.501961" points="97,96 221,21 214,111" />
+<polygon fill="#89d9ff" fill-opacity="0.501961" points="262,-6 271,104 164,-16" />
+<polygon fill="#94bdc5" fill-opacity="0.501961" points="204,104 164,207 59,104" />
+<polygon fill="#d4813d" fill-opacity="0.501961" points="-16,36 123,195 -16,194" />
+<polygon fill="#000000" fill-opacity="0.501961" points="164,19 95,77 138,13" />
+<polygon fill="#391119" fill-opacity="0.501961" points="50,143 115,185 -4,165" />
+<polygon fill="#003d81" fill-opacity="0.501961" points="86,109 53,76 90,24" />
+<polygon fill="#fcc69c" fill-opacity="0.501961" points="31,161 80,105 -16,28" />
+<polygon fill="#9eddff" fill-opacity="0.501961" points="201,-7 31,-16 2,60" />
+<polygon fill="#012039" fill-opacity="0.501961" points="132,85 240,-5 173,130" />
+<polygon fill="#fdbc8f" fill-opacity="0.501961" points="193,127 231,94 250,124" />
+<polygon fill="#430600" fill-opacity="0.501961" points="251,207 237,83 271,97" />
+<polygon fill="#80bfee" fill-opacity="0.501961" points="117,134 88,177 90,28" />
+<polygon fill="#000000" fill-opacity="0.501961" points="127,38 172,68 223,55" />
+<polygon fill="#190e16" fill-opacity="0.501961" points="201,204 161,101 271,192" />
+<polygon fill="#f6aa71" fill-opacity="0.501961" points="201,164 226,141 261,152" />
+<polygon fill="#e03600" fill-opacity="0.501961" points="-16,-2 29,-16 -6,58" />
+<polygon fill="#ffe4ba" fill-opacity="0.501961" points="146,45 118,75 148,76" />
+<polygon fill="#000012" fill-opacity="0.501961" points="118,44 107,109 100,51" />
+<polygon fill="#bdd5e4" fill-opacity="0.501961" points="271,41 253,-16 211,89" />
+<polygon fill="#520000" fill-opacity="0.501961" points="87,127 83,150 55,111" />
+<polygon fill="#00b3a1" fill-opacity="0.501961" points="124,185 135,207 194,176" />
+<polygon fill="#220000" fill-opacity="0.501961" points="59,151 33,124 52,169" />
+<polygon fill="#becbcb" fill-opacity="0.501961" points="149,42 183,-16 178,47" />
+<polygon fill="#ffd4b1" fill-opacity="0.501961" points="211,119 184,100 182,124" />
+<polygon fill="#ffe139" fill-opacity="0.501961" points="73,207 140,180 -13,187" />
+<polygon fill="#a7b0ad" fill-opacity="0.501961" points="122,181 200,182 93,82" />
+<polygon fill="#000000" fill-opacity="0.501961" points="271,168 170,185 221,207" />
+</g>
+</svg>
diff --git a/shiny/iconvg/upgrade.go b/shiny/iconvg/upgrade.go
new file mode 100644
index 0000000..5cfdef0
--- /dev/null
+++ b/shiny/iconvg/upgrade.go
@@ -0,0 +1,1100 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "bytes"
+ "errors"
+ "image/color"
+ "math"
+)
+
+// UpgradeToFileFormatVersion1Options are the options to the
+// UpgradeToFileFormatVersion1 function.
+type UpgradeToFileFormatVersion1Options struct {
+ // ArcsExpandWithHighResolutionCoordinates is like the
+ // Encoder.HighResolutionCoordinates field. It controls whether to favor
+ // file size (false) or precision (true) when replacing File Format Version
+ // 0's arcs with cubic BΓ©zier curves.
+ ArcsExpandWithHighResolutionCoordinates bool
+}
+
+// UpgradeToFileFormatVersion1 upgrades IconVG data from the 2016 experimental
+// "File Format Version 0" to the 2021 "File Format Version 1".
+//
+// This package (golang.org/x/exp/shiny/iconvg) holds a decoder for FFV0,
+// including this function to convert from FFV0 to FFV1. Different packages
+// (github.com/google/iconvg/src/go/*) decode FFV1.
+//
+// Amongst some new features and other clean-ups, FFV1 sets up the capability
+// for animated vector graphics, therefore removing some FFV0 features (such as
+// arc segments) that can be hard to animate smoothly. The IconvG FFV1 format
+// and its design decisions are discussed at
+// https://github.com/google/iconvg/issues/4#issuecomment-874105547
+func UpgradeToFileFormatVersion1(v0 []byte, opts *UpgradeToFileFormatVersion1Options) (v1 []byte, retErr error) {
+ u := &upgrader{}
+ if opts != nil {
+ u.opts = *opts
+ }
+ for i := range u.creg {
+ u.creg[i] = upgradeColor{
+ typ: ColorTypePaletteIndex,
+ paletteIndex: uint8(i),
+ }
+ }
+
+ if !bytes.HasPrefix(v0, magicBytes) {
+ return nil, errInvalidMagicIdentifier
+ }
+ v1 = append(v1, "\x8AIVG"...)
+ v0 = v0[4:]
+
+ v1, v0, retErr = u.upgradeMetadata(v1, v0)
+ if retErr != nil {
+ return nil, retErr
+ }
+
+ v1, _, retErr = u.upgradeBytecode(v1, v0)
+ if retErr != nil {
+ return nil, retErr
+ }
+
+ return v1, nil
+}
+
+const (
+ upgradeVerbMoveTo = 0
+ upgradeVerbLineTo = 1
+ upgradeVerbQuadTo = 2
+ upgradeVerbCubeTo = 3
+)
+
+type upgrader struct {
+ opts UpgradeToFileFormatVersion1Options
+
+ // These fields hold the current path's geometry.
+ verbs []uint8
+ args [][2]float32
+
+ // These fields track most of the FFV0 virtual machine register state. The
+ // FFV1 register model is different enough that we don't just translate
+ // each FFV0 register-related opcode individually.
+ creg [64]upgradeColor
+ nreg [64]float32
+ csel uint32
+ nsel uint32
+ fill uint32
+
+ // These fields track the most recent color written to FFV1 register
+ // REGS[SEL+7] (and SEL is kept at 56). As a file size optimization, we
+ // don't have to emit the first half of "Set REGS[SEL+7] = etc; Use
+ // REGS[SEL+7]" if the register already holds the "etc" value.
+ regsSel7 color.RGBA
+ hasRegsSel7 bool
+
+ // calculatingJumpLOD is whether the upgrader.upgradeBytecode method is
+ // being called recursively. FFV0 sets a Level-Of-Detail filter that
+ // applies implicitly until the next SetLOD opcode (if any). FFV1 instead
+ // explicitly gives the number of opcodes to skip if outside the LOD range.
+ calculatingJumpLOD bool
+}
+
+func (u *upgrader) upgradeMetadata(v1 buffer, v0 buffer) (newV1 buffer, newV0 buffer, retErr error) {
+ nMetadataChunks, n := v0.decodeNatural()
+ if n == 0 {
+ return nil, nil, errInvalidNumberOfMetadataChunks
+ }
+ v1.encodeNaturalFFV1(nMetadataChunks)
+ v0 = v0[n:]
+
+ for ; nMetadataChunks > 0; nMetadataChunks-- {
+ length, n := v0.decodeNatural()
+ if n == 0 {
+ return nil, nil, errInvalidMetadataChunkLength
+ }
+ v0 = v0[n:]
+ if uint64(length) > uint64(len(v0)) {
+ return nil, nil, errInvalidMetadataChunkLength
+ }
+ upgrade, err := u.upgradeMetadataChunk(v0[:length])
+ if err != nil {
+ return nil, nil, err
+ }
+ v1.encodeNaturalFFV1(uint32(len(upgrade)))
+ v1 = append(v1, upgrade...)
+ v0 = v0[length:]
+ }
+ return v1, v0, nil
+}
+
+func (u *upgrader) upgradeMetadataChunk(v0 buffer) (v1 buffer, retErr error) {
+ mid, n := v0.decodeNatural()
+ if n == 0 {
+ return nil, errInvalidMetadataIdentifier
+ }
+ switch mid {
+ case midViewBox:
+ mid = ffv1MIDViewBox
+ case midSuggestedPalette:
+ mid = ffv1MIDSuggestedPalette
+ default:
+ return nil, errInvalidMetadataIdentifier
+ }
+ v1.encodeNaturalFFV1(mid)
+ v0 = v0[n:]
+
+ switch mid {
+ case ffv1MIDViewBox:
+ for i := 0; i < 4; i++ {
+ x, n := v0.decodeNatural()
+ if n == 0 {
+ return nil, errInvalidViewBox
+ }
+ v1.encodeNaturalFFV1(x)
+ v0 = v0[n:]
+ }
+ if len(v0) != 0 {
+ return nil, errInvalidViewBox
+ }
+
+ case ffv1MIDSuggestedPalette:
+ if len(v0) == 0 {
+ return nil, errInvalidSuggestedPalette
+ }
+ numColors := 1 + int(v0[0]&0x3f)
+ colorLength := 1 + int(v0[0]>>6)
+ v1 = append(v1, uint8(numColors-1))
+ v0 = v0[1:]
+ for i := 0; i < numColors; i++ {
+ c, n := Color{}, 0
+ switch colorLength {
+ case 1:
+ c, n = v0.decodeColor1()
+ case 2:
+ c, n = v0.decodeColor2()
+ case 3:
+ c, n = v0.decodeColor3Direct()
+ case 4:
+ c, n = v0.decodeColor4()
+ }
+ if n == 0 {
+ return nil, errInvalidSuggestedPalette
+ } else if (c.typ == ColorTypeRGBA) && validAlphaPremulColor(c.data) {
+ v1 = append(v1, c.data.R, c.data.G, c.data.B, c.data.A)
+ } else {
+ v1 = append(v1, 0x00, 0x00, 0x00, 0xff)
+ }
+ v0 = v0[n:]
+ }
+ if len(v0) != 0 {
+ return nil, errInvalidSuggestedPalette
+ }
+ }
+ return v1, nil
+}
+
+func (u *upgrader) upgradeBytecode(v1 buffer, v0 buffer) (newV1 buffer, newV0 buffer, retErr error) {
+ uf := upgradeFunc(upgradeStyling)
+ for len(v0) > 0 {
+ uf, v1, v0, retErr = uf(u, v1, v0)
+ if retErr != nil {
+ if retErr == errCalculatingJumpLOD {
+ return v1, v0, nil
+ }
+ return nil, nil, retErr
+ }
+ }
+ return v1, v0, nil
+}
+
+var errCalculatingJumpLOD = errors.New("iconvg: calculating JumpLOD")
+
+type upgradeFunc func(*upgrader, buffer, buffer) (upgradeFunc, buffer, buffer, error)
+
+func upgradeStyling(u *upgrader, v1 buffer, v0 buffer) (uf upgradeFunc, newV1 buffer, newV0 buffer, retErr error) {
+ for len(v0) > 0 {
+ switch opcode := v0[0]; {
+ case opcode < 0x80: // "Set CSEL/NSEL"
+ if opcode < 0x40 {
+ u.csel = uint32(opcode & 63)
+ } else {
+ u.nsel = uint32(opcode & 63)
+ }
+ v0 = v0[1:]
+
+ case opcode < 0xa8: // "Set CREG[etc] to an etc color"
+ adj := uint32(opcode & 7)
+ if adj == 7 {
+ adj = 0
+ }
+ index := (u.csel - adj) & 63
+
+ v0 = v0[1:]
+ c, n := Color{}, 0
+ switch (opcode - 0x80) >> 3 {
+ case 0:
+ c, n = v0.decodeColor1()
+ case 1:
+ c, n = v0.decodeColor2()
+ case 2:
+ c, n = v0.decodeColor3Direct()
+ case 3:
+ c, n = v0.decodeColor4()
+ case 4:
+ c, n = v0.decodeColor3Indirect()
+ }
+ if n == 0 {
+ return nil, nil, nil, errInvalidColor
+ }
+ u.creg[index], retErr = u.resolve(c, false)
+ if retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ v0 = v0[n:]
+
+ if (opcode & 7) == 7 {
+ u.csel = (u.csel + 1) & 63
+ }
+
+ case opcode < 0xc0: // "Set NREG[etc] to a real number"
+ adj := uint32(opcode & 7)
+ if adj == 7 {
+ adj = 0
+ }
+ index := (u.nsel - adj) & 63
+
+ v0 = v0[1:]
+ f, n := float32(0), 0
+ switch (opcode - 0x80) >> 3 {
+ case 5:
+ f, n = v0.decodeReal()
+ case 6:
+ f, n = v0.decodeCoordinate()
+ case 7:
+ f, n = v0.decodeZeroToOne()
+ }
+ if n == 0 {
+ return nil, nil, nil, errInvalidNumber
+ }
+ u.nreg[index] = f
+ v0 = v0[n:]
+
+ if (opcode & 7) == 7 {
+ u.nsel = (u.nsel + 1) & 63
+ }
+
+ case opcode < 0xc7: // Start path.
+ adj := uint32(opcode & 7)
+ u.fill = (u.csel - adj) & 63
+ v1 = append(v1, 0x35) // FFV1 MoveTo.
+ v0 = v0[1:]
+ return upgradeDrawing, v1, v0, nil
+
+ case opcode == 0xc7: // "Set LOD"
+ if u.calculatingJumpLOD {
+ u.calculatingJumpLOD = false
+ return nil, v1, v0, errCalculatingJumpLOD
+ }
+
+ v0 = v0[1:]
+ lod := [2]float32{}
+ for i := range lod {
+ f, n := v0.decodeReal()
+ if n == 0 {
+ return nil, nil, nil, errInvalidNumber
+ }
+ lod[i] = f
+ v0 = v0[n:]
+ }
+ if (lod[0] == 0) && math.IsInf(float64(lod[1]), +1) {
+ break
+ }
+
+ u.calculatingJumpLOD = true
+ ifTrue := []byte(nil)
+ if ifTrue, v0, retErr = u.upgradeBytecode(nil, v0); retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ nInstructions := countFFV1Instructions(ifTrue)
+ if nInstructions >= (1 << 30) {
+ return nil, nil, nil, errUnsupportedUpgrade
+ }
+ v1 = append(v1, 0x3a) // FFV1 JumpLOD.
+ v1.encodeNaturalFFV1(uint32(nInstructions))
+ v1.encodeCoordinateFFV1(lod[0])
+ v1.encodeCoordinateFFV1(lod[1])
+ v1 = append(v1, ifTrue...)
+
+ default:
+ return nil, nil, nil, errUnsupportedStylingOpcode
+ }
+ }
+ return upgradeStyling, v1, v0, nil
+}
+
+func upgradeDrawing(u *upgrader, v1 buffer, v0 buffer) (uf upgradeFunc, newV1 buffer, newV0 buffer, retErr error) {
+ u.verbs = u.verbs[:0]
+ u.args = u.args[:0]
+
+ coords := [3][2]float32{}
+ pen := [2]float32{}
+ prevSmoothType := smoothTypeNone
+ prevSmoothPoint := [2]float32{}
+
+ // Handle the implicit M after a "Start path" styling op.
+ v0, retErr = decodeCoordinates(pen[:2], nil, v0)
+ if retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ u.verbs = append(u.verbs, upgradeVerbMoveTo)
+ u.args = append(u.args, pen)
+ startingPoint := pen
+
+ for len(v0) > 0 {
+ switch opcode := v0[0]; {
+ case opcode < 0xc0: // LineTo, QuadTo, CubeTo.
+ nCoordPairs, nReps, relative, smoothType := 0, 1+int(opcode&0x0f), false, smoothTypeNone
+ switch opcode >> 4 {
+ case 0x00, 0x01: // "L (absolute lineTo)"
+ nCoordPairs = 1
+ nReps = 1 + int(opcode&0x1f)
+ case 0x02, 0x03: // "l (relative lineTo)"
+ nCoordPairs = 1
+ nReps = 1 + int(opcode&0x1f)
+ relative = true
+ case 0x04: // "T (absolute smooth quadTo)"
+ nCoordPairs = 1
+ smoothType = smoothTypeQuad
+ case 0x05: // "t (relative smooth quadTo)"
+ nCoordPairs = 1
+ relative = true
+ smoothType = smoothTypeQuad
+ case 0x06: // "Q (absolute quadTo)"
+ nCoordPairs = 2
+ case 0x07: // "q (relative quadTo)"
+ nCoordPairs = 2
+ relative = true
+ case 0x08: // "S (absolute smooth cubeTo)"
+ nCoordPairs = 2
+ smoothType = smoothTypeCube
+ case 0x09: // "s (relative smooth cubeTo)"
+ nCoordPairs = 2
+ relative = true
+ smoothType = smoothTypeCube
+ case 0x0a: // "C (absolute cubeTo)"
+ nCoordPairs = 3
+ case 0x0b: // "c (relative cubeTo)"
+ nCoordPairs = 3
+ relative = true
+ }
+ v0 = v0[1:]
+
+ for i := 0; i < nReps; i++ {
+ smoothIndex := 0
+ if smoothType != smoothTypeNone {
+ smoothIndex = 1
+ if smoothType != prevSmoothType {
+ coords[0][0] = pen[0]
+ coords[0][1] = pen[1]
+ } else {
+ coords[0][0] = (2 * pen[0]) - prevSmoothPoint[0]
+ coords[0][1] = (2 * pen[1]) - prevSmoothPoint[1]
+ }
+ }
+ allCoords := coords[:smoothIndex+nCoordPairs]
+ explicitCoords := allCoords[smoothIndex:]
+
+ v0, retErr = decodeCoordinatePairs(explicitCoords, nil, v0)
+ if retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ if relative {
+ for c := range explicitCoords {
+ explicitCoords[c][0] += pen[0]
+ explicitCoords[c][1] += pen[1]
+ }
+ }
+
+ u.verbs = append(u.verbs, uint8(len(allCoords)))
+ u.args = append(u.args, allCoords...)
+
+ pen = allCoords[len(allCoords)-1]
+ if len(allCoords) == 2 {
+ prevSmoothPoint = allCoords[0]
+ prevSmoothType = smoothTypeQuad
+ } else if len(allCoords) == 3 {
+ prevSmoothPoint = allCoords[1]
+ prevSmoothType = smoothTypeCube
+ } else {
+ prevSmoothType = smoothTypeNone
+ }
+ }
+
+ case opcode < 0xe0: // ArcTo.
+ v1, v0, retErr = u.upgradeArcs(&pen, v1, v0)
+ if retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ prevSmoothType = smoothTypeNone
+
+ default: // Other drawing opcodes.
+ v0 = v0[1:]
+ switch opcode {
+ case 0xe1: // "z (closePath); end path"
+ goto endPath
+
+ case 0xe2, 0xe3: // "z (closePath); M (absolute/relative moveTo)"
+ v0, retErr = decodeCoordinatePairs(coords[:1], nil, v0)
+ if retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ if opcode == 0xe2 {
+ pen[0] = coords[0][0]
+ pen[1] = coords[0][1]
+ } else {
+ pen[0] += coords[0][0]
+ pen[1] += coords[0][1]
+ }
+ u.verbs = append(u.verbs, upgradeVerbMoveTo)
+ u.args = append(u.args, pen)
+
+ default:
+ tmp := [1]float32{}
+ v0, retErr = decodeCoordinates(tmp[:1], nil, v0)
+ if retErr != nil {
+ return nil, nil, nil, retErr
+ }
+ switch opcode {
+ case 0xe6: // "H (absolute horizontal lineTo)"
+ pen[0] = tmp[0]
+ case 0xe7: // "h (relative horizontal lineTo)"
+ pen[0] += tmp[0]
+ case 0xe8: // "V (absolute vertical lineTo)"
+ pen[1] = tmp[0]
+ case 0xe9: // "v (relative vertical lineTo)"
+ pen[1] += tmp[0]
+ default:
+ return nil, nil, nil, errUnsupportedDrawingOpcode
+ }
+ u.verbs = append(u.verbs, upgradeVerbLineTo)
+ u.args = append(u.args, pen)
+ }
+ prevSmoothType = smoothTypeNone
+ }
+ }
+
+endPath:
+ v1, retErr = u.finishDrawing(v1, startingPoint)
+ return upgradeStyling, v1, v0, retErr
+}
+
+func (u *upgrader) finishDrawing(v1 buffer, startingPoint [2]float32) (newV1 buffer, retErr error) {
+ v1.encodeCoordinatePairFFV1(u.args[0])
+
+ for i, j := 1, 1; i < len(u.verbs); {
+ curr := u.args[j-1]
+ runLength := u.computeRunLength(u.verbs[i:])
+ verb := u.verbs[i]
+
+ if verb == upgradeVerbMoveTo {
+ v1 = append(v1, 0x35) // FFV1 MoveTo.
+ v1.encodeCoordinatePairFFV1(u.args[j])
+ i += 1
+ j += 1
+ continue
+ }
+
+ switch verb {
+ case upgradeVerbLineTo:
+ if ((runLength == 3) && ((j + 3) == len(u.args)) && u.looksLikeParallelogram3(&curr, u.args[j:], &startingPoint)) ||
+ ((runLength == 4) && u.looksLikeParallelogram4(&curr, u.args[j:j+4])) {
+ v1 = append(v1, 0x34) // FFV1 Parallelogram.
+ v1.encodeCoordinatePairFFV1(u.args[j+0])
+ v1.encodeCoordinatePairFFV1(u.args[j+1])
+ i += 4
+ j += 4 * 1
+ continue
+ }
+ case upgradeVerbCubeTo:
+ if (runLength == 4) && u.looksLikeEllipse(&curr, u.args[j:j+(4*3)]) {
+ v1 = append(v1, 0x33) // FFV1 Ellipse (4 quarters).
+ v1.encodeCoordinatePairFFV1(u.args[j+2])
+ v1.encodeCoordinatePairFFV1(u.args[j+5])
+ i += 4
+ j += 4 * 3
+ continue
+ }
+ }
+
+ opcodeBase := 0x10 * (verb - 1) // FFV1 LineTo / QuadTo / CubeTo.
+ if runLength < 16 {
+ v1 = append(v1, opcodeBase|uint8(runLength))
+ } else {
+ v1 = append(v1, opcodeBase)
+ v1.encodeNaturalFFV1(uint32(runLength) - 16)
+ }
+ args := u.args[j : j+(runLength*int(verb))]
+ for _, arg := range args {
+ v1.encodeCoordinatePairFFV1(arg)
+ }
+ i += runLength
+ j += len(args)
+ }
+
+ return u.emitFill(v1)
+}
+
+func (u *upgrader) emitFill(v1 buffer) (newV1 buffer, retErr error) {
+ switch c := u.creg[u.fill]; c.typ {
+ case ColorTypeRGBA:
+ if validAlphaPremulColor(c.rgba) {
+ if !u.hasRegsSel7 || (u.regsSel7 != c.rgba) {
+ u.hasRegsSel7, u.regsSel7 = true, c.rgba
+ v1 = append(v1, 0x57, // FFV1 Set REGS[SEL+7].hi32.
+ c.rgba.R, c.rgba.G, c.rgba.B, c.rgba.A)
+ }
+ v1 = append(v1, 0x87) // FFV1 Fill (flat color) with REGS[SEL+7].
+
+ } else if (c.rgba.A == 0) && (c.rgba.B&0x80 != 0) {
+ nStops := int(c.rgba.R & 63)
+ cBase := int(c.rgba.G & 63)
+ nBase := int(c.rgba.B & 63)
+ if nStops < 2 {
+ return nil, errInvalidColor
+ } else if nStops > 17 {
+ return nil, errUnsupportedUpgrade
+ }
+
+ v1 = append(v1, 0x70|uint8(nStops-2)) // FFV1 SEL -= N; Set REGS[SEL+1 .. SEL+1+N].
+ for i := 0; i < nStops; i++ {
+ if stopOffset := u.nreg[(nBase+i)&63]; stopOffset <= 0 {
+ v1 = append(v1, 0x00, 0x00, 0x00, 0x00)
+ } else if stopOffset < 1 {
+ u := uint32(stopOffset * 0x10000)
+ v1 = append(v1, uint8(u>>0), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+ } else {
+ v1 = append(v1, 0x00, 0x00, 0x01, 0x00)
+ }
+
+ if stopColor := u.creg[(cBase+i)&63]; stopColor.typ != ColorTypeRGBA {
+ return nil, errUnsupportedUpgrade
+ } else {
+ v1 = append(v1,
+ stopColor.rgba.R,
+ stopColor.rgba.G,
+ stopColor.rgba.B,
+ stopColor.rgba.A,
+ )
+ }
+ }
+
+ nMatrixElements := 0
+ if c.rgba.B&0x40 == 0 {
+ v1 = append(v1, 0x91, // FFV1 Fill (linear gradient) with REGS[SEL+1 .. SEL+1+N].
+ (c.rgba.G&0xc0)|uint8(nStops-2))
+ nMatrixElements = 3
+ } else {
+ v1 = append(v1, 0xa1, // FFV1 Fill (radial gradient) with REGS[SEL+1 .. SEL+1+N].
+ (c.rgba.G&0xc0)|uint8(nStops-2))
+ nMatrixElements = 6
+ }
+ for i := 0; i < nMatrixElements; i++ {
+ u := math.Float32bits(u.nreg[(nBase+i-6)&63])
+ v1 = append(v1, uint8(u>>0), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+ }
+
+ v1 = append(v1, 0x36, // FFV1 SEL += N.
+ uint8(nStops))
+ } else {
+ return nil, errInvalidColor
+ }
+
+ case ColorTypePaletteIndex:
+ if c.paletteIndex < 7 {
+ v1 = append(v1, 0x88+c.paletteIndex) // FFV1 Fill (flat color) with REGS[SEL+8+N].
+ } else {
+ v1 = append(v1, 0x56, // FFV1 Set REGS[SEL+6].hi32.
+ 0x80|c.paletteIndex, 0, 0, 0,
+ 0x86) // FFV1 Fill (flat color) with REGS[SEL+6].
+ }
+
+ case ColorTypeBlend:
+ if c.color0.typ == ColorTypeRGBA {
+ v1 = append(v1, 0x53, // FFV1 Set REGS[SEL+3].hi32.
+ c.color0.rgba.R, c.color0.rgba.G, c.color0.rgba.B, c.color0.rgba.A)
+ }
+ if c.color1.typ == ColorTypeRGBA {
+ v1 = append(v1, 0x54, // FFV1 Set REGS[SEL+4].hi32.
+ c.color1.rgba.R, c.color1.rgba.G, c.color1.rgba.B, c.color1.rgba.A)
+ }
+ v1 = append(v1, 0x55, // FFV1 Set REGS[SEL+5].hi32.
+ c.blend)
+ if c.color0.typ == ColorTypeRGBA {
+ v1 = append(v1, 0xfe)
+ } else {
+ v1 = append(v1, 0x80|c.color0.paletteIndex)
+ }
+ if c.color1.typ == ColorTypeRGBA {
+ v1 = append(v1, 0xff)
+ } else {
+ v1 = append(v1, 0x80|c.color1.paletteIndex)
+ }
+ v1 = append(v1, 0, 0x85) // FFV1 Fill (flat color) with REGS[SEL+5].
+ }
+
+ return v1, nil
+}
+
+func (u *upgrader) computeRunLength(verbs []uint8) int {
+ firstVerb := verbs[0]
+ if firstVerb == 0 {
+ return 1
+ }
+ n := 1
+ for ; (n < len(verbs)) && (verbs[n] == firstVerb); n++ {
+ }
+ return n
+}
+
+// looksLikeParallelogram3 is like looksLikeParallelogram4 but the final point
+// (implied by the ClosePath op) is separate from the middle 3 args.
+func (u *upgrader) looksLikeParallelogram3(curr *[2]float32, args [][2]float32, final *[2]float32) bool {
+ if len(args) != 3 {
+ panic("unreachable")
+ }
+ return (*curr == *final) &&
+ (curr[0] == (args[0][0] - args[1][0] + args[2][0])) &&
+ (curr[1] == (args[0][1] - args[1][1] + args[2][1]))
+}
+
+// looksLikeParallelogram4 returns whether the 5 coordinate pairs (A, B, C, D,
+// E) form a parallelogram:
+//
+// E=A B
+//
+// o---------o
+// \ \
+// \ \
+// \ \
+// o---------o
+// D C
+//
+// Specifically, it checks that (A == E) and ((A - B) == (D - C)). That last
+// equation can be rearranged as (A == (B - C + D)).
+//
+// The motivation is that, if looksLikeParallelogram4 is true, then the 5 input
+// coordinate pairs can then be compressed to 3: A, B and C. Or, if the current
+// point A is implied by context then 4 input pairs can be compressed to 2.
+func (u *upgrader) looksLikeParallelogram4(curr *[2]float32, args [][2]float32) bool {
+ if len(args) != 4 {
+ panic("unreachable")
+ }
+ return (*curr == args[3]) &&
+ (curr[0] == (args[0][0] - args[1][0] + args[2][0])) &&
+ (curr[1] == (args[0][1] - args[1][1] + args[2][1]))
+}
+
+// looksLikeEllipse returns whether the 13 coordinate pairs (A, A+, B-, B, B+,
+// C- C, C+, D-, D, D+, A-, E) form a cubic BΓ©zier approximation to an ellipse.
+// Let AΒ± denote the two tangent vectors (A+ - A) and (A - A-) and likewise for
+// BΒ±, CΒ± and DΒ±.
+//
+// A+ B-
+//
+// E=A o o B
+// A- o---------o B+
+//
+// o \ \ o
+// \ X \
+// o \ \ o
+// D+ o---------o C-
+// D o o C
+// D- C+
+//
+// See https://nigeltao.github.io/blog/2021/three-points-define-ellipse.html
+// for a better version of that ASCII art.
+//
+// Specifically, it checks that (A, B, C, D, E), also known as (*curr, args[2],
+// args[5], args[8] and args[11]), forms a parallelogram. If so, let X be the
+// parallelogram center and define two axis vectors: r = B-X and s = C-X.
+//
+// These axes define the parallelogram's or ellipse's shape but they are not
+// necessarily orthogonal and hence not necessarily the ellipse's major
+// (longest) and minor (shortest) axes. If s is a 90 degree rotation of r then
+// the parallelogram is a square and the ellipse is a circle.
+//
+// This function further checks that the AΒ±, BΒ± CΒ± and DΒ± tangents are
+// approximately equal to +λ×r, +λ×s, -λ×r and -λ×s, where Ξ» = ((math.Sqrt2 -
+// 1) Γ— 4 / 3) comes from the cubic BΓ©zier approximation to a quarter-circle.
+//
+// The motivation is that, if looksLikeEllipse is true, then the 13 input
+// coordinate pairs can then be compressed to 3: A, B and C. Or, if the current
+// point A is implied by context then 12 input pairs can be compressed to 2.
+func (u *upgrader) looksLikeEllipse(curr *[2]float32, args [][2]float32) bool {
+ if len(args) != 12 {
+ panic("unreachable")
+ }
+ if (*curr != args[11]) ||
+ (curr[0] != (args[2][0] - args[5][0] + args[8][0])) ||
+ (curr[1] != (args[2][1] - args[5][1] + args[8][1])) {
+ return false
+ }
+ center := [2]float32{
+ (args[2][0] + args[8][0]) / 2,
+ (args[2][1] + args[8][1]) / 2,
+ }
+
+ // 0.5522847498307933984022516322796 β‰ˆ ((math.Sqrt2 - 1) Γ— 4 / 3), the
+ // tangent lengths (as a fraction of the radius) for a commonly used cubic
+ // BΓ©zier approximation to a circle. Multiplying that by 0.98 and 1.02
+ // checks that we're within 2% of that fraction.
+ //
+ // This also covers the slightly different 0.551784777779014 constant,
+ // recommended by https://pomax.github.io/bezierinfo/#circles_cubic
+ const Ξ»Min = 0.98 * 0.5522847498307933984022516322796
+ const Ξ»Max = 1.02 * 0.5522847498307933984022516322796
+
+ // Check the first axis.
+ r := [2]float32{
+ args[2][0] - center[0],
+ args[2][1] - center[1],
+ }
+ rMin := [2]float32{r[0] * Ξ»Min, r[1] * Ξ»Min}
+ rMax := [2]float32{r[0] * Ξ»Max, r[1] * Ξ»Max}
+ if rMin[0] > rMax[0] {
+ rMin[0], rMax[0] = rMax[0], rMin[0]
+ }
+ if rMin[1] > rMax[1] {
+ rMin[1], rMax[1] = rMax[1], rMin[1]
+ }
+ if !within(args[0][0]-curr[0], args[0][1]-curr[1], rMin, rMax) ||
+ !within(args[4][0]-args[5][0], args[4][1]-args[5][1], rMin, rMax) ||
+ !within(args[5][0]-args[6][0], args[5][1]-args[6][1], rMin, rMax) ||
+ !within(args[11][0]-args[10][0], args[11][1]-args[10][1], rMin, rMax) {
+ return false
+ }
+
+ // Check the second axis.
+ s := [2]float32{
+ args[5][0] - center[0],
+ args[5][1] - center[1],
+ }
+ sMin := [2]float32{s[0] * Ξ»Min, s[1] * Ξ»Min}
+ sMax := [2]float32{s[0] * Ξ»Max, s[1] * Ξ»Max}
+ if sMin[0] > sMax[0] {
+ sMin[0], sMax[0] = sMax[0], sMin[0]
+ }
+ if sMin[1] > sMax[1] {
+ sMin[1], sMax[1] = sMax[1], sMin[1]
+ }
+ if !within(args[2][0]-args[1][0], args[2][1]-args[1][1], sMin, sMax) ||
+ !within(args[3][0]-args[2][0], args[3][1]-args[2][1], sMin, sMax) ||
+ !within(args[7][0]-args[8][0], args[7][1]-args[8][1], sMin, sMax) ||
+ !within(args[8][0]-args[9][0], args[8][1]-args[9][1], sMin, sMax) {
+ return false
+ }
+
+ return true
+}
+
+func within(v0 float32, v1 float32, min [2]float32, max [2]float32) bool {
+ return (min[0] <= v0) && (v0 <= max[0]) && (min[1] <= v1) && (v1 <= max[1])
+}
+
+func (u *upgrader) upgradeArcs(pen *[2]float32, v1 buffer, v0 buffer) (newV1 buffer, newV0 buffer, retErr error) {
+ coords := [6]float32{}
+ largeArc, sweep := false, false
+ opcode := v0[0]
+ v0 = v0[1:]
+ nReps := 1 + int(opcode&0x0f)
+ for i := 0; i < nReps; i++ {
+ v0, retErr = decodeCoordinates(coords[:2], nil, v0)
+ if retErr != nil {
+ return nil, nil, retErr
+ }
+ coords[2], v0, retErr = decodeAngle(nil, v0)
+ if retErr != nil {
+ return nil, nil, retErr
+ }
+ largeArc, sweep, v0, retErr = decodeArcToFlags(nil, v0)
+ if retErr != nil {
+ return nil, nil, retErr
+ }
+ v0, retErr = decodeCoordinates(coords[4:6], nil, v0)
+ if retErr != nil {
+ return nil, nil, retErr
+ }
+ if (opcode >> 4) == 0x0d {
+ coords[4] += pen[0]
+ coords[5] += pen[1]
+ }
+ u.upgradeArc(pen, coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
+ pen[0] = coords[4]
+ pen[1] = coords[5]
+ }
+ return v1, v0, nil
+}
+
+func (u *upgrader) upgradeArc(pen *[2]float32, rx, ry, xAxisRotation float32, largeArc, sweep bool, finalX, finalY float32) {
+ // We follow the "Conversion from endpoint to center parameterization"
+ // algorithm as per
+ // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+
+ // There seems to be a bug in the spec's "implementation notes".
+ //
+ // Actual implementations, such as
+ // - https://git.gnome.org/browse/librsvg/tree/rsvg-path.c
+ // - http://svn.apache.org/repos/asf/xmlgraphics/batik/branches/svg11/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java
+ // - https://java.net/projects/svgsalamander/sources/svn/content/trunk/svg-core/src/main/java/com/kitfox/svg/pathcmd/Arc.java
+ // - https://github.com/millermedeiros/SVGParser/blob/master/com/millermedeiros/geom/SVGArc.as
+ // do something slightly different (marked with a †).
+
+ // (†) The Abs isn't part of the spec. Neither is checking that Rx and Ry
+ // are non-zero (and non-NaN).
+ Rx := math.Abs(float64(rx))
+ Ry := math.Abs(float64(ry))
+ if !(Rx > 0 && Ry > 0) {
+ u.verbs = append(u.verbs, upgradeVerbLineTo)
+ u.args = append(u.args, [2]float32{finalX, finalY})
+ return
+ }
+
+ x1 := float64(pen[0])
+ y1 := float64(pen[1])
+ x2 := float64(finalX)
+ y2 := float64(finalY)
+
+ phi := 2 * math.Pi * float64(xAxisRotation)
+
+ // Step 1: Compute (x1β€², y1β€²)
+ halfDx := (x1 - x2) / 2
+ halfDy := (y1 - y2) / 2
+ cosPhi := math.Cos(phi)
+ sinPhi := math.Sin(phi)
+ x1Prime := +cosPhi*halfDx + sinPhi*halfDy
+ y1Prime := -sinPhi*halfDx + cosPhi*halfDy
+
+ // Step 2: Compute (cxβ€², cyβ€²)
+ rxSq := Rx * Rx
+ rySq := Ry * Ry
+ x1PrimeSq := x1Prime * x1Prime
+ y1PrimeSq := y1Prime * y1Prime
+
+ // (†) Check that the radii are large enough.
+ radiiCheck := x1PrimeSq/rxSq + y1PrimeSq/rySq
+ if radiiCheck > 1 {
+ c := math.Sqrt(radiiCheck)
+ Rx *= c
+ Ry *= c
+ rxSq = Rx * Rx
+ rySq = Ry * Ry
+ }
+
+ denom := rxSq*y1PrimeSq + rySq*x1PrimeSq
+ step2 := 0.0
+ if a := rxSq*rySq/denom - 1; a > 0 {
+ step2 = math.Sqrt(a)
+ }
+ if largeArc == sweep {
+ step2 = -step2
+ }
+ cxPrime := +step2 * Rx * y1Prime / Ry
+ cyPrime := -step2 * Ry * x1Prime / Rx
+
+ // Step 3: Compute (cx, cy) from (cxβ€², cyβ€²)
+ cx := +cosPhi*cxPrime - sinPhi*cyPrime + (x1+x2)/2
+ cy := +sinPhi*cxPrime + cosPhi*cyPrime + (y1+y2)/2
+
+ // Step 4: Compute ΞΈ1 and Δθ
+ ax := (+x1Prime - cxPrime) / Rx
+ ay := (+y1Prime - cyPrime) / Ry
+ bx := (-x1Prime - cxPrime) / Rx
+ by := (-y1Prime - cyPrime) / Ry
+ theta1 := angle(1, 0, ax, ay)
+ deltaTheta := angle(ax, ay, bx, by)
+ if sweep {
+ if deltaTheta < 0 {
+ deltaTheta += 2 * math.Pi
+ }
+ } else {
+ if deltaTheta > 0 {
+ deltaTheta -= 2 * math.Pi
+ }
+ }
+
+ // This ends the
+ // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+ // algorithm. What follows below is specific to this implementation.
+
+ // We approximate an arc by one or more cubic BΓ©zier curves.
+ n := int(math.Ceil(math.Abs(deltaTheta) / (math.Pi/2 + 0.001)))
+ for i := 0; i < n; i++ {
+ u.arcSegmentTo(cx, cy,
+ theta1+deltaTheta*float64(i+0)/float64(n),
+ theta1+deltaTheta*float64(i+1)/float64(n),
+ Rx, Ry, cosPhi, sinPhi,
+ )
+ }
+}
+
+// arcSegmentTo approximates an arc by a cubic BΓ©zier curve. The mathematical
+// formulae for the control points are the same as that used by librsvg.
+func (u *upgrader) arcSegmentTo(cx, cy, theta1, theta2, rx, ry, cosPhi, sinPhi float64) {
+ halfDeltaTheta := (theta2 - theta1) * 0.5
+ q := math.Sin(halfDeltaTheta * 0.5)
+ t := (8 * q * q) / (3 * math.Sin(halfDeltaTheta))
+ cos1 := math.Cos(theta1)
+ sin1 := math.Sin(theta1)
+ cos2 := math.Cos(theta2)
+ sin2 := math.Sin(theta2)
+ x1 := rx * (+cos1 - t*sin1)
+ y1 := ry * (+sin1 + t*cos1)
+ x2 := rx * (+cos2 + t*sin2)
+ y2 := ry * (+sin2 - t*cos2)
+ x3 := rx * (+cos2)
+ y3 := ry * (+sin2)
+ highResolutionCoordinates := u.opts.ArcsExpandWithHighResolutionCoordinates
+ u.verbs = append(u.verbs, upgradeVerbCubeTo)
+ u.args = append(u.args,
+ [2]float32{
+ quantize(float32(cx+cosPhi*x1-sinPhi*y1), highResolutionCoordinates),
+ quantize(float32(cy+sinPhi*x1+cosPhi*y1), highResolutionCoordinates),
+ },
+ [2]float32{
+ quantize(float32(cx+cosPhi*x2-sinPhi*y2), highResolutionCoordinates),
+ quantize(float32(cy+sinPhi*x2+cosPhi*y2), highResolutionCoordinates),
+ },
+ [2]float32{
+ quantize(float32(cx+cosPhi*x3-sinPhi*y3), highResolutionCoordinates),
+ quantize(float32(cy+sinPhi*x3+cosPhi*y3), highResolutionCoordinates),
+ },
+ )
+}
+
+func countFFV1Instructions(src buffer) (ret uint64) {
+ for len(src) > 0 {
+ ret++
+ opcode := src[0]
+ src = src[1:]
+
+ switch {
+ case opcode < 0x40:
+ switch {
+ case opcode < 0x30:
+ nReps := uint32(opcode & 15)
+ if nReps == 0 {
+ n := 0
+ nReps, n = src.decodeNaturalFFV1()
+ src = src[n:]
+ nReps += 16
+ }
+ nCoords := 2 * (1 + int(opcode>>4))
+ for ; nReps > 0; nReps-- {
+ for i := 0; i < nCoords; i++ {
+ _, n := src.decodeNaturalFFV1()
+ src = src[n:]
+ }
+ }
+ case opcode < 0x35:
+ for i := 0; i < 4; i++ {
+ _, n := src.decodeNaturalFFV1()
+ src = src[n:]
+ }
+ case opcode == 0x35:
+ for i := 0; i < 2; i++ {
+ _, n := src.decodeNaturalFFV1()
+ src = src[n:]
+ }
+ case opcode == 0x36:
+ src = src[1:]
+ case opcode == 0x37:
+ // No-op.
+ default:
+ // upgradeBytecode (with calculatingJumpLOD set) will not emit
+ // jump or call instructions.
+ panic("unexpected FFV1 instruction")
+ }
+
+ case opcode < 0x80:
+ switch (opcode >> 4) & 3 {
+ case 0, 1:
+ src = src[4:]
+ case 2:
+ src = src[8:]
+ default:
+ src = src[8*(2+int(opcode&15)):]
+ }
+
+ case opcode < 0xc0:
+ switch (opcode >> 4) & 3 {
+ case 0:
+ // No-op.
+ case 1:
+ src = src[13:]
+ case 2:
+ src = src[25:]
+ default:
+ // upgradeBytecode (with calculatingJumpLOD set) will not emit
+ // reserved instructions.
+ panic("unexpected FFV1 instruction")
+ }
+
+ default:
+ // upgradeBytecode (with calculatingJumpLOD set) will not emit
+ // reserved instructions.
+ panic("unexpected FFV1 instruction")
+ }
+ }
+ return ret
+}
+
+type upgradeColor struct {
+ typ ColorType
+ paletteIndex uint8
+ blend uint8
+ rgba color.RGBA
+ color0 *upgradeColor
+ color1 *upgradeColor
+}
+
+func (u *upgrader) resolve(c Color, denyBlend bool) (upgradeColor, error) {
+ switch c.typ {
+ case ColorTypeRGBA:
+ return upgradeColor{
+ typ: ColorTypeRGBA,
+ rgba: c.data,
+ }, nil
+ case ColorTypePaletteIndex:
+ return upgradeColor{
+ typ: ColorTypePaletteIndex,
+ paletteIndex: c.paletteIndex(),
+ }, nil
+ case ColorTypeCReg:
+ upgrade := u.creg[c.cReg()]
+ if denyBlend && (upgrade.typ == ColorTypeBlend) {
+ return upgradeColor{}, errUnsupportedUpgrade
+ }
+ return upgrade, nil
+ }
+
+ if denyBlend {
+ return upgradeColor{}, errUnsupportedUpgrade
+ }
+ t, c0, c1 := c.blend()
+ color0, err := u.resolve(decodeColor1(c0), true)
+ if err != nil {
+ return upgradeColor{}, err
+ }
+ color1, err := u.resolve(decodeColor1(c1), true)
+ if err != nil {
+ return upgradeColor{}, err
+ }
+ return upgradeColor{
+ typ: ColorTypeBlend,
+ blend: t,
+ color0: &color0,
+ color1: &color1,
+ }, nil
+}
diff --git a/shiny/iconvg/upgrade_test.go b/shiny/iconvg/upgrade_test.go
new file mode 100644
index 0000000..0b4b175
--- /dev/null
+++ b/shiny/iconvg/upgrade_test.go
@@ -0,0 +1,47 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package iconvg
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestUpgradeToFileFormatVersion1(t *testing.T) {
+ for _, tc := range testdataTestCases {
+ original, err := os.ReadFile(filepath.FromSlash(tc.filename) + ".ivg")
+ if err != nil {
+ t.Errorf("%s: ReadFile: %v", tc.filename, err)
+ continue
+ }
+
+ upgraded, err := UpgradeToFileFormatVersion1(original, nil)
+ if err != nil {
+ t.Errorf("%s: Upgrade: %v", tc.filename, err)
+ continue
+ }
+
+ // For most of the testdataTestCases, we just check (above) that
+ // calling UpgradeToFileFormatVersion1 returns a nil error. As a
+ // further basic consistency check, we hard-code the expected results
+ // for upgrading the "action-info.lores" icon.
+ //
+ // These 36 bytes (and its disassembly via the cmd/iconvg-disassemble
+ // tool) is also a file in the test/data directory of the
+ // github.com/google/iconvg repository (the repository that is
+ // generally responsible for "File Format Version 1").
+ if tc.filename == "testdata/action-info.lores" {
+ const want = "" +
+ "\x8A\x49\x56\x47\x03\x0B\x11\x51\x51\xB1\xB1\x35\x81\x59\x33\x59" +
+ "\x81\x81\xA9\x35\x85\x95\x34\x7D\x95\x7D\x7D\x35\x85\x75\x34\x7D" +
+ "\x75\x7D\x6D\x88"
+ if got := string(upgraded); got != want {
+ t.Errorf("%s: Upgrade: got:\n% 02x\nwant:\n% 02x", tc.filename, got, want)
+ continue
+ }
+ }
+ }
+}
diff --git a/shiny/imageutil/imageutil.go b/shiny/imageutil/imageutil.go
new file mode 100644
index 0000000..43b2309
--- /dev/null
+++ b/shiny/imageutil/imageutil.go
@@ -0,0 +1,101 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package imageutil implements some image utility functions.
+package imageutil
+
+import (
+ "image"
+)
+
+// TODO: move Border into the standard library's package image?
+
+// Border returns four rectangles that together contain those points between r
+// and r.Inset(inset). Visually:
+//
+// 00000000
+// 00000000
+// 11....22
+// 11....22
+// 11....22
+// 33333333
+// 33333333
+//
+// The inset may be negative, in which case the points will be outside r.
+//
+// Some of the returned rectangles may be empty. None of the returned
+// rectangles will overlap.
+func Border(r image.Rectangle, inset int) [4]image.Rectangle {
+ if inset == 0 {
+ return [4]image.Rectangle{}
+ }
+ if r.Dx() <= 2*inset || r.Dy() <= 2*inset {
+ return [4]image.Rectangle{r}
+ }
+
+ x := [4]int{
+ r.Min.X,
+ r.Min.X + inset,
+ r.Max.X - inset,
+ r.Max.X,
+ }
+ y := [4]int{
+ r.Min.Y,
+ r.Min.Y + inset,
+ r.Max.Y - inset,
+ r.Max.Y,
+ }
+ if inset < 0 {
+ x[0], x[1] = x[1], x[0]
+ x[2], x[3] = x[3], x[2]
+ y[0], y[1] = y[1], y[0]
+ y[2], y[3] = y[3], y[2]
+ }
+
+ // The top and bottom sections are responsible for filling the corners.
+ // The top and bottom sections go from x[0] to x[3], across the y's.
+ // The left and right sections go from y[1] to y[2], across the x's.
+
+ return [4]image.Rectangle{{
+ // Top section.
+ Min: image.Point{
+ X: x[0],
+ Y: y[0],
+ },
+ Max: image.Point{
+ X: x[3],
+ Y: y[1],
+ },
+ }, {
+ // Left section.
+ Min: image.Point{
+ X: x[0],
+ Y: y[1],
+ },
+ Max: image.Point{
+ X: x[1],
+ Y: y[2],
+ },
+ }, {
+ // Right section.
+ Min: image.Point{
+ X: x[2],
+ Y: y[1],
+ },
+ Max: image.Point{
+ X: x[3],
+ Y: y[2],
+ },
+ }, {
+ // Bottom section.
+ Min: image.Point{
+ X: x[0],
+ Y: y[2],
+ },
+ Max: image.Point{
+ X: x[3],
+ Y: y[3],
+ },
+ }}
+}
diff --git a/shiny/imageutil/imageutil_test.go b/shiny/imageutil/imageutil_test.go
new file mode 100644
index 0000000..9272294
--- /dev/null
+++ b/shiny/imageutil/imageutil_test.go
@@ -0,0 +1,74 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package imageutil
+
+import (
+ "image"
+ "testing"
+)
+
+func area(r image.Rectangle) int {
+ dx, dy := r.Dx(), r.Dy()
+ if dx <= 0 || dy <= 0 {
+ return 0
+ }
+ return dx * dy
+}
+
+func TestBorder(t *testing.T) {
+ r := image.Rect(100, 200, 400, 300)
+
+ insets := []int{
+ -100,
+ -1,
+ +0,
+ +1,
+ +20,
+ +49,
+ +50,
+ +51,
+ +149,
+ +150,
+ +151,
+ }
+
+ for _, inset := range insets {
+ border := Border(r, inset)
+
+ outer, inner := r, r.Inset(inset)
+ if inset < 0 {
+ outer, inner = inner, outer
+ }
+
+ got := 0
+ for _, b := range border {
+ got += area(b)
+ }
+ want := area(outer) - area(inner)
+ if got != want {
+ t.Errorf("inset=%d: total area: got %d, want %d", inset, got, want)
+ }
+
+ for i, bi := range border {
+ for j, bj := range border {
+ if i <= j {
+ continue
+ }
+ if !bi.Intersect(bj).Empty() {
+ t.Errorf("inset=%d: %v and %v overlap", inset, bi, bj)
+ }
+ }
+ }
+
+ for _, b := range border {
+ if got := outer.Intersect(b); got != b {
+ t.Errorf("inset=%d: outer intersection: got %v, want %v", inset, got, b)
+ }
+ if got := inner.Intersect(b); !got.Empty() {
+ t.Errorf("inset=%d: inner intersection: got %v, want empty", inset, got)
+ }
+ }
+ }
+}
diff --git a/shiny/materialdesign/colornames/colornames.go b/shiny/materialdesign/colornames/colornames.go
new file mode 100644
index 0000000..d2e14a4
--- /dev/null
+++ b/shiny/materialdesign/colornames/colornames.go
@@ -0,0 +1,11 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run gen.go
+
+// Package colornames provides named colors as defined in the Material Design
+// style guide.
+//
+// See https://material.google.com/style/color.html
+package colornames
diff --git a/shiny/materialdesign/colornames/colornames_test.go b/shiny/materialdesign/colornames/colornames_test.go
new file mode 100644
index 0000000..22411db
--- /dev/null
+++ b/shiny/materialdesign/colornames/colornames_test.go
@@ -0,0 +1,46 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package colornames
+
+import (
+ "image/color"
+ "testing"
+)
+
+func TestColornames(t *testing.T) {
+ if len(Map) != len(Names) {
+ t.Fatalf("Map and Names have different length: %d vs %d", len(Map), len(Names))
+ }
+
+ for name, want := range testCases {
+ got, ok := Map[name]
+ if !ok {
+ t.Errorf("Did not find %s", name)
+ continue
+ }
+ if got != want {
+ t.Errorf("%s:\ngot %x\nwant %x", name, got, want)
+ }
+ }
+}
+
+var testCases = map[string]color.RGBA{
+ "Red500": color.RGBA{0xf4, 0x43, 0x36, 0xff},
+ "Red50": color.RGBA{0xff, 0xeb, 0xee, 0xff},
+ "Red900": color.RGBA{0xb7, 0x1c, 0x1c, 0xff},
+ "RedA700": color.RGBA{0xd5, 0x00, 0x00, 0xff},
+ "Pink300": color.RGBA{0xf0, 0x62, 0x92, 0xff},
+ "Purple100": color.RGBA{0xe1, 0xbe, 0xe7, 0xff},
+ "Cyan400": color.RGBA{0x26, 0xc6, 0xda, 0xff},
+ "LightGreen800": color.RGBA{0x55, 0x8b, 0x2f, 0xff},
+ "DeepOrangeA200": color.RGBA{0xff, 0x6e, 0x40, 0xff},
+ "Brown50": color.RGBA{0xef, 0xeb, 0xe9, 0xff},
+ "Grey500": color.RGBA{0x9e, 0x9e, 0x9e, 0xff},
+ "Grey600": color.RGBA{0x75, 0x75, 0x75, 0xff},
+ "Grey700": color.RGBA{0x61, 0x61, 0x61, 0xff},
+ "BlueGrey400": color.RGBA{0x78, 0x90, 0x9c, 0xff},
+ "Black": color.RGBA{0x00, 0x00, 0x00, 0xff},
+ "White": color.RGBA{0xff, 0xff, 0xff, 0xff},
+}
diff --git a/shiny/materialdesign/colornames/gen.go b/shiny/materialdesign/colornames/gen.go
new file mode 100644
index 0000000..aa09e93
--- /dev/null
+++ b/shiny/materialdesign/colornames/gen.go
@@ -0,0 +1,178 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+// This program generates table.go from
+// https://material.google.com/style/color.html
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/format"
+ "image/color"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+
+ "golang.org/x/net/html"
+ "golang.org/x/net/html/atom"
+)
+
+type matchFunc func(*html.Node) bool
+
+func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node {
+ if mf(n) {
+ dst = append(dst, n)
+ }
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ dst = appendAll(dst, c, mf)
+ }
+ return dst
+}
+
+func match(a atom.Atom, namespace, key, value string) matchFunc {
+ return func(n *html.Node) bool {
+ return n.DataAtom == a && strings.HasPrefix(getAttr(n, namespace, key), value)
+ }
+}
+
+func getAttr(n *html.Node, namespace, key string) string {
+ for _, attr := range n.Attr {
+ if attr.Namespace == namespace && attr.Key == key {
+ return attr.Val
+ }
+ }
+ return ""
+}
+
+func getText(n *html.Node) string {
+ if n.FirstChild == nil || n.FirstChild.Type != html.TextNode {
+ return ""
+ }
+ return n.FirstChild.Data
+}
+
+var unhex = [256]byte{
+ '0': 0,
+ '1': 1,
+ '2': 2,
+ '3': 3,
+ '4': 4,
+ '5': 5,
+ '6': 6,
+ '7': 7,
+ '8': 8,
+ '9': 9,
+ 'a': 10,
+ 'b': 11,
+ 'c': 12,
+ 'd': 13,
+ 'e': 14,
+ 'f': 15,
+}
+
+func parseRGB(s string) color.RGBA {
+ if len(s) != 7 || s[0] != '#' {
+ return color.RGBA{}
+ }
+ return color.RGBA{
+ R: unhex[s[1]]<<4 | unhex[s[2]],
+ G: unhex[s[3]]<<4 | unhex[s[4]],
+ B: unhex[s[5]]<<4 | unhex[s[6]],
+ A: 0xff,
+ }
+}
+
+type entry struct {
+ name string
+ rgba color.RGBA
+}
+
+func extractColors(tree *html.Node) (ret []entry) {
+ for _, table := range appendAll(nil, tree, match(atom.Section, "", "class", "color-group")) {
+ name := ""
+ for _, nameNode := range appendAll(nil, table, match(atom.Span, "", "class", "name")) {
+ name = strings.Replace(getText(nameNode), " ", "", -1)
+ break
+ }
+ shades := appendAll(nil, table, match(atom.Span, "", "class", "shade"))
+ hexes := appendAll(nil, table, match(atom.Span, "", "class", "hex"))
+ if len(shades) != len(hexes) || len(shades) == 0 {
+ continue
+ }
+ // Remove the duplicated 500 shade at the start of the list.
+ if getText(shades[0]) == "500" {
+ shades, hexes = shades[1:], hexes[1:]
+ }
+ for i, shade := range shades {
+ ret = append(ret, entry{
+ name + getText(shade),
+ parseRGB(getText(hexes[i])),
+ })
+ }
+ }
+ return ret
+}
+
+const preamble = `// generated by go generate; DO NOT EDIT.
+
+package colornames
+
+import "image/color"
+
+`
+
+func writeColorNames(w io.Writer, entries []entry) {
+ fmt.Fprintln(w, preamble)
+ fmt.Fprintln(w, "// Map contains named colors defined in the Material Design style guide.")
+ fmt.Fprintln(w, "var Map = map[string]color.RGBA{")
+ for _, e := range entries {
+ c := e.rgba
+ fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n",
+ e.name, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
+ }
+ fmt.Fprintln(w, "}\n")
+ fmt.Fprintln(w, "// Names contains the color names defined in the Material Design style guide.")
+ fmt.Fprintln(w, "var Names = []string{")
+ for _, e := range entries {
+ fmt.Fprintf(w, "%q,\n", e.name)
+ }
+ fmt.Fprintln(w, "}\n")
+ fmt.Fprintln(w, "var (")
+ for _, e := range entries {
+ c := e.rgba
+ fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n",
+ e.name, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
+ }
+ fmt.Fprintln(w, ")")
+}
+
+const url = "https://material.google.com/style/color.html"
+
+func main() {
+ res, err := http.Get(url)
+ if err != nil {
+ log.Fatalf("Couldn't read from %s: %s\n", url, err)
+ }
+ defer res.Body.Close()
+
+ tree, err := html.Parse(res.Body)
+ if err != nil {
+ log.Fatalf("Couldn't parse %s: %s\n", url, err)
+ }
+
+ buf := &bytes.Buffer{}
+ writeColorNames(buf, extractColors(tree))
+ fmted, err := format.Source(buf.Bytes())
+ if err != nil {
+ log.Fatalf("Error while formatting code: %s\n", err)
+ }
+
+ if err := os.WriteFile("table.go", fmted, 0644); err != nil {
+ log.Fatalf("Error writing table.go: %s\n", err)
+ }
+}
diff --git a/shiny/materialdesign/colornames/table.go b/shiny/materialdesign/colornames/table.go
new file mode 100644
index 0000000..aafdf8a
--- /dev/null
+++ b/shiny/materialdesign/colornames/table.go
@@ -0,0 +1,784 @@
+// generated by go generate; DO NOT EDIT.
+
+package colornames
+
+import "image/color"
+
+// Map contains named colors defined in the Material Design style guide.
+var Map = map[string]color.RGBA{
+ "Red50": color.RGBA{0xff, 0xeb, 0xee, 0xff}, // rgb(255, 235, 238)
+ "Red100": color.RGBA{0xff, 0xcd, 0xd2, 0xff}, // rgb(255, 205, 210)
+ "Red200": color.RGBA{0xef, 0x9a, 0x9a, 0xff}, // rgb(239, 154, 154)
+ "Red300": color.RGBA{0xe5, 0x73, 0x73, 0xff}, // rgb(229, 115, 115)
+ "Red400": color.RGBA{0xef, 0x53, 0x50, 0xff}, // rgb(239, 83, 80)
+ "Red500": color.RGBA{0xf4, 0x43, 0x36, 0xff}, // rgb(244, 67, 54)
+ "Red600": color.RGBA{0xe5, 0x39, 0x35, 0xff}, // rgb(229, 57, 53)
+ "Red700": color.RGBA{0xd3, 0x2f, 0x2f, 0xff}, // rgb(211, 47, 47)
+ "Red800": color.RGBA{0xc6, 0x28, 0x28, 0xff}, // rgb(198, 40, 40)
+ "Red900": color.RGBA{0xb7, 0x1c, 0x1c, 0xff}, // rgb(183, 28, 28)
+ "RedA100": color.RGBA{0xff, 0x8a, 0x80, 0xff}, // rgb(255, 138, 128)
+ "RedA200": color.RGBA{0xff, 0x52, 0x52, 0xff}, // rgb(255, 82, 82)
+ "RedA400": color.RGBA{0xff, 0x17, 0x44, 0xff}, // rgb(255, 23, 68)
+ "RedA700": color.RGBA{0xd5, 0x00, 0x00, 0xff}, // rgb(213, 0, 0)
+ "Pink50": color.RGBA{0xfc, 0xe4, 0xec, 0xff}, // rgb(252, 228, 236)
+ "Pink100": color.RGBA{0xf8, 0xbb, 0xd0, 0xff}, // rgb(248, 187, 208)
+ "Pink200": color.RGBA{0xf4, 0x8f, 0xb1, 0xff}, // rgb(244, 143, 177)
+ "Pink300": color.RGBA{0xf0, 0x62, 0x92, 0xff}, // rgb(240, 98, 146)
+ "Pink400": color.RGBA{0xec, 0x40, 0x7a, 0xff}, // rgb(236, 64, 122)
+ "Pink500": color.RGBA{0xe9, 0x1e, 0x63, 0xff}, // rgb(233, 30, 99)
+ "Pink600": color.RGBA{0xd8, 0x1b, 0x60, 0xff}, // rgb(216, 27, 96)
+ "Pink700": color.RGBA{0xc2, 0x18, 0x5b, 0xff}, // rgb(194, 24, 91)
+ "Pink800": color.RGBA{0xad, 0x14, 0x57, 0xff}, // rgb(173, 20, 87)
+ "Pink900": color.RGBA{0x88, 0x0e, 0x4f, 0xff}, // rgb(136, 14, 79)
+ "PinkA100": color.RGBA{0xff, 0x80, 0xab, 0xff}, // rgb(255, 128, 171)
+ "PinkA200": color.RGBA{0xff, 0x40, 0x81, 0xff}, // rgb(255, 64, 129)
+ "PinkA400": color.RGBA{0xf5, 0x00, 0x57, 0xff}, // rgb(245, 0, 87)
+ "PinkA700": color.RGBA{0xc5, 0x11, 0x62, 0xff}, // rgb(197, 17, 98)
+ "Purple50": color.RGBA{0xf3, 0xe5, 0xf5, 0xff}, // rgb(243, 229, 245)
+ "Purple100": color.RGBA{0xe1, 0xbe, 0xe7, 0xff}, // rgb(225, 190, 231)
+ "Purple200": color.RGBA{0xce, 0x93, 0xd8, 0xff}, // rgb(206, 147, 216)
+ "Purple300": color.RGBA{0xba, 0x68, 0xc8, 0xff}, // rgb(186, 104, 200)
+ "Purple400": color.RGBA{0xab, 0x47, 0xbc, 0xff}, // rgb(171, 71, 188)
+ "Purple500": color.RGBA{0x9c, 0x27, 0xb0, 0xff}, // rgb(156, 39, 176)
+ "Purple600": color.RGBA{0x8e, 0x24, 0xaa, 0xff}, // rgb(142, 36, 170)
+ "Purple700": color.RGBA{0x7b, 0x1f, 0xa2, 0xff}, // rgb(123, 31, 162)
+ "Purple800": color.RGBA{0x6a, 0x1b, 0x9a, 0xff}, // rgb(106, 27, 154)
+ "Purple900": color.RGBA{0x4a, 0x14, 0x8c, 0xff}, // rgb(74, 20, 140)
+ "PurpleA100": color.RGBA{0xea, 0x80, 0xfc, 0xff}, // rgb(234, 128, 252)
+ "PurpleA200": color.RGBA{0xe0, 0x40, 0xfb, 0xff}, // rgb(224, 64, 251)
+ "PurpleA400": color.RGBA{0xd5, 0x00, 0xf9, 0xff}, // rgb(213, 0, 249)
+ "PurpleA700": color.RGBA{0xaa, 0x00, 0xff, 0xff}, // rgb(170, 0, 255)
+ "DeepPurple50": color.RGBA{0xed, 0xe7, 0xf6, 0xff}, // rgb(237, 231, 246)
+ "DeepPurple100": color.RGBA{0xd1, 0xc4, 0xe9, 0xff}, // rgb(209, 196, 233)
+ "DeepPurple200": color.RGBA{0xb3, 0x9d, 0xdb, 0xff}, // rgb(179, 157, 219)
+ "DeepPurple300": color.RGBA{0x95, 0x75, 0xcd, 0xff}, // rgb(149, 117, 205)
+ "DeepPurple400": color.RGBA{0x7e, 0x57, 0xc2, 0xff}, // rgb(126, 87, 194)
+ "DeepPurple500": color.RGBA{0x67, 0x3a, 0xb7, 0xff}, // rgb(103, 58, 183)
+ "DeepPurple600": color.RGBA{0x5e, 0x35, 0xb1, 0xff}, // rgb(94, 53, 177)
+ "DeepPurple700": color.RGBA{0x51, 0x2d, 0xa8, 0xff}, // rgb(81, 45, 168)
+ "DeepPurple800": color.RGBA{0x45, 0x27, 0xa0, 0xff}, // rgb(69, 39, 160)
+ "DeepPurple900": color.RGBA{0x31, 0x1b, 0x92, 0xff}, // rgb(49, 27, 146)
+ "DeepPurpleA100": color.RGBA{0xb3, 0x88, 0xff, 0xff}, // rgb(179, 136, 255)
+ "DeepPurpleA200": color.RGBA{0x7c, 0x4d, 0xff, 0xff}, // rgb(124, 77, 255)
+ "DeepPurpleA400": color.RGBA{0x65, 0x1f, 0xff, 0xff}, // rgb(101, 31, 255)
+ "DeepPurpleA700": color.RGBA{0x62, 0x00, 0xea, 0xff}, // rgb(98, 0, 234)
+ "Indigo50": color.RGBA{0xe8, 0xea, 0xf6, 0xff}, // rgb(232, 234, 246)
+ "Indigo100": color.RGBA{0xc5, 0xca, 0xe9, 0xff}, // rgb(197, 202, 233)
+ "Indigo200": color.RGBA{0x9f, 0xa8, 0xda, 0xff}, // rgb(159, 168, 218)
+ "Indigo300": color.RGBA{0x79, 0x86, 0xcb, 0xff}, // rgb(121, 134, 203)
+ "Indigo400": color.RGBA{0x5c, 0x6b, 0xc0, 0xff}, // rgb(92, 107, 192)
+ "Indigo500": color.RGBA{0x3f, 0x51, 0xb5, 0xff}, // rgb(63, 81, 181)
+ "Indigo600": color.RGBA{0x39, 0x49, 0xab, 0xff}, // rgb(57, 73, 171)
+ "Indigo700": color.RGBA{0x30, 0x3f, 0x9f, 0xff}, // rgb(48, 63, 159)
+ "Indigo800": color.RGBA{0x28, 0x35, 0x93, 0xff}, // rgb(40, 53, 147)
+ "Indigo900": color.RGBA{0x1a, 0x23, 0x7e, 0xff}, // rgb(26, 35, 126)
+ "IndigoA100": color.RGBA{0x8c, 0x9e, 0xff, 0xff}, // rgb(140, 158, 255)
+ "IndigoA200": color.RGBA{0x53, 0x6d, 0xfe, 0xff}, // rgb(83, 109, 254)
+ "IndigoA400": color.RGBA{0x3d, 0x5a, 0xfe, 0xff}, // rgb(61, 90, 254)
+ "IndigoA700": color.RGBA{0x30, 0x4f, 0xfe, 0xff}, // rgb(48, 79, 254)
+ "Blue50": color.RGBA{0xe3, 0xf2, 0xfd, 0xff}, // rgb(227, 242, 253)
+ "Blue100": color.RGBA{0xbb, 0xde, 0xfb, 0xff}, // rgb(187, 222, 251)
+ "Blue200": color.RGBA{0x90, 0xca, 0xf9, 0xff}, // rgb(144, 202, 249)
+ "Blue300": color.RGBA{0x64, 0xb5, 0xf6, 0xff}, // rgb(100, 181, 246)
+ "Blue400": color.RGBA{0x42, 0xa5, 0xf5, 0xff}, // rgb(66, 165, 245)
+ "Blue500": color.RGBA{0x21, 0x96, 0xf3, 0xff}, // rgb(33, 150, 243)
+ "Blue600": color.RGBA{0x1e, 0x88, 0xe5, 0xff}, // rgb(30, 136, 229)
+ "Blue700": color.RGBA{0x19, 0x76, 0xd2, 0xff}, // rgb(25, 118, 210)
+ "Blue800": color.RGBA{0x15, 0x65, 0xc0, 0xff}, // rgb(21, 101, 192)
+ "Blue900": color.RGBA{0x0d, 0x47, 0xa1, 0xff}, // rgb(13, 71, 161)
+ "BlueA100": color.RGBA{0x82, 0xb1, 0xff, 0xff}, // rgb(130, 177, 255)
+ "BlueA200": color.RGBA{0x44, 0x8a, 0xff, 0xff}, // rgb(68, 138, 255)
+ "BlueA400": color.RGBA{0x29, 0x79, 0xff, 0xff}, // rgb(41, 121, 255)
+ "BlueA700": color.RGBA{0x29, 0x62, 0xff, 0xff}, // rgb(41, 98, 255)
+ "LightBlue50": color.RGBA{0xe1, 0xf5, 0xfe, 0xff}, // rgb(225, 245, 254)
+ "LightBlue100": color.RGBA{0xb3, 0xe5, 0xfc, 0xff}, // rgb(179, 229, 252)
+ "LightBlue200": color.RGBA{0x81, 0xd4, 0xfa, 0xff}, // rgb(129, 212, 250)
+ "LightBlue300": color.RGBA{0x4f, 0xc3, 0xf7, 0xff}, // rgb(79, 195, 247)
+ "LightBlue400": color.RGBA{0x29, 0xb6, 0xf6, 0xff}, // rgb(41, 182, 246)
+ "LightBlue500": color.RGBA{0x03, 0xa9, 0xf4, 0xff}, // rgb(3, 169, 244)
+ "LightBlue600": color.RGBA{0x03, 0x9b, 0xe5, 0xff}, // rgb(3, 155, 229)
+ "LightBlue700": color.RGBA{0x02, 0x88, 0xd1, 0xff}, // rgb(2, 136, 209)
+ "LightBlue800": color.RGBA{0x02, 0x77, 0xbd, 0xff}, // rgb(2, 119, 189)
+ "LightBlue900": color.RGBA{0x01, 0x57, 0x9b, 0xff}, // rgb(1, 87, 155)
+ "LightBlueA100": color.RGBA{0x80, 0xd8, 0xff, 0xff}, // rgb(128, 216, 255)
+ "LightBlueA200": color.RGBA{0x40, 0xc4, 0xff, 0xff}, // rgb(64, 196, 255)
+ "LightBlueA400": color.RGBA{0x00, 0xb0, 0xff, 0xff}, // rgb(0, 176, 255)
+ "LightBlueA700": color.RGBA{0x00, 0x91, 0xea, 0xff}, // rgb(0, 145, 234)
+ "Cyan50": color.RGBA{0xe0, 0xf7, 0xfa, 0xff}, // rgb(224, 247, 250)
+ "Cyan100": color.RGBA{0xb2, 0xeb, 0xf2, 0xff}, // rgb(178, 235, 242)
+ "Cyan200": color.RGBA{0x80, 0xde, 0xea, 0xff}, // rgb(128, 222, 234)
+ "Cyan300": color.RGBA{0x4d, 0xd0, 0xe1, 0xff}, // rgb(77, 208, 225)
+ "Cyan400": color.RGBA{0x26, 0xc6, 0xda, 0xff}, // rgb(38, 198, 218)
+ "Cyan500": color.RGBA{0x00, 0xbc, 0xd4, 0xff}, // rgb(0, 188, 212)
+ "Cyan600": color.RGBA{0x00, 0xac, 0xc1, 0xff}, // rgb(0, 172, 193)
+ "Cyan700": color.RGBA{0x00, 0x97, 0xa7, 0xff}, // rgb(0, 151, 167)
+ "Cyan800": color.RGBA{0x00, 0x83, 0x8f, 0xff}, // rgb(0, 131, 143)
+ "Cyan900": color.RGBA{0x00, 0x60, 0x64, 0xff}, // rgb(0, 96, 100)
+ "CyanA100": color.RGBA{0x84, 0xff, 0xff, 0xff}, // rgb(132, 255, 255)
+ "CyanA200": color.RGBA{0x18, 0xff, 0xff, 0xff}, // rgb(24, 255, 255)
+ "CyanA400": color.RGBA{0x00, 0xe5, 0xff, 0xff}, // rgb(0, 229, 255)
+ "CyanA700": color.RGBA{0x00, 0xb8, 0xd4, 0xff}, // rgb(0, 184, 212)
+ "Teal50": color.RGBA{0xe0, 0xf2, 0xf1, 0xff}, // rgb(224, 242, 241)
+ "Teal100": color.RGBA{0xb2, 0xdf, 0xdb, 0xff}, // rgb(178, 223, 219)
+ "Teal200": color.RGBA{0x80, 0xcb, 0xc4, 0xff}, // rgb(128, 203, 196)
+ "Teal300": color.RGBA{0x4d, 0xb6, 0xac, 0xff}, // rgb(77, 182, 172)
+ "Teal400": color.RGBA{0x26, 0xa6, 0x9a, 0xff}, // rgb(38, 166, 154)
+ "Teal500": color.RGBA{0x00, 0x96, 0x88, 0xff}, // rgb(0, 150, 136)
+ "Teal600": color.RGBA{0x00, 0x89, 0x7b, 0xff}, // rgb(0, 137, 123)
+ "Teal700": color.RGBA{0x00, 0x79, 0x6b, 0xff}, // rgb(0, 121, 107)
+ "Teal800": color.RGBA{0x00, 0x69, 0x5c, 0xff}, // rgb(0, 105, 92)
+ "Teal900": color.RGBA{0x00, 0x4d, 0x40, 0xff}, // rgb(0, 77, 64)
+ "TealA100": color.RGBA{0xa7, 0xff, 0xeb, 0xff}, // rgb(167, 255, 235)
+ "TealA200": color.RGBA{0x64, 0xff, 0xda, 0xff}, // rgb(100, 255, 218)
+ "TealA400": color.RGBA{0x1d, 0xe9, 0xb6, 0xff}, // rgb(29, 233, 182)
+ "TealA700": color.RGBA{0x00, 0xbf, 0xa5, 0xff}, // rgb(0, 191, 165)
+ "Green50": color.RGBA{0xe8, 0xf5, 0xe9, 0xff}, // rgb(232, 245, 233)
+ "Green100": color.RGBA{0xc8, 0xe6, 0xc9, 0xff}, // rgb(200, 230, 201)
+ "Green200": color.RGBA{0xa5, 0xd6, 0xa7, 0xff}, // rgb(165, 214, 167)
+ "Green300": color.RGBA{0x81, 0xc7, 0x84, 0xff}, // rgb(129, 199, 132)
+ "Green400": color.RGBA{0x66, 0xbb, 0x6a, 0xff}, // rgb(102, 187, 106)
+ "Green500": color.RGBA{0x4c, 0xaf, 0x50, 0xff}, // rgb(76, 175, 80)
+ "Green600": color.RGBA{0x43, 0xa0, 0x47, 0xff}, // rgb(67, 160, 71)
+ "Green700": color.RGBA{0x38, 0x8e, 0x3c, 0xff}, // rgb(56, 142, 60)
+ "Green800": color.RGBA{0x2e, 0x7d, 0x32, 0xff}, // rgb(46, 125, 50)
+ "Green900": color.RGBA{0x1b, 0x5e, 0x20, 0xff}, // rgb(27, 94, 32)
+ "GreenA100": color.RGBA{0xb9, 0xf6, 0xca, 0xff}, // rgb(185, 246, 202)
+ "GreenA200": color.RGBA{0x69, 0xf0, 0xae, 0xff}, // rgb(105, 240, 174)
+ "GreenA400": color.RGBA{0x00, 0xe6, 0x76, 0xff}, // rgb(0, 230, 118)
+ "GreenA700": color.RGBA{0x00, 0xc8, 0x53, 0xff}, // rgb(0, 200, 83)
+ "LightGreen50": color.RGBA{0xf1, 0xf8, 0xe9, 0xff}, // rgb(241, 248, 233)
+ "LightGreen100": color.RGBA{0xdc, 0xed, 0xc8, 0xff}, // rgb(220, 237, 200)
+ "LightGreen200": color.RGBA{0xc5, 0xe1, 0xa5, 0xff}, // rgb(197, 225, 165)
+ "LightGreen300": color.RGBA{0xae, 0xd5, 0x81, 0xff}, // rgb(174, 213, 129)
+ "LightGreen400": color.RGBA{0x9c, 0xcc, 0x65, 0xff}, // rgb(156, 204, 101)
+ "LightGreen500": color.RGBA{0x8b, 0xc3, 0x4a, 0xff}, // rgb(139, 195, 74)
+ "LightGreen600": color.RGBA{0x7c, 0xb3, 0x42, 0xff}, // rgb(124, 179, 66)
+ "LightGreen700": color.RGBA{0x68, 0x9f, 0x38, 0xff}, // rgb(104, 159, 56)
+ "LightGreen800": color.RGBA{0x55, 0x8b, 0x2f, 0xff}, // rgb(85, 139, 47)
+ "LightGreen900": color.RGBA{0x33, 0x69, 0x1e, 0xff}, // rgb(51, 105, 30)
+ "LightGreenA100": color.RGBA{0xcc, 0xff, 0x90, 0xff}, // rgb(204, 255, 144)
+ "LightGreenA200": color.RGBA{0xb2, 0xff, 0x59, 0xff}, // rgb(178, 255, 89)
+ "LightGreenA400": color.RGBA{0x76, 0xff, 0x03, 0xff}, // rgb(118, 255, 3)
+ "LightGreenA700": color.RGBA{0x64, 0xdd, 0x17, 0xff}, // rgb(100, 221, 23)
+ "Lime50": color.RGBA{0xf9, 0xfb, 0xe7, 0xff}, // rgb(249, 251, 231)
+ "Lime100": color.RGBA{0xf0, 0xf4, 0xc3, 0xff}, // rgb(240, 244, 195)
+ "Lime200": color.RGBA{0xe6, 0xee, 0x9c, 0xff}, // rgb(230, 238, 156)
+ "Lime300": color.RGBA{0xdc, 0xe7, 0x75, 0xff}, // rgb(220, 231, 117)
+ "Lime400": color.RGBA{0xd4, 0xe1, 0x57, 0xff}, // rgb(212, 225, 87)
+ "Lime500": color.RGBA{0xcd, 0xdc, 0x39, 0xff}, // rgb(205, 220, 57)
+ "Lime600": color.RGBA{0xc0, 0xca, 0x33, 0xff}, // rgb(192, 202, 51)
+ "Lime700": color.RGBA{0xaf, 0xb4, 0x2b, 0xff}, // rgb(175, 180, 43)
+ "Lime800": color.RGBA{0x9e, 0x9d, 0x24, 0xff}, // rgb(158, 157, 36)
+ "Lime900": color.RGBA{0x82, 0x77, 0x17, 0xff}, // rgb(130, 119, 23)
+ "LimeA100": color.RGBA{0xf4, 0xff, 0x81, 0xff}, // rgb(244, 255, 129)
+ "LimeA200": color.RGBA{0xee, 0xff, 0x41, 0xff}, // rgb(238, 255, 65)
+ "LimeA400": color.RGBA{0xc6, 0xff, 0x00, 0xff}, // rgb(198, 255, 0)
+ "LimeA700": color.RGBA{0xae, 0xea, 0x00, 0xff}, // rgb(174, 234, 0)
+ "Yellow50": color.RGBA{0xff, 0xfd, 0xe7, 0xff}, // rgb(255, 253, 231)
+ "Yellow100": color.RGBA{0xff, 0xf9, 0xc4, 0xff}, // rgb(255, 249, 196)
+ "Yellow200": color.RGBA{0xff, 0xf5, 0x9d, 0xff}, // rgb(255, 245, 157)
+ "Yellow300": color.RGBA{0xff, 0xf1, 0x76, 0xff}, // rgb(255, 241, 118)
+ "Yellow400": color.RGBA{0xff, 0xee, 0x58, 0xff}, // rgb(255, 238, 88)
+ "Yellow500": color.RGBA{0xff, 0xeb, 0x3b, 0xff}, // rgb(255, 235, 59)
+ "Yellow600": color.RGBA{0xfd, 0xd8, 0x35, 0xff}, // rgb(253, 216, 53)
+ "Yellow700": color.RGBA{0xfb, 0xc0, 0x2d, 0xff}, // rgb(251, 192, 45)
+ "Yellow800": color.RGBA{0xf9, 0xa8, 0x25, 0xff}, // rgb(249, 168, 37)
+ "Yellow900": color.RGBA{0xf5, 0x7f, 0x17, 0xff}, // rgb(245, 127, 23)
+ "YellowA100": color.RGBA{0xff, 0xff, 0x8d, 0xff}, // rgb(255, 255, 141)
+ "YellowA200": color.RGBA{0xff, 0xff, 0x00, 0xff}, // rgb(255, 255, 0)
+ "YellowA400": color.RGBA{0xff, 0xea, 0x00, 0xff}, // rgb(255, 234, 0)
+ "YellowA700": color.RGBA{0xff, 0xd6, 0x00, 0xff}, // rgb(255, 214, 0)
+ "Amber50": color.RGBA{0xff, 0xf8, 0xe1, 0xff}, // rgb(255, 248, 225)
+ "Amber100": color.RGBA{0xff, 0xec, 0xb3, 0xff}, // rgb(255, 236, 179)
+ "Amber200": color.RGBA{0xff, 0xe0, 0x82, 0xff}, // rgb(255, 224, 130)
+ "Amber300": color.RGBA{0xff, 0xd5, 0x4f, 0xff}, // rgb(255, 213, 79)
+ "Amber400": color.RGBA{0xff, 0xca, 0x28, 0xff}, // rgb(255, 202, 40)
+ "Amber500": color.RGBA{0xff, 0xc1, 0x07, 0xff}, // rgb(255, 193, 7)
+ "Amber600": color.RGBA{0xff, 0xb3, 0x00, 0xff}, // rgb(255, 179, 0)
+ "Amber700": color.RGBA{0xff, 0xa0, 0x00, 0xff}, // rgb(255, 160, 0)
+ "Amber800": color.RGBA{0xff, 0x8f, 0x00, 0xff}, // rgb(255, 143, 0)
+ "Amber900": color.RGBA{0xff, 0x6f, 0x00, 0xff}, // rgb(255, 111, 0)
+ "AmberA100": color.RGBA{0xff, 0xe5, 0x7f, 0xff}, // rgb(255, 229, 127)
+ "AmberA200": color.RGBA{0xff, 0xd7, 0x40, 0xff}, // rgb(255, 215, 64)
+ "AmberA400": color.RGBA{0xff, 0xc4, 0x00, 0xff}, // rgb(255, 196, 0)
+ "AmberA700": color.RGBA{0xff, 0xab, 0x00, 0xff}, // rgb(255, 171, 0)
+ "Orange50": color.RGBA{0xff, 0xf3, 0xe0, 0xff}, // rgb(255, 243, 224)
+ "Orange100": color.RGBA{0xff, 0xe0, 0xb2, 0xff}, // rgb(255, 224, 178)
+ "Orange200": color.RGBA{0xff, 0xcc, 0x80, 0xff}, // rgb(255, 204, 128)
+ "Orange300": color.RGBA{0xff, 0xb7, 0x4d, 0xff}, // rgb(255, 183, 77)
+ "Orange400": color.RGBA{0xff, 0xa7, 0x26, 0xff}, // rgb(255, 167, 38)
+ "Orange500": color.RGBA{0xff, 0x98, 0x00, 0xff}, // rgb(255, 152, 0)
+ "Orange600": color.RGBA{0xfb, 0x8c, 0x00, 0xff}, // rgb(251, 140, 0)
+ "Orange700": color.RGBA{0xf5, 0x7c, 0x00, 0xff}, // rgb(245, 124, 0)
+ "Orange800": color.RGBA{0xef, 0x6c, 0x00, 0xff}, // rgb(239, 108, 0)
+ "Orange900": color.RGBA{0xe6, 0x51, 0x00, 0xff}, // rgb(230, 81, 0)
+ "OrangeA100": color.RGBA{0xff, 0xd1, 0x80, 0xff}, // rgb(255, 209, 128)
+ "OrangeA200": color.RGBA{0xff, 0xab, 0x40, 0xff}, // rgb(255, 171, 64)
+ "OrangeA400": color.RGBA{0xff, 0x91, 0x00, 0xff}, // rgb(255, 145, 0)
+ "OrangeA700": color.RGBA{0xff, 0x6d, 0x00, 0xff}, // rgb(255, 109, 0)
+ "DeepOrange50": color.RGBA{0xfb, 0xe9, 0xe7, 0xff}, // rgb(251, 233, 231)
+ "DeepOrange100": color.RGBA{0xff, 0xcc, 0xbc, 0xff}, // rgb(255, 204, 188)
+ "DeepOrange200": color.RGBA{0xff, 0xab, 0x91, 0xff}, // rgb(255, 171, 145)
+ "DeepOrange300": color.RGBA{0xff, 0x8a, 0x65, 0xff}, // rgb(255, 138, 101)
+ "DeepOrange400": color.RGBA{0xff, 0x70, 0x43, 0xff}, // rgb(255, 112, 67)
+ "DeepOrange500": color.RGBA{0xff, 0x57, 0x22, 0xff}, // rgb(255, 87, 34)
+ "DeepOrange600": color.RGBA{0xf4, 0x51, 0x1e, 0xff}, // rgb(244, 81, 30)
+ "DeepOrange700": color.RGBA{0xe6, 0x4a, 0x19, 0xff}, // rgb(230, 74, 25)
+ "DeepOrange800": color.RGBA{0xd8, 0x43, 0x15, 0xff}, // rgb(216, 67, 21)
+ "DeepOrange900": color.RGBA{0xbf, 0x36, 0x0c, 0xff}, // rgb(191, 54, 12)
+ "DeepOrangeA100": color.RGBA{0xff, 0x9e, 0x80, 0xff}, // rgb(255, 158, 128)
+ "DeepOrangeA200": color.RGBA{0xff, 0x6e, 0x40, 0xff}, // rgb(255, 110, 64)
+ "DeepOrangeA400": color.RGBA{0xff, 0x3d, 0x00, 0xff}, // rgb(255, 61, 0)
+ "DeepOrangeA700": color.RGBA{0xdd, 0x2c, 0x00, 0xff}, // rgb(221, 44, 0)
+ "Brown50": color.RGBA{0xef, 0xeb, 0xe9, 0xff}, // rgb(239, 235, 233)
+ "Brown100": color.RGBA{0xd7, 0xcc, 0xc8, 0xff}, // rgb(215, 204, 200)
+ "Brown200": color.RGBA{0xbc, 0xaa, 0xa4, 0xff}, // rgb(188, 170, 164)
+ "Brown300": color.RGBA{0xa1, 0x88, 0x7f, 0xff}, // rgb(161, 136, 127)
+ "Brown400": color.RGBA{0x8d, 0x6e, 0x63, 0xff}, // rgb(141, 110, 99)
+ "Brown500": color.RGBA{0x79, 0x55, 0x48, 0xff}, // rgb(121, 85, 72)
+ "Brown600": color.RGBA{0x6d, 0x4c, 0x41, 0xff}, // rgb(109, 76, 65)
+ "Brown700": color.RGBA{0x5d, 0x40, 0x37, 0xff}, // rgb(93, 64, 55)
+ "Brown800": color.RGBA{0x4e, 0x34, 0x2e, 0xff}, // rgb(78, 52, 46)
+ "Brown900": color.RGBA{0x3e, 0x27, 0x23, 0xff}, // rgb(62, 39, 35)
+ "Grey50": color.RGBA{0xfa, 0xfa, 0xfa, 0xff}, // rgb(250, 250, 250)
+ "Grey100": color.RGBA{0xf5, 0xf5, 0xf5, 0xff}, // rgb(245, 245, 245)
+ "Grey200": color.RGBA{0xee, 0xee, 0xee, 0xff}, // rgb(238, 238, 238)
+ "Grey300": color.RGBA{0xe0, 0xe0, 0xe0, 0xff}, // rgb(224, 224, 224)
+ "Grey400": color.RGBA{0xbd, 0xbd, 0xbd, 0xff}, // rgb(189, 189, 189)
+ "Grey500": color.RGBA{0x9e, 0x9e, 0x9e, 0xff}, // rgb(158, 158, 158)
+ "Grey600": color.RGBA{0x75, 0x75, 0x75, 0xff}, // rgb(117, 117, 117)
+ "Grey700": color.RGBA{0x61, 0x61, 0x61, 0xff}, // rgb(97, 97, 97)
+ "Grey800": color.RGBA{0x42, 0x42, 0x42, 0xff}, // rgb(66, 66, 66)
+ "Grey900": color.RGBA{0x21, 0x21, 0x21, 0xff}, // rgb(33, 33, 33)
+ "BlueGrey50": color.RGBA{0xec, 0xef, 0xf1, 0xff}, // rgb(236, 239, 241)
+ "BlueGrey100": color.RGBA{0xcf, 0xd8, 0xdc, 0xff}, // rgb(207, 216, 220)
+ "BlueGrey200": color.RGBA{0xb0, 0xbe, 0xc5, 0xff}, // rgb(176, 190, 197)
+ "BlueGrey300": color.RGBA{0x90, 0xa4, 0xae, 0xff}, // rgb(144, 164, 174)
+ "BlueGrey400": color.RGBA{0x78, 0x90, 0x9c, 0xff}, // rgb(120, 144, 156)
+ "BlueGrey500": color.RGBA{0x60, 0x7d, 0x8b, 0xff}, // rgb(96, 125, 139)
+ "BlueGrey600": color.RGBA{0x54, 0x6e, 0x7a, 0xff}, // rgb(84, 110, 122)
+ "BlueGrey700": color.RGBA{0x45, 0x5a, 0x64, 0xff}, // rgb(69, 90, 100)
+ "BlueGrey800": color.RGBA{0x37, 0x47, 0x4f, 0xff}, // rgb(55, 71, 79)
+ "BlueGrey900": color.RGBA{0x26, 0x32, 0x38, 0xff}, // rgb(38, 50, 56)
+ "Black": color.RGBA{0x00, 0x00, 0x00, 0xff}, // rgb(0, 0, 0)
+ "White": color.RGBA{0xff, 0xff, 0xff, 0xff}, // rgb(255, 255, 255)
+}
+
+// Names contains the color names defined in the Material Design style guide.
+var Names = []string{
+ "Red50",
+ "Red100",
+ "Red200",
+ "Red300",
+ "Red400",
+ "Red500",
+ "Red600",
+ "Red700",
+ "Red800",
+ "Red900",
+ "RedA100",
+ "RedA200",
+ "RedA400",
+ "RedA700",
+ "Pink50",
+ "Pink100",
+ "Pink200",
+ "Pink300",
+ "Pink400",
+ "Pink500",
+ "Pink600",
+ "Pink700",
+ "Pink800",
+ "Pink900",
+ "PinkA100",
+ "PinkA200",
+ "PinkA400",
+ "PinkA700",
+ "Purple50",
+ "Purple100",
+ "Purple200",
+ "Purple300",
+ "Purple400",
+ "Purple500",
+ "Purple600",
+ "Purple700",
+ "Purple800",
+ "Purple900",
+ "PurpleA100",
+ "PurpleA200",
+ "PurpleA400",
+ "PurpleA700",
+ "DeepPurple50",
+ "DeepPurple100",
+ "DeepPurple200",
+ "DeepPurple300",
+ "DeepPurple400",
+ "DeepPurple500",
+ "DeepPurple600",
+ "DeepPurple700",
+ "DeepPurple800",
+ "DeepPurple900",
+ "DeepPurpleA100",
+ "DeepPurpleA200",
+ "DeepPurpleA400",
+ "DeepPurpleA700",
+ "Indigo50",
+ "Indigo100",
+ "Indigo200",
+ "Indigo300",
+ "Indigo400",
+ "Indigo500",
+ "Indigo600",
+ "Indigo700",
+ "Indigo800",
+ "Indigo900",
+ "IndigoA100",
+ "IndigoA200",
+ "IndigoA400",
+ "IndigoA700",
+ "Blue50",
+ "Blue100",
+ "Blue200",
+ "Blue300",
+ "Blue400",
+ "Blue500",
+ "Blue600",
+ "Blue700",
+ "Blue800",
+ "Blue900",
+ "BlueA100",
+ "BlueA200",
+ "BlueA400",
+ "BlueA700",
+ "LightBlue50",
+ "LightBlue100",
+ "LightBlue200",
+ "LightBlue300",
+ "LightBlue400",
+ "LightBlue500",
+ "LightBlue600",
+ "LightBlue700",
+ "LightBlue800",
+ "LightBlue900",
+ "LightBlueA100",
+ "LightBlueA200",
+ "LightBlueA400",
+ "LightBlueA700",
+ "Cyan50",
+ "Cyan100",
+ "Cyan200",
+ "Cyan300",
+ "Cyan400",
+ "Cyan500",
+ "Cyan600",
+ "Cyan700",
+ "Cyan800",
+ "Cyan900",
+ "CyanA100",
+ "CyanA200",
+ "CyanA400",
+ "CyanA700",
+ "Teal50",
+ "Teal100",
+ "Teal200",
+ "Teal300",
+ "Teal400",
+ "Teal500",
+ "Teal600",
+ "Teal700",
+ "Teal800",
+ "Teal900",
+ "TealA100",
+ "TealA200",
+ "TealA400",
+ "TealA700",
+ "Green50",
+ "Green100",
+ "Green200",
+ "Green300",
+ "Green400",
+ "Green500",
+ "Green600",
+ "Green700",
+ "Green800",
+ "Green900",
+ "GreenA100",
+ "GreenA200",
+ "GreenA400",
+ "GreenA700",
+ "LightGreen50",
+ "LightGreen100",
+ "LightGreen200",
+ "LightGreen300",
+ "LightGreen400",
+ "LightGreen500",
+ "LightGreen600",
+ "LightGreen700",
+ "LightGreen800",
+ "LightGreen900",
+ "LightGreenA100",
+ "LightGreenA200",
+ "LightGreenA400",
+ "LightGreenA700",
+ "Lime50",
+ "Lime100",
+ "Lime200",
+ "Lime300",
+ "Lime400",
+ "Lime500",
+ "Lime600",
+ "Lime700",
+ "Lime800",
+ "Lime900",
+ "LimeA100",
+ "LimeA200",
+ "LimeA400",
+ "LimeA700",
+ "Yellow50",
+ "Yellow100",
+ "Yellow200",
+ "Yellow300",
+ "Yellow400",
+ "Yellow500",
+ "Yellow600",
+ "Yellow700",
+ "Yellow800",
+ "Yellow900",
+ "YellowA100",
+ "YellowA200",
+ "YellowA400",
+ "YellowA700",
+ "Amber50",
+ "Amber100",
+ "Amber200",
+ "Amber300",
+ "Amber400",
+ "Amber500",
+ "Amber600",
+ "Amber700",
+ "Amber800",
+ "Amber900",
+ "AmberA100",
+ "AmberA200",
+ "AmberA400",
+ "AmberA700",
+ "Orange50",
+ "Orange100",
+ "Orange200",
+ "Orange300",
+ "Orange400",
+ "Orange500",
+ "Orange600",
+ "Orange700",
+ "Orange800",
+ "Orange900",
+ "OrangeA100",
+ "OrangeA200",
+ "OrangeA400",
+ "OrangeA700",
+ "DeepOrange50",
+ "DeepOrange100",
+ "DeepOrange200",
+ "DeepOrange300",
+ "DeepOrange400",
+ "DeepOrange500",
+ "DeepOrange600",
+ "DeepOrange700",
+ "DeepOrange800",
+ "DeepOrange900",
+ "DeepOrangeA100",
+ "DeepOrangeA200",
+ "DeepOrangeA400",
+ "DeepOrangeA700",
+ "Brown50",
+ "Brown100",
+ "Brown200",
+ "Brown300",
+ "Brown400",
+ "Brown500",
+ "Brown600",
+ "Brown700",
+ "Brown800",
+ "Brown900",
+ "Grey50",
+ "Grey100",
+ "Grey200",
+ "Grey300",
+ "Grey400",
+ "Grey500",
+ "Grey600",
+ "Grey700",
+ "Grey800",
+ "Grey900",
+ "BlueGrey50",
+ "BlueGrey100",
+ "BlueGrey200",
+ "BlueGrey300",
+ "BlueGrey400",
+ "BlueGrey500",
+ "BlueGrey600",
+ "BlueGrey700",
+ "BlueGrey800",
+ "BlueGrey900",
+ "Black",
+ "White",
+}
+
+var (
+ Red50 = color.RGBA{0xff, 0xeb, 0xee, 0xff} // rgb(255, 235, 238)
+ Red100 = color.RGBA{0xff, 0xcd, 0xd2, 0xff} // rgb(255, 205, 210)
+ Red200 = color.RGBA{0xef, 0x9a, 0x9a, 0xff} // rgb(239, 154, 154)
+ Red300 = color.RGBA{0xe5, 0x73, 0x73, 0xff} // rgb(229, 115, 115)
+ Red400 = color.RGBA{0xef, 0x53, 0x50, 0xff} // rgb(239, 83, 80)
+ Red500 = color.RGBA{0xf4, 0x43, 0x36, 0xff} // rgb(244, 67, 54)
+ Red600 = color.RGBA{0xe5, 0x39, 0x35, 0xff} // rgb(229, 57, 53)
+ Red700 = color.RGBA{0xd3, 0x2f, 0x2f, 0xff} // rgb(211, 47, 47)
+ Red800 = color.RGBA{0xc6, 0x28, 0x28, 0xff} // rgb(198, 40, 40)
+ Red900 = color.RGBA{0xb7, 0x1c, 0x1c, 0xff} // rgb(183, 28, 28)
+ RedA100 = color.RGBA{0xff, 0x8a, 0x80, 0xff} // rgb(255, 138, 128)
+ RedA200 = color.RGBA{0xff, 0x52, 0x52, 0xff} // rgb(255, 82, 82)
+ RedA400 = color.RGBA{0xff, 0x17, 0x44, 0xff} // rgb(255, 23, 68)
+ RedA700 = color.RGBA{0xd5, 0x00, 0x00, 0xff} // rgb(213, 0, 0)
+ Pink50 = color.RGBA{0xfc, 0xe4, 0xec, 0xff} // rgb(252, 228, 236)
+ Pink100 = color.RGBA{0xf8, 0xbb, 0xd0, 0xff} // rgb(248, 187, 208)
+ Pink200 = color.RGBA{0xf4, 0x8f, 0xb1, 0xff} // rgb(244, 143, 177)
+ Pink300 = color.RGBA{0xf0, 0x62, 0x92, 0xff} // rgb(240, 98, 146)
+ Pink400 = color.RGBA{0xec, 0x40, 0x7a, 0xff} // rgb(236, 64, 122)
+ Pink500 = color.RGBA{0xe9, 0x1e, 0x63, 0xff} // rgb(233, 30, 99)
+ Pink600 = color.RGBA{0xd8, 0x1b, 0x60, 0xff} // rgb(216, 27, 96)
+ Pink700 = color.RGBA{0xc2, 0x18, 0x5b, 0xff} // rgb(194, 24, 91)
+ Pink800 = color.RGBA{0xad, 0x14, 0x57, 0xff} // rgb(173, 20, 87)
+ Pink900 = color.RGBA{0x88, 0x0e, 0x4f, 0xff} // rgb(136, 14, 79)
+ PinkA100 = color.RGBA{0xff, 0x80, 0xab, 0xff} // rgb(255, 128, 171)
+ PinkA200 = color.RGBA{0xff, 0x40, 0x81, 0xff} // rgb(255, 64, 129)
+ PinkA400 = color.RGBA{0xf5, 0x00, 0x57, 0xff} // rgb(245, 0, 87)
+ PinkA700 = color.RGBA{0xc5, 0x11, 0x62, 0xff} // rgb(197, 17, 98)
+ Purple50 = color.RGBA{0xf3, 0xe5, 0xf5, 0xff} // rgb(243, 229, 245)
+ Purple100 = color.RGBA{0xe1, 0xbe, 0xe7, 0xff} // rgb(225, 190, 231)
+ Purple200 = color.RGBA{0xce, 0x93, 0xd8, 0xff} // rgb(206, 147, 216)
+ Purple300 = color.RGBA{0xba, 0x68, 0xc8, 0xff} // rgb(186, 104, 200)
+ Purple400 = color.RGBA{0xab, 0x47, 0xbc, 0xff} // rgb(171, 71, 188)
+ Purple500 = color.RGBA{0x9c, 0x27, 0xb0, 0xff} // rgb(156, 39, 176)
+ Purple600 = color.RGBA{0x8e, 0x24, 0xaa, 0xff} // rgb(142, 36, 170)
+ Purple700 = color.RGBA{0x7b, 0x1f, 0xa2, 0xff} // rgb(123, 31, 162)
+ Purple800 = color.RGBA{0x6a, 0x1b, 0x9a, 0xff} // rgb(106, 27, 154)
+ Purple900 = color.RGBA{0x4a, 0x14, 0x8c, 0xff} // rgb(74, 20, 140)
+ PurpleA100 = color.RGBA{0xea, 0x80, 0xfc, 0xff} // rgb(234, 128, 252)
+ PurpleA200 = color.RGBA{0xe0, 0x40, 0xfb, 0xff} // rgb(224, 64, 251)
+ PurpleA400 = color.RGBA{0xd5, 0x00, 0xf9, 0xff} // rgb(213, 0, 249)
+ PurpleA700 = color.RGBA{0xaa, 0x00, 0xff, 0xff} // rgb(170, 0, 255)
+ DeepPurple50 = color.RGBA{0xed, 0xe7, 0xf6, 0xff} // rgb(237, 231, 246)
+ DeepPurple100 = color.RGBA{0xd1, 0xc4, 0xe9, 0xff} // rgb(209, 196, 233)
+ DeepPurple200 = color.RGBA{0xb3, 0x9d, 0xdb, 0xff} // rgb(179, 157, 219)
+ DeepPurple300 = color.RGBA{0x95, 0x75, 0xcd, 0xff} // rgb(149, 117, 205)
+ DeepPurple400 = color.RGBA{0x7e, 0x57, 0xc2, 0xff} // rgb(126, 87, 194)
+ DeepPurple500 = color.RGBA{0x67, 0x3a, 0xb7, 0xff} // rgb(103, 58, 183)
+ DeepPurple600 = color.RGBA{0x5e, 0x35, 0xb1, 0xff} // rgb(94, 53, 177)
+ DeepPurple700 = color.RGBA{0x51, 0x2d, 0xa8, 0xff} // rgb(81, 45, 168)
+ DeepPurple800 = color.RGBA{0x45, 0x27, 0xa0, 0xff} // rgb(69, 39, 160)
+ DeepPurple900 = color.RGBA{0x31, 0x1b, 0x92, 0xff} // rgb(49, 27, 146)
+ DeepPurpleA100 = color.RGBA{0xb3, 0x88, 0xff, 0xff} // rgb(179, 136, 255)
+ DeepPurpleA200 = color.RGBA{0x7c, 0x4d, 0xff, 0xff} // rgb(124, 77, 255)
+ DeepPurpleA400 = color.RGBA{0x65, 0x1f, 0xff, 0xff} // rgb(101, 31, 255)
+ DeepPurpleA700 = color.RGBA{0x62, 0x00, 0xea, 0xff} // rgb(98, 0, 234)
+ Indigo50 = color.RGBA{0xe8, 0xea, 0xf6, 0xff} // rgb(232, 234, 246)
+ Indigo100 = color.RGBA{0xc5, 0xca, 0xe9, 0xff} // rgb(197, 202, 233)
+ Indigo200 = color.RGBA{0x9f, 0xa8, 0xda, 0xff} // rgb(159, 168, 218)
+ Indigo300 = color.RGBA{0x79, 0x86, 0xcb, 0xff} // rgb(121, 134, 203)
+ Indigo400 = color.RGBA{0x5c, 0x6b, 0xc0, 0xff} // rgb(92, 107, 192)
+ Indigo500 = color.RGBA{0x3f, 0x51, 0xb5, 0xff} // rgb(63, 81, 181)
+ Indigo600 = color.RGBA{0x39, 0x49, 0xab, 0xff} // rgb(57, 73, 171)
+ Indigo700 = color.RGBA{0x30, 0x3f, 0x9f, 0xff} // rgb(48, 63, 159)
+ Indigo800 = color.RGBA{0x28, 0x35, 0x93, 0xff} // rgb(40, 53, 147)
+ Indigo900 = color.RGBA{0x1a, 0x23, 0x7e, 0xff} // rgb(26, 35, 126)
+ IndigoA100 = color.RGBA{0x8c, 0x9e, 0xff, 0xff} // rgb(140, 158, 255)
+ IndigoA200 = color.RGBA{0x53, 0x6d, 0xfe, 0xff} // rgb(83, 109, 254)
+ IndigoA400 = color.RGBA{0x3d, 0x5a, 0xfe, 0xff} // rgb(61, 90, 254)
+ IndigoA700 = color.RGBA{0x30, 0x4f, 0xfe, 0xff} // rgb(48, 79, 254)
+ Blue50 = color.RGBA{0xe3, 0xf2, 0xfd, 0xff} // rgb(227, 242, 253)
+ Blue100 = color.RGBA{0xbb, 0xde, 0xfb, 0xff} // rgb(187, 222, 251)
+ Blue200 = color.RGBA{0x90, 0xca, 0xf9, 0xff} // rgb(144, 202, 249)
+ Blue300 = color.RGBA{0x64, 0xb5, 0xf6, 0xff} // rgb(100, 181, 246)
+ Blue400 = color.RGBA{0x42, 0xa5, 0xf5, 0xff} // rgb(66, 165, 245)
+ Blue500 = color.RGBA{0x21, 0x96, 0xf3, 0xff} // rgb(33, 150, 243)
+ Blue600 = color.RGBA{0x1e, 0x88, 0xe5, 0xff} // rgb(30, 136, 229)
+ Blue700 = color.RGBA{0x19, 0x76, 0xd2, 0xff} // rgb(25, 118, 210)
+ Blue800 = color.RGBA{0x15, 0x65, 0xc0, 0xff} // rgb(21, 101, 192)
+ Blue900 = color.RGBA{0x0d, 0x47, 0xa1, 0xff} // rgb(13, 71, 161)
+ BlueA100 = color.RGBA{0x82, 0xb1, 0xff, 0xff} // rgb(130, 177, 255)
+ BlueA200 = color.RGBA{0x44, 0x8a, 0xff, 0xff} // rgb(68, 138, 255)
+ BlueA400 = color.RGBA{0x29, 0x79, 0xff, 0xff} // rgb(41, 121, 255)
+ BlueA700 = color.RGBA{0x29, 0x62, 0xff, 0xff} // rgb(41, 98, 255)
+ LightBlue50 = color.RGBA{0xe1, 0xf5, 0xfe, 0xff} // rgb(225, 245, 254)
+ LightBlue100 = color.RGBA{0xb3, 0xe5, 0xfc, 0xff} // rgb(179, 229, 252)
+ LightBlue200 = color.RGBA{0x81, 0xd4, 0xfa, 0xff} // rgb(129, 212, 250)
+ LightBlue300 = color.RGBA{0x4f, 0xc3, 0xf7, 0xff} // rgb(79, 195, 247)
+ LightBlue400 = color.RGBA{0x29, 0xb6, 0xf6, 0xff} // rgb(41, 182, 246)
+ LightBlue500 = color.RGBA{0x03, 0xa9, 0xf4, 0xff} // rgb(3, 169, 244)
+ LightBlue600 = color.RGBA{0x03, 0x9b, 0xe5, 0xff} // rgb(3, 155, 229)
+ LightBlue700 = color.RGBA{0x02, 0x88, 0xd1, 0xff} // rgb(2, 136, 209)
+ LightBlue800 = color.RGBA{0x02, 0x77, 0xbd, 0xff} // rgb(2, 119, 189)
+ LightBlue900 = color.RGBA{0x01, 0x57, 0x9b, 0xff} // rgb(1, 87, 155)
+ LightBlueA100 = color.RGBA{0x80, 0xd8, 0xff, 0xff} // rgb(128, 216, 255)
+ LightBlueA200 = color.RGBA{0x40, 0xc4, 0xff, 0xff} // rgb(64, 196, 255)
+ LightBlueA400 = color.RGBA{0x00, 0xb0, 0xff, 0xff} // rgb(0, 176, 255)
+ LightBlueA700 = color.RGBA{0x00, 0x91, 0xea, 0xff} // rgb(0, 145, 234)
+ Cyan50 = color.RGBA{0xe0, 0xf7, 0xfa, 0xff} // rgb(224, 247, 250)
+ Cyan100 = color.RGBA{0xb2, 0xeb, 0xf2, 0xff} // rgb(178, 235, 242)
+ Cyan200 = color.RGBA{0x80, 0xde, 0xea, 0xff} // rgb(128, 222, 234)
+ Cyan300 = color.RGBA{0x4d, 0xd0, 0xe1, 0xff} // rgb(77, 208, 225)
+ Cyan400 = color.RGBA{0x26, 0xc6, 0xda, 0xff} // rgb(38, 198, 218)
+ Cyan500 = color.RGBA{0x00, 0xbc, 0xd4, 0xff} // rgb(0, 188, 212)
+ Cyan600 = color.RGBA{0x00, 0xac, 0xc1, 0xff} // rgb(0, 172, 193)
+ Cyan700 = color.RGBA{0x00, 0x97, 0xa7, 0xff} // rgb(0, 151, 167)
+ Cyan800 = color.RGBA{0x00, 0x83, 0x8f, 0xff} // rgb(0, 131, 143)
+ Cyan900 = color.RGBA{0x00, 0x60, 0x64, 0xff} // rgb(0, 96, 100)
+ CyanA100 = color.RGBA{0x84, 0xff, 0xff, 0xff} // rgb(132, 255, 255)
+ CyanA200 = color.RGBA{0x18, 0xff, 0xff, 0xff} // rgb(24, 255, 255)
+ CyanA400 = color.RGBA{0x00, 0xe5, 0xff, 0xff} // rgb(0, 229, 255)
+ CyanA700 = color.RGBA{0x00, 0xb8, 0xd4, 0xff} // rgb(0, 184, 212)
+ Teal50 = color.RGBA{0xe0, 0xf2, 0xf1, 0xff} // rgb(224, 242, 241)
+ Teal100 = color.RGBA{0xb2, 0xdf, 0xdb, 0xff} // rgb(178, 223, 219)
+ Teal200 = color.RGBA{0x80, 0xcb, 0xc4, 0xff} // rgb(128, 203, 196)
+ Teal300 = color.RGBA{0x4d, 0xb6, 0xac, 0xff} // rgb(77, 182, 172)
+ Teal400 = color.RGBA{0x26, 0xa6, 0x9a, 0xff} // rgb(38, 166, 154)
+ Teal500 = color.RGBA{0x00, 0x96, 0x88, 0xff} // rgb(0, 150, 136)
+ Teal600 = color.RGBA{0x00, 0x89, 0x7b, 0xff} // rgb(0, 137, 123)
+ Teal700 = color.RGBA{0x00, 0x79, 0x6b, 0xff} // rgb(0, 121, 107)
+ Teal800 = color.RGBA{0x00, 0x69, 0x5c, 0xff} // rgb(0, 105, 92)
+ Teal900 = color.RGBA{0x00, 0x4d, 0x40, 0xff} // rgb(0, 77, 64)
+ TealA100 = color.RGBA{0xa7, 0xff, 0xeb, 0xff} // rgb(167, 255, 235)
+ TealA200 = color.RGBA{0x64, 0xff, 0xda, 0xff} // rgb(100, 255, 218)
+ TealA400 = color.RGBA{0x1d, 0xe9, 0xb6, 0xff} // rgb(29, 233, 182)
+ TealA700 = color.RGBA{0x00, 0xbf, 0xa5, 0xff} // rgb(0, 191, 165)
+ Green50 = color.RGBA{0xe8, 0xf5, 0xe9, 0xff} // rgb(232, 245, 233)
+ Green100 = color.RGBA{0xc8, 0xe6, 0xc9, 0xff} // rgb(200, 230, 201)
+ Green200 = color.RGBA{0xa5, 0xd6, 0xa7, 0xff} // rgb(165, 214, 167)
+ Green300 = color.RGBA{0x81, 0xc7, 0x84, 0xff} // rgb(129, 199, 132)
+ Green400 = color.RGBA{0x66, 0xbb, 0x6a, 0xff} // rgb(102, 187, 106)
+ Green500 = color.RGBA{0x4c, 0xaf, 0x50, 0xff} // rgb(76, 175, 80)
+ Green600 = color.RGBA{0x43, 0xa0, 0x47, 0xff} // rgb(67, 160, 71)
+ Green700 = color.RGBA{0x38, 0x8e, 0x3c, 0xff} // rgb(56, 142, 60)
+ Green800 = color.RGBA{0x2e, 0x7d, 0x32, 0xff} // rgb(46, 125, 50)
+ Green900 = color.RGBA{0x1b, 0x5e, 0x20, 0xff} // rgb(27, 94, 32)
+ GreenA100 = color.RGBA{0xb9, 0xf6, 0xca, 0xff} // rgb(185, 246, 202)
+ GreenA200 = color.RGBA{0x69, 0xf0, 0xae, 0xff} // rgb(105, 240, 174)
+ GreenA400 = color.RGBA{0x00, 0xe6, 0x76, 0xff} // rgb(0, 230, 118)
+ GreenA700 = color.RGBA{0x00, 0xc8, 0x53, 0xff} // rgb(0, 200, 83)
+ LightGreen50 = color.RGBA{0xf1, 0xf8, 0xe9, 0xff} // rgb(241, 248, 233)
+ LightGreen100 = color.RGBA{0xdc, 0xed, 0xc8, 0xff} // rgb(220, 237, 200)
+ LightGreen200 = color.RGBA{0xc5, 0xe1, 0xa5, 0xff} // rgb(197, 225, 165)
+ LightGreen300 = color.RGBA{0xae, 0xd5, 0x81, 0xff} // rgb(174, 213, 129)
+ LightGreen400 = color.RGBA{0x9c, 0xcc, 0x65, 0xff} // rgb(156, 204, 101)
+ LightGreen500 = color.RGBA{0x8b, 0xc3, 0x4a, 0xff} // rgb(139, 195, 74)
+ LightGreen600 = color.RGBA{0x7c, 0xb3, 0x42, 0xff} // rgb(124, 179, 66)
+ LightGreen700 = color.RGBA{0x68, 0x9f, 0x38, 0xff} // rgb(104, 159, 56)
+ LightGreen800 = color.RGBA{0x55, 0x8b, 0x2f, 0xff} // rgb(85, 139, 47)
+ LightGreen900 = color.RGBA{0x33, 0x69, 0x1e, 0xff} // rgb(51, 105, 30)
+ LightGreenA100 = color.RGBA{0xcc, 0xff, 0x90, 0xff} // rgb(204, 255, 144)
+ LightGreenA200 = color.RGBA{0xb2, 0xff, 0x59, 0xff} // rgb(178, 255, 89)
+ LightGreenA400 = color.RGBA{0x76, 0xff, 0x03, 0xff} // rgb(118, 255, 3)
+ LightGreenA700 = color.RGBA{0x64, 0xdd, 0x17, 0xff} // rgb(100, 221, 23)
+ Lime50 = color.RGBA{0xf9, 0xfb, 0xe7, 0xff} // rgb(249, 251, 231)
+ Lime100 = color.RGBA{0xf0, 0xf4, 0xc3, 0xff} // rgb(240, 244, 195)
+ Lime200 = color.RGBA{0xe6, 0xee, 0x9c, 0xff} // rgb(230, 238, 156)
+ Lime300 = color.RGBA{0xdc, 0xe7, 0x75, 0xff} // rgb(220, 231, 117)
+ Lime400 = color.RGBA{0xd4, 0xe1, 0x57, 0xff} // rgb(212, 225, 87)
+ Lime500 = color.RGBA{0xcd, 0xdc, 0x39, 0xff} // rgb(205, 220, 57)
+ Lime600 = color.RGBA{0xc0, 0xca, 0x33, 0xff} // rgb(192, 202, 51)
+ Lime700 = color.RGBA{0xaf, 0xb4, 0x2b, 0xff} // rgb(175, 180, 43)
+ Lime800 = color.RGBA{0x9e, 0x9d, 0x24, 0xff} // rgb(158, 157, 36)
+ Lime900 = color.RGBA{0x82, 0x77, 0x17, 0xff} // rgb(130, 119, 23)
+ LimeA100 = color.RGBA{0xf4, 0xff, 0x81, 0xff} // rgb(244, 255, 129)
+ LimeA200 = color.RGBA{0xee, 0xff, 0x41, 0xff} // rgb(238, 255, 65)
+ LimeA400 = color.RGBA{0xc6, 0xff, 0x00, 0xff} // rgb(198, 255, 0)
+ LimeA700 = color.RGBA{0xae, 0xea, 0x00, 0xff} // rgb(174, 234, 0)
+ Yellow50 = color.RGBA{0xff, 0xfd, 0xe7, 0xff} // rgb(255, 253, 231)
+ Yellow100 = color.RGBA{0xff, 0xf9, 0xc4, 0xff} // rgb(255, 249, 196)
+ Yellow200 = color.RGBA{0xff, 0xf5, 0x9d, 0xff} // rgb(255, 245, 157)
+ Yellow300 = color.RGBA{0xff, 0xf1, 0x76, 0xff} // rgb(255, 241, 118)
+ Yellow400 = color.RGBA{0xff, 0xee, 0x58, 0xff} // rgb(255, 238, 88)
+ Yellow500 = color.RGBA{0xff, 0xeb, 0x3b, 0xff} // rgb(255, 235, 59)
+ Yellow600 = color.RGBA{0xfd, 0xd8, 0x35, 0xff} // rgb(253, 216, 53)
+ Yellow700 = color.RGBA{0xfb, 0xc0, 0x2d, 0xff} // rgb(251, 192, 45)
+ Yellow800 = color.RGBA{0xf9, 0xa8, 0x25, 0xff} // rgb(249, 168, 37)
+ Yellow900 = color.RGBA{0xf5, 0x7f, 0x17, 0xff} // rgb(245, 127, 23)
+ YellowA100 = color.RGBA{0xff, 0xff, 0x8d, 0xff} // rgb(255, 255, 141)
+ YellowA200 = color.RGBA{0xff, 0xff, 0x00, 0xff} // rgb(255, 255, 0)
+ YellowA400 = color.RGBA{0xff, 0xea, 0x00, 0xff} // rgb(255, 234, 0)
+ YellowA700 = color.RGBA{0xff, 0xd6, 0x00, 0xff} // rgb(255, 214, 0)
+ Amber50 = color.RGBA{0xff, 0xf8, 0xe1, 0xff} // rgb(255, 248, 225)
+ Amber100 = color.RGBA{0xff, 0xec, 0xb3, 0xff} // rgb(255, 236, 179)
+ Amber200 = color.RGBA{0xff, 0xe0, 0x82, 0xff} // rgb(255, 224, 130)
+ Amber300 = color.RGBA{0xff, 0xd5, 0x4f, 0xff} // rgb(255, 213, 79)
+ Amber400 = color.RGBA{0xff, 0xca, 0x28, 0xff} // rgb(255, 202, 40)
+ Amber500 = color.RGBA{0xff, 0xc1, 0x07, 0xff} // rgb(255, 193, 7)
+ Amber600 = color.RGBA{0xff, 0xb3, 0x00, 0xff} // rgb(255, 179, 0)
+ Amber700 = color.RGBA{0xff, 0xa0, 0x00, 0xff} // rgb(255, 160, 0)
+ Amber800 = color.RGBA{0xff, 0x8f, 0x00, 0xff} // rgb(255, 143, 0)
+ Amber900 = color.RGBA{0xff, 0x6f, 0x00, 0xff} // rgb(255, 111, 0)
+ AmberA100 = color.RGBA{0xff, 0xe5, 0x7f, 0xff} // rgb(255, 229, 127)
+ AmberA200 = color.RGBA{0xff, 0xd7, 0x40, 0xff} // rgb(255, 215, 64)
+ AmberA400 = color.RGBA{0xff, 0xc4, 0x00, 0xff} // rgb(255, 196, 0)
+ AmberA700 = color.RGBA{0xff, 0xab, 0x00, 0xff} // rgb(255, 171, 0)
+ Orange50 = color.RGBA{0xff, 0xf3, 0xe0, 0xff} // rgb(255, 243, 224)
+ Orange100 = color.RGBA{0xff, 0xe0, 0xb2, 0xff} // rgb(255, 224, 178)
+ Orange200 = color.RGBA{0xff, 0xcc, 0x80, 0xff} // rgb(255, 204, 128)
+ Orange300 = color.RGBA{0xff, 0xb7, 0x4d, 0xff} // rgb(255, 183, 77)
+ Orange400 = color.RGBA{0xff, 0xa7, 0x26, 0xff} // rgb(255, 167, 38)
+ Orange500 = color.RGBA{0xff, 0x98, 0x00, 0xff} // rgb(255, 152, 0)
+ Orange600 = color.RGBA{0xfb, 0x8c, 0x00, 0xff} // rgb(251, 140, 0)
+ Orange700 = color.RGBA{0xf5, 0x7c, 0x00, 0xff} // rgb(245, 124, 0)
+ Orange800 = color.RGBA{0xef, 0x6c, 0x00, 0xff} // rgb(239, 108, 0)
+ Orange900 = color.RGBA{0xe6, 0x51, 0x00, 0xff} // rgb(230, 81, 0)
+ OrangeA100 = color.RGBA{0xff, 0xd1, 0x80, 0xff} // rgb(255, 209, 128)
+ OrangeA200 = color.RGBA{0xff, 0xab, 0x40, 0xff} // rgb(255, 171, 64)
+ OrangeA400 = color.RGBA{0xff, 0x91, 0x00, 0xff} // rgb(255, 145, 0)
+ OrangeA700 = color.RGBA{0xff, 0x6d, 0x00, 0xff} // rgb(255, 109, 0)
+ DeepOrange50 = color.RGBA{0xfb, 0xe9, 0xe7, 0xff} // rgb(251, 233, 231)
+ DeepOrange100 = color.RGBA{0xff, 0xcc, 0xbc, 0xff} // rgb(255, 204, 188)
+ DeepOrange200 = color.RGBA{0xff, 0xab, 0x91, 0xff} // rgb(255, 171, 145)
+ DeepOrange300 = color.RGBA{0xff, 0x8a, 0x65, 0xff} // rgb(255, 138, 101)
+ DeepOrange400 = color.RGBA{0xff, 0x70, 0x43, 0xff} // rgb(255, 112, 67)
+ DeepOrange500 = color.RGBA{0xff, 0x57, 0x22, 0xff} // rgb(255, 87, 34)
+ DeepOrange600 = color.RGBA{0xf4, 0x51, 0x1e, 0xff} // rgb(244, 81, 30)
+ DeepOrange700 = color.RGBA{0xe6, 0x4a, 0x19, 0xff} // rgb(230, 74, 25)
+ DeepOrange800 = color.RGBA{0xd8, 0x43, 0x15, 0xff} // rgb(216, 67, 21)
+ DeepOrange900 = color.RGBA{0xbf, 0x36, 0x0c, 0xff} // rgb(191, 54, 12)
+ DeepOrangeA100 = color.RGBA{0xff, 0x9e, 0x80, 0xff} // rgb(255, 158, 128)
+ DeepOrangeA200 = color.RGBA{0xff, 0x6e, 0x40, 0xff} // rgb(255, 110, 64)
+ DeepOrangeA400 = color.RGBA{0xff, 0x3d, 0x00, 0xff} // rgb(255, 61, 0)
+ DeepOrangeA700 = color.RGBA{0xdd, 0x2c, 0x00, 0xff} // rgb(221, 44, 0)
+ Brown50 = color.RGBA{0xef, 0xeb, 0xe9, 0xff} // rgb(239, 235, 233)
+ Brown100 = color.RGBA{0xd7, 0xcc, 0xc8, 0xff} // rgb(215, 204, 200)
+ Brown200 = color.RGBA{0xbc, 0xaa, 0xa4, 0xff} // rgb(188, 170, 164)
+ Brown300 = color.RGBA{0xa1, 0x88, 0x7f, 0xff} // rgb(161, 136, 127)
+ Brown400 = color.RGBA{0x8d, 0x6e, 0x63, 0xff} // rgb(141, 110, 99)
+ Brown500 = color.RGBA{0x79, 0x55, 0x48, 0xff} // rgb(121, 85, 72)
+ Brown600 = color.RGBA{0x6d, 0x4c, 0x41, 0xff} // rgb(109, 76, 65)
+ Brown700 = color.RGBA{0x5d, 0x40, 0x37, 0xff} // rgb(93, 64, 55)
+ Brown800 = color.RGBA{0x4e, 0x34, 0x2e, 0xff} // rgb(78, 52, 46)
+ Brown900 = color.RGBA{0x3e, 0x27, 0x23, 0xff} // rgb(62, 39, 35)
+ Grey50 = color.RGBA{0xfa, 0xfa, 0xfa, 0xff} // rgb(250, 250, 250)
+ Grey100 = color.RGBA{0xf5, 0xf5, 0xf5, 0xff} // rgb(245, 245, 245)
+ Grey200 = color.RGBA{0xee, 0xee, 0xee, 0xff} // rgb(238, 238, 238)
+ Grey300 = color.RGBA{0xe0, 0xe0, 0xe0, 0xff} // rgb(224, 224, 224)
+ Grey400 = color.RGBA{0xbd, 0xbd, 0xbd, 0xff} // rgb(189, 189, 189)
+ Grey500 = color.RGBA{0x9e, 0x9e, 0x9e, 0xff} // rgb(158, 158, 158)
+ Grey600 = color.RGBA{0x75, 0x75, 0x75, 0xff} // rgb(117, 117, 117)
+ Grey700 = color.RGBA{0x61, 0x61, 0x61, 0xff} // rgb(97, 97, 97)
+ Grey800 = color.RGBA{0x42, 0x42, 0x42, 0xff} // rgb(66, 66, 66)
+ Grey900 = color.RGBA{0x21, 0x21, 0x21, 0xff} // rgb(33, 33, 33)
+ BlueGrey50 = color.RGBA{0xec, 0xef, 0xf1, 0xff} // rgb(236, 239, 241)
+ BlueGrey100 = color.RGBA{0xcf, 0xd8, 0xdc, 0xff} // rgb(207, 216, 220)
+ BlueGrey200 = color.RGBA{0xb0, 0xbe, 0xc5, 0xff} // rgb(176, 190, 197)
+ BlueGrey300 = color.RGBA{0x90, 0xa4, 0xae, 0xff} // rgb(144, 164, 174)
+ BlueGrey400 = color.RGBA{0x78, 0x90, 0x9c, 0xff} // rgb(120, 144, 156)
+ BlueGrey500 = color.RGBA{0x60, 0x7d, 0x8b, 0xff} // rgb(96, 125, 139)
+ BlueGrey600 = color.RGBA{0x54, 0x6e, 0x7a, 0xff} // rgb(84, 110, 122)
+ BlueGrey700 = color.RGBA{0x45, 0x5a, 0x64, 0xff} // rgb(69, 90, 100)
+ BlueGrey800 = color.RGBA{0x37, 0x47, 0x4f, 0xff} // rgb(55, 71, 79)
+ BlueGrey900 = color.RGBA{0x26, 0x32, 0x38, 0xff} // rgb(38, 50, 56)
+ Black = color.RGBA{0x00, 0x00, 0x00, 0xff} // rgb(0, 0, 0)
+ White = color.RGBA{0xff, 0xff, 0xff, 0xff} // rgb(255, 255, 255)
+)
diff --git a/shiny/materialdesign/icons/LICENSE b/shiny/materialdesign/icons/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/shiny/materialdesign/icons/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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. \ No newline at end of file
diff --git a/shiny/materialdesign/icons/data.go b/shiny/materialdesign/icons/data.go
new file mode 100644
index 0000000..a685c4d
--- /dev/null
+++ b/shiny/materialdesign/icons/data.go
@@ -0,0 +1,10969 @@
+// generated by go run gen.go; DO NOT EDIT
+
+package icons
+
+var Action3DRotation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x09, 0x77, 0xf9, 0x92,
+ 0xa0, 0x81, 0x70, 0xe1, 0x8f, 0xd1, 0x6b, 0x85, 0x89, 0x19, 0x6b, 0x84, 0xe7, 0x7a, 0xa0, 0x21,
+ 0x69, 0x51, 0x8e, 0x6d, 0x73, 0xb0, 0x80, 0xb0, 0xb0, 0x75, 0x80, 0x80, 0xe1, 0x80, 0xf5, 0x7f,
+ 0x51, 0x81, 0xf1, 0x7f, 0x00, 0xb5, 0x79, 0x4d, 0x90, 0x20, 0x55, 0x7d, 0xad, 0x82, 0xe3, 0xc9,
+ 0x81, 0xf5, 0x72, 0xb3, 0xa1, 0x7f, 0x80, 0x45, 0x7f, 0xf5, 0x7f, 0xf5, 0x7e, 0xd5, 0x7f, 0xb1,
+ 0x7f, 0xe5, 0x7f, 0x6d, 0x7f, 0xbd, 0x7f, 0x35, 0x7f, 0x89, 0x7f, 0xc9, 0x7f, 0xcd, 0x7f, 0x9d,
+ 0x7f, 0x8d, 0x7f, 0x7d, 0x7f, 0x45, 0x7f, 0xe1, 0x7f, 0xb5, 0x7f, 0xd1, 0x7f, 0x69, 0x7f, 0xd1,
+ 0x7f, 0x11, 0x7f, 0xe7, 0x69, 0x7d, 0xb5, 0x80, 0xb9, 0x80, 0x25, 0x80, 0x59, 0x81, 0x6d, 0x80,
+ 0xe9, 0x81, 0x49, 0x80, 0x8d, 0x80, 0xa9, 0x80, 0x05, 0x81, 0x21, 0x81, 0x61, 0x81, 0x79, 0x80,
+ 0x61, 0x80, 0x05, 0x81, 0xa5, 0x80, 0xa1, 0x81, 0xd5, 0x80, 0xa1, 0x80, 0x35, 0x80, 0x45, 0x81,
+ 0x4d, 0x80, 0xf1, 0x81, 0x4d, 0x80, 0xbd, 0x80, 0x80, 0x71, 0x81, 0xe9, 0x7f, 0x11, 0x82, 0xb5,
+ 0x7f, 0xa5, 0x80, 0xcd, 0x7f, 0x31, 0x81, 0x81, 0x7f, 0xa9, 0x81, 0x1d, 0x7f, 0x90, 0xd5, 0x80,
+ 0x21, 0x7f, 0x19, 0x81, 0x91, 0x7e, 0xbf, 0x45, 0x80, 0x71, 0x7f, 0x69, 0x80, 0xc9, 0x7e, 0x69,
+ 0x80, 0x0d, 0x7e, 0x80, 0x9d, 0x7f, 0xf5, 0x7f, 0x3d, 0x7f, 0xdd, 0x7f, 0xe1, 0x7e, 0xe9, 0x7f,
+ 0xa5, 0x7f, 0xc1, 0x7f, 0x4d, 0x7f, 0x8d, 0x7f, 0xfd, 0x7e, 0xcd, 0x7f, 0xb1, 0x7f, 0x85, 0x7f,
+ 0x69, 0x7f, 0x31, 0x7f, 0x25, 0x7f, 0xad, 0x7f, 0xc1, 0x7f, 0x45, 0x7f, 0x8d, 0x7f, 0xcd, 0x7e,
+ 0x61, 0x7f, 0x69, 0x80, 0xd1, 0x7f, 0xc1, 0x80, 0x99, 0x7f, 0x0d, 0x81, 0x59, 0x7f, 0x4d, 0x80,
+ 0xbd, 0x7f, 0x8d, 0x80, 0x75, 0x7f, 0xc1, 0x80, 0x2d, 0x7f, 0x35, 0x80, 0xb5, 0x7f, 0x59, 0x80,
+ 0x69, 0x7f, 0x75, 0x80, 0x15, 0x7f, 0x19, 0x80, 0xb1, 0x7f, 0x29, 0x80, 0x5d, 0x7f, 0x29, 0x80,
+ 0x0d, 0x7f, 0x80, 0x45, 0x7f, 0xe1, 0x7f, 0xa1, 0x7e, 0xa5, 0x7f, 0x15, 0x7e, 0xc5, 0x7f, 0x75,
+ 0x7f, 0x6d, 0x7f, 0xfd, 0x7e, 0xfd, 0x7e, 0xa1, 0x7e, 0x91, 0x7f, 0xa1, 0x7f, 0x0d, 0x7f, 0x59,
+ 0x7f, 0x6d, 0x7e, 0x29, 0x7f, 0x5d, 0x7f, 0xcd, 0x7f, 0xb1, 0x7e, 0xb5, 0x7f, 0xf1, 0x7d, 0xb5,
+ 0x7f, 0x49, 0x7f, 0x80, 0x9d, 0x7e, 0x1d, 0x80, 0x7c, 0x51, 0x80, 0x65, 0x7f, 0x35, 0x80, 0xe1,
+ 0x7e, 0x85, 0x80, 0x71, 0x7e, 0xe5, 0x80, 0x91, 0x7f, 0x61, 0x80, 0x39, 0x7f, 0xd5, 0x80, 0xf9,
+ 0x7e, 0x59, 0x81, 0xb0, 0xc1, 0x7f, 0x85, 0x80, 0xa1, 0x7f, 0x19, 0x81, 0xa1, 0x7f, 0xb5, 0x81,
+ 0xe7, 0x99, 0x82, 0xb9, 0x80, 0xa9, 0x7f, 0x11, 0x80, 0x5d, 0x7f, 0x31, 0x80, 0x19, 0x7f, 0x21,
+ 0x80, 0xbd, 0x7f, 0x4d, 0x80, 0x81, 0x7f, 0x81, 0x80, 0x51, 0x7f, 0x35, 0x80, 0xd1, 0x7f, 0x79,
+ 0x80, 0xa9, 0x7f, 0xc5, 0x80, 0x91, 0x7f, 0x4d, 0x80, 0xe9, 0x7f, 0x9d, 0x80, 0xd9, 0x7f, 0xf5,
+ 0x80, 0xd9, 0x7f, 0xcd, 0x80, 0x80, 0x65, 0x81, 0x35, 0x80, 0xc9, 0x81, 0xa1, 0x80, 0x65, 0x80,
+ 0x69, 0x80, 0x95, 0x80, 0xfd, 0x80, 0x95, 0x80, 0xbd, 0x81, 0x80, 0x5d, 0x80, 0xf5, 0x7f, 0xb1,
+ 0x80, 0xd9, 0x7f, 0xf9, 0x80, 0xe5, 0x7f, 0x4d, 0x80, 0xbd, 0x7f, 0x8d, 0x80, 0x85, 0x7f, 0xc1,
+ 0x80, 0xc9, 0x7f, 0x35, 0x80, 0x81, 0x7f, 0x61, 0x80, 0x31, 0x7f, 0x7d, 0x80, 0xad, 0x7f, 0x21,
+ 0x80, 0x49, 0x7f, 0x31, 0x80, 0xd9, 0x7e, 0x31, 0x80, 0xe7, 0x75, 0x7e, 0xe9, 0x0d, 0x82, 0xe7,
+ 0x8d, 0x81, 0xb5, 0x71, 0x80, 0x80, 0xd9, 0x80, 0x0d, 0x80, 0x31, 0x81, 0x29, 0x80, 0x59, 0x80,
+ 0x19, 0x80, 0xa9, 0x80, 0x41, 0x80, 0xe9, 0x80, 0x79, 0x80, 0x41, 0x80, 0x35, 0x80, 0x71, 0x80,
+ 0x7d, 0x80, 0x95, 0x80, 0xcd, 0x80, 0x21, 0x80, 0x51, 0x80, 0x35, 0x80, 0xb5, 0x80, 0x35, 0x80,
+ 0x25, 0x81, 0x80, 0xd1, 0x80, 0xc5, 0x7f, 0x71, 0x81, 0x4d, 0x7f, 0xdd, 0x81, 0x8d, 0x7f, 0x6d,
+ 0x80, 0xed, 0x7e, 0xa1, 0x80, 0x1d, 0x7e, 0xa1, 0x80, 0xe3, 0x21, 0x91, 0x29, 0x74, 0xb1, 0x61,
+ 0x7f, 0x59, 0x7f, 0x9d, 0x7e, 0xd5, 0x7e, 0xbd, 0x7d, 0x79, 0x7e, 0x1d, 0x7f, 0xa5, 0x7f, 0x25,
+ 0x7e, 0x75, 0x7f, 0x11, 0x7d, 0x75, 0x7f, 0xe6, 0x80, 0xe9, 0xa0, 0xe7, 0x99, 0x84, 0xb3, 0x1d,
+ 0x81, 0x80, 0x1d, 0x82, 0xd1, 0x7f, 0x05, 0x83, 0x75, 0x7f, 0xe9, 0x80, 0xa5, 0x7f, 0xb1, 0x81,
+ 0x21, 0x7f, 0x51, 0x82, 0x79, 0x7e, 0xa5, 0x80, 0x59, 0x7f, 0x25, 0x81, 0x8d, 0x7e, 0x7d, 0x81,
+ 0x9d, 0x7d, 0x59, 0x80, 0x11, 0x7f, 0x85, 0x80, 0x05, 0x7e, 0x85, 0x80, 0xdd, 0x7c, 0xe9, 0x35,
+ 0x7f, 0xb1, 0x80, 0xd9, 0x7e, 0xd1, 0x7f, 0xcd, 0x7d, 0x79, 0x7f, 0xdd, 0x7c, 0xa9, 0x7f, 0x11,
+ 0x7f, 0x29, 0x7f, 0x45, 0x7e, 0x89, 0x7e, 0x99, 0x7d, 0xe3, 0x35, 0x7f, 0x59, 0x86, 0xb3, 0x80,
+ 0xd5, 0x80, 0xe9, 0x7f, 0x99, 0x81, 0xb5, 0x7f, 0x41, 0x82, 0xd1, 0x7f, 0xad, 0x80, 0x89, 0x7f,
+ 0x3d, 0x81, 0x29, 0x7f, 0xb1, 0x81, 0xa1, 0x7f, 0x75, 0x80, 0x29, 0x7f, 0xd1, 0x80, 0x95, 0x7e,
+ 0x11, 0x81, 0x71, 0x7f, 0x3d, 0x80, 0xc5, 0x7e, 0x61, 0x80, 0x05, 0x7e, 0x61, 0x80, 0xe7, 0x31,
+ 0x7e, 0xe8, 0x3d, 0x7a, 0xe7, 0xf5, 0x81, 0xb1, 0x71, 0x81, 0x80, 0x89, 0x82, 0x75, 0x80, 0x4d,
+ 0x83, 0x61, 0x81, 0xc1, 0x80, 0xed, 0x80, 0x21, 0x81, 0x3d, 0x82, 0x21, 0x81, 0xfd, 0x83, 0xe9,
+ 0xd1, 0x80, 0xe2, 0x80, 0x50, 0xb0, 0x8d, 0x7f, 0x80, 0x21, 0x7f, 0x0d, 0x80, 0xb1, 0x7e, 0x11,
+ 0x80, 0x00, 0x4d, 0x86, 0xb5, 0x6f, 0x20, 0xa9, 0x82, 0x59, 0x7d, 0xa0, 0x81, 0x8f, 0x21, 0x70,
+ 0x31, 0x94, 0x7d, 0x76, 0xe9, 0x94, 0x7c, 0xe7, 0x86, 0xa0, 0xe1, 0x96, 0xb1, 0x71, 0x95, 0x8c,
+ 0x50, 0x80, 0x50, 0xe1,
+}
+
+var ActionAccessibility = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x92, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe3, 0xa4, 0x9c, 0xe6, 0x8c, 0xe9, 0xb4, 0xe7, 0x78,
+ 0xe8, 0x90, 0xe7, 0x78, 0xe9, 0x98, 0xe7, 0x78, 0xe8, 0x74, 0xe6, 0x5c, 0xe9, 0x78, 0xe7, 0xc8,
+ 0xe9, 0x88, 0xe1,
+}
+
+var ActionAccessible = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x84, 0xe9, 0x78,
+ 0xb0, 0xf1, 0x7c, 0x0d, 0x80, 0xd1, 0x79, 0x81, 0x7e, 0xdd, 0x77, 0x55, 0x7c, 0x20, 0x69, 0x7d,
+ 0x25, 0x7d, 0xb1, 0xa9, 0x7f, 0xa1, 0x7f, 0x3d, 0x7f, 0x51, 0x7f, 0xc9, 0x7e, 0x19, 0x7f, 0xfd,
+ 0x7f, 0xfd, 0x7f, 0xf9, 0x7f, 0xfd, 0x7f, 0xf5, 0x7f, 0xf9, 0x7f, 0xe7, 0xfd, 0x7f, 0xb1, 0x51,
+ 0x7f, 0x99, 0x7f, 0x7d, 0x7e, 0x69, 0x7f, 0xa1, 0x7d, 0x7d, 0x7f, 0xed, 0x7d, 0x35, 0x80, 0x65,
+ 0x7c, 0x11, 0x82, 0x65, 0x7c, 0x29, 0x84, 0xe8, 0x8c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x92, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe7, 0x74, 0xe9, 0x19, 0x79, 0xb0, 0x95, 0x82, 0x21, 0x82, 0x7d, 0x86, 0xe5,
+ 0x83, 0x94, 0xe9, 0x83, 0xe2, 0xa9, 0x81, 0x98, 0xb2, 0x31, 0x7f, 0x55, 0x82, 0xf5, 0x7c, 0x88,
+ 0x59, 0x7a, 0x88, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x80, 0x65, 0x7d, 0xad, 0x81,
+ 0x2d, 0x7b, 0x88, 0x59, 0x7a, 0xe8, 0x35, 0x80, 0xb2, 0x71, 0x7b, 0xf1, 0x80, 0x70, 0xf5, 0x84,
+ 0x70, 0xcd, 0x89, 0x80, 0x85, 0x85, 0x7d, 0x84, 0x94, 0x94, 0x94, 0xd9, 0x84, 0x80, 0xe1, 0x88,
+ 0x91, 0x7c, 0xcd, 0x89, 0x70, 0xe7, 0xd9, 0x7b, 0xe2, 0x78, 0x60, 0xd1, 0x88, 0x88, 0x00, 0x04,
+ 0x90, 0x80, 0x88, 0x88, 0x00, 0x04, 0x70, 0x80, 0xe1,
+}
+
+var ActionAccountBalance = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x78, 0xe9, 0x9c,
+ 0xe7, 0x8c, 0xe8, 0x78, 0xe6, 0x60, 0xe3, 0x98, 0x80, 0xe9, 0x9c, 0xe7, 0x8c, 0xe8, 0x78, 0xe7,
+ 0x74, 0xe2, 0x58, 0xa8, 0xe7, 0xcc, 0xe9, 0x74, 0xe6, 0x58, 0xe9, 0x8c, 0xe3, 0xb8, 0x50, 0xe9,
+ 0x9c, 0xe7, 0x8c, 0xe8, 0x78, 0xe7, 0x74, 0xe2, 0x7e, 0x54, 0x00, 0x58, 0x68, 0xe9, 0x88, 0xe7,
+ 0xcc, 0xe9, 0x78, 0x00, 0x7e, 0x54, 0xe1,
+}
+
+var ActionAccountBalanceWallet = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x98, 0xe9, 0x84,
+ 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe7,
+ 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x84, 0xe6, 0x80, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0xa4, 0xe3, 0x5c, 0x78, 0xe7, 0xa8, 0xe8, 0x70, 0xe6, 0x80, 0xe9, 0xa0, 0xe3,
+ 0x90, 0x76, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var ActionAccountBox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe9, 0xb8,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6,
+ 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe3, 0xb0, 0x90, 0xb0, 0x80, 0x51,
+ 0x83, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0x90, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xb0, 0x80, 0xb1, 0x7c,
+ 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x90, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0xe2, 0x68, 0x94, 0xb0, 0x80,
+ 0x78, 0x90, 0xcd, 0x79, 0x98, 0xcd, 0x79, 0x80, 0x98, 0x8c, 0x98, 0x94, 0xe9, 0x84, 0xe6, 0x68,
+ 0xe9, 0x7c, 0xe1,
+}
+
+var ActionAccountCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0x8c, 0xb1, 0x51, 0x83, 0x80, 0x8c,
+ 0xb1, 0x82, 0x8c, 0x8c, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0x90, 0x74, 0x51, 0x7d,
+ 0x74, 0x74, 0xb0, 0x80, 0xb1, 0x7c, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0xe3, 0x80, 0x69, 0x9c, 0xb1,
+ 0xfd, 0x7a, 0x80, 0x99, 0x76, 0x71, 0x7d, 0x68, 0x91, 0x79, 0x0d, 0x80, 0x09, 0x7c, 0x05, 0x88,
+ 0xd9, 0x79, 0x98, 0xd9, 0x79, 0x90, 0xf1, 0x8b, 0x31, 0x82, 0x98, 0x29, 0x86, 0xb0, 0x69, 0x7d,
+ 0xe1, 0x83, 0x05, 0x79, 0x71, 0x86, 0x68, 0x71, 0x86, 0xe1,
+}
+
+var ActionAddShoppingCart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x74, 0xe7, 0x88,
+ 0xe9, 0x74, 0xe7, 0x8c, 0xe8, 0x60, 0xe7, 0x74, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x8c, 0xe7, 0x74,
+ 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x8c, 0xe3, 0x70, 0xa4, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd,
+ 0x81, 0x05, 0x7c, 0x88, 0x92, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0xa8, 0x80, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81,
+ 0x05, 0x7c, 0x88, 0x92, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x59, 0x6c, 0x81, 0x79, 0xb0, 0x80, 0xe9, 0x7f, 0x05, 0x80, 0xd5,
+ 0x7f, 0x11, 0x80, 0xc5, 0x7f, 0x20, 0xcd, 0x81, 0xbd, 0x7c, 0xe7, 0xe9, 0x8e, 0xb0, 0x81, 0x81,
+ 0x80, 0xd1, 0x82, 0x2d, 0x7f, 0x81, 0x83, 0xf1, 0x7d, 0x20, 0xb9, 0x87, 0xfd, 0x71, 0x00, 0xd5,
+ 0x8e, 0x60, 0xe7, 0xfd, 0x7f, 0x21, 0xcd, 0x7d, 0x88, 0x7d, 0x7a, 0x94, 0xe6, 0x11, 0x79, 0x20,
+ 0xbd, 0x7f, 0x75, 0x7f, 0x00, 0x51, 0x74, 0x68, 0x21, 0x19, 0x7e, 0x78, 0x1d, 0x7e, 0x78, 0xe6,
+ 0x54, 0xe9, 0x88, 0xe7, 0x88, 0x21, 0x35, 0x87, 0x2d, 0x8f, 0x4d, 0x7d, 0xe9, 0x84, 0xb1, 0xb1,
+ 0x7f, 0x95, 0x80, 0x85, 0x7f, 0x3d, 0x81, 0x85, 0x7f, 0xf1, 0x81, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0xd9, 0x76, 0xb0, 0xb9, 0x7f, 0x80, 0x81, 0x7f,
+ 0xc9, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0xe1,
+}
+
+var ActionAlarm = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x71, 0x73, 0x22,
+ 0xd1, 0x76, 0x4d, 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31, 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73,
+ 0xe2, 0xc5, 0x77, 0xc9, 0x6e, 0x20, 0x71, 0x7d, 0xf1, 0x7c, 0x00, 0x58, 0x71, 0x73, 0x21, 0x91,
+ 0x82, 0x11, 0x83, 0x31, 0x89, 0x4d, 0x78, 0xe2, 0x82, 0x70, 0xe7, 0x7a, 0xe9, 0x98, 0x20, 0x7d,
+ 0x89, 0xb5, 0x85, 0x00, 0x92, 0x3d, 0x87, 0x20, 0x70, 0x45, 0x7b, 0xe8, 0x70, 0xe3, 0xfd, 0x7e,
+ 0x70, 0xa0, 0x0d, 0x76, 0x60, 0x5c, 0x11, 0x78, 0x5c, 0x84, 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91,
+ 0xa4, 0x81, 0xa4, 0xf1, 0x8b, 0xa4, 0x84, 0xf1, 0x89, 0x60, 0xfd, 0x7f, 0x60, 0xe2, 0x80, 0xa0,
+ 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c,
+ 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var ActionAlarmAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xc5, 0x77, 0xc9, 0x6e,
+ 0x20, 0x71, 0x7d, 0xf1, 0x7c, 0x00, 0x58, 0x71, 0x73, 0x21, 0x91, 0x82, 0x11, 0x83, 0x31, 0x89,
+ 0x4d, 0x78, 0xe2, 0xa8, 0x71, 0x73, 0x22, 0xd1, 0x76, 0x4d, 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31,
+ 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73, 0xe2, 0xfd, 0x7f, 0x60, 0xa0, 0x0d, 0x76, 0x60, 0x5c,
+ 0x11, 0x78, 0x5c, 0x84, 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0x81, 0xa4, 0xf1, 0x8b, 0xa4,
+ 0x84, 0xf1, 0x89, 0x60, 0xfd, 0x7f, 0x60, 0xe2, 0x80, 0xa0, 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd,
+ 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79,
+ 0x9c, 0x64, 0x9c, 0xe3, 0x84, 0x54, 0xe7, 0x78, 0xe9, 0x8c, 0xe7, 0x74, 0xe9, 0x88, 0xe7, 0x8c,
+ 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0x8c, 0xe9, 0x78, 0xe7, 0x74, 0xe9, 0x74, 0xe1,
+}
+
+var ActionAlarmOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x68, 0xb1, 0xbd,
+ 0x87, 0x80, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0x80, 0xb1, 0x81, 0xb1, 0x7f, 0x4d, 0x83, 0x25, 0x7f,
+ 0xcd, 0x84, 0x20, 0x0d, 0x83, 0x0d, 0x83, 0xa0, 0x59, 0x91, 0x79, 0x87, 0xa4, 0xd1, 0x84, 0xa4,
+ 0x84, 0xb1, 0x80, 0x11, 0x76, 0xf1, 0x77, 0x5c, 0xfd, 0x6d, 0x5c, 0x31, 0x7d, 0x80, 0x8d, 0x7a,
+ 0xa9, 0x80, 0x29, 0x78, 0xd1, 0x81, 0x20, 0x0d, 0x83, 0x0d, 0x83, 0xb0, 0x81, 0x81, 0x75, 0x7f,
+ 0x1d, 0x83, 0x25, 0x7f, 0xcd, 0x84, 0x25, 0x7f, 0xe3, 0xa8, 0x71, 0x7f, 0x22, 0xd1, 0x76, 0x4d,
+ 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31, 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73, 0xe2, 0xd9, 0x6d,
+ 0x99, 0x6c, 0x00, 0x4d, 0x6b, 0x21, 0x6f, 0x24, 0xa9, 0x82, 0xa9, 0x82, 0xc9, 0x7d, 0xdd, 0x81,
+ 0xd9, 0x82, 0xd9, 0x82, 0x39, 0x82, 0x25, 0x7e, 0x99, 0x81, 0x99, 0x81, 0xa0, 0xa9, 0x6f, 0x65,
+ 0x79, 0x5c, 0x81, 0x7d, 0x5c, 0x84, 0xb1, 0x80, 0xf1, 0x89, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4,
+ 0x85, 0x84, 0x80, 0xa1, 0x88, 0x55, 0x7e, 0xc5, 0x8b, 0x99, 0x7b, 0x21, 0x69, 0x84, 0x69, 0x84,
+ 0x8d, 0x82, 0x75, 0x7d, 0x01, 0xcd, 0x6f, 0x8d, 0x6e, 0xd9, 0x6d, 0x99, 0x6c, 0xe3, 0x19, 0x9b,
+ 0x31, 0xa0, 0xa0, 0x85, 0x86, 0xcd, 0x8e, 0x69, 0x83, 0xa0, 0x80, 0xa0, 0xb1, 0x45, 0x78, 0x80,
+ 0x64, 0xbd, 0x79, 0x64, 0x64, 0x80, 0x99, 0x7c, 0x35, 0x81, 0x7d, 0x79, 0x39, 0x83, 0x11, 0x77,
+ 0x20, 0xb9, 0x93, 0xb9, 0x93, 0xe2, 0x09, 0x78, 0x8d, 0x6e, 0x23, 0x29, 0x7d, 0x29, 0x7d, 0x4d,
+ 0x7e, 0x71, 0x81, 0xd9, 0x82, 0xd9, 0x82, 0xb5, 0x81, 0x91, 0x7e, 0xe1,
+}
+
+var ActionAlarmOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x71, 0x73, 0x22,
+ 0xd1, 0x76, 0x4d, 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31, 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73,
+ 0xe2, 0xc5, 0x77, 0xc9, 0x6e, 0x20, 0x71, 0x7d, 0xf1, 0x7c, 0x00, 0x58, 0x71, 0x73, 0x21, 0x91,
+ 0x82, 0x11, 0x83, 0x31, 0x89, 0x4d, 0x78, 0xe2, 0xfd, 0x7f, 0x60, 0xa0, 0x0d, 0x76, 0x60, 0x5c,
+ 0x11, 0x78, 0x5c, 0x84, 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0x81, 0xa4, 0xf1, 0x8b, 0xa4,
+ 0x84, 0xf1, 0x89, 0x60, 0xfd, 0x7f, 0x60, 0xe2, 0x80, 0xa0, 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd,
+ 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79,
+ 0x9c, 0x64, 0x9c, 0xe3, 0x11, 0x7d, 0x0d, 0x75, 0x25, 0xc5, 0x7b, 0xc5, 0x7b, 0xe1, 0x7d, 0x21,
+ 0x82, 0x5d, 0x86, 0x5d, 0x86, 0x05, 0x8c, 0xfd, 0x73, 0xe1, 0x7d, 0xe1, 0x7d, 0x1d, 0x76, 0xe5,
+ 0x89, 0xe1,
+}
+
+var ActionAllOut = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x60, 0x20, 0x90,
+ 0x90, 0xe8, 0x60, 0xe3, 0x90, 0xb0, 0x20, 0x70, 0x90, 0xe7, 0x90, 0xe3, 0x50, 0x90, 0x20, 0x70,
+ 0x70, 0xe9, 0x90, 0xe2, 0x60, 0x70, 0x20, 0x90, 0x70, 0xe6, 0x60, 0xe3, 0xe9, 0x99, 0x19, 0x7e,
+ 0xb0, 0x89, 0x7a, 0x89, 0x7a, 0xad, 0x71, 0x89, 0x7a, 0x35, 0x6c, 0x80, 0x92, 0x89, 0x7a, 0x55,
+ 0x8e, 0x80, 0xcd, 0x93, 0x55, 0x8e, 0x79, 0x85, 0xcd, 0x93, 0x80, 0x79, 0x85, 0xad, 0x71, 0x80,
+ 0x35, 0x6c, 0xe3, 0xcd, 0x7d, 0x99, 0x91, 0xb0, 0xc1, 0x7b, 0x41, 0x84, 0xd9, 0x74, 0x41, 0x84,
+ 0x99, 0x70, 0x80, 0x92, 0xc1, 0x7b, 0xd9, 0x74, 0x80, 0x99, 0x70, 0x29, 0x8b, 0xc1, 0x7b, 0x69,
+ 0x8f, 0x80, 0x41, 0x84, 0x29, 0x8b, 0x80, 0x69, 0x8f, 0xe1,
+}
+
+var ActionAndroid = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x98, 0xb0, 0x80,
+ 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xe9, 0x8e, 0xb0, 0x80, 0xa9, 0x81, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0x90, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe9, 0x72, 0xe7, 0x88, 0xe9, 0x8e,
+ 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86, 0x86, 0x90, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe9,
+ 0x72, 0xe7, 0x84, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x70, 0xe6, 0x68,
+ 0xe9, 0xa8, 0xe2, 0x5e, 0x70, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x9c,
+ 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86, 0x86, 0x90, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe8,
+ 0x76, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0xc4, 0x80, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x9c, 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86,
+ 0x86, 0x90, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe8, 0x76, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a,
+ 0x7a, 0x7a, 0xe2, 0x11, 0x87, 0x51, 0x6c, 0x20, 0x9d, 0x82, 0x65, 0x7d, 0xb1, 0x65, 0x80, 0x9d,
+ 0x7f, 0x65, 0x80, 0xfd, 0x7e, 0x80, 0x99, 0x7e, 0x9d, 0x7f, 0x9d, 0x7f, 0xfd, 0x7e, 0x9d, 0x7f,
+ 0x99, 0x7e, 0x80, 0x00, 0x4d, 0x85, 0x41, 0x6b, 0xa0, 0xb5, 0x83, 0x75, 0x6a, 0xe9, 0x81, 0x54,
+ 0x80, 0x54, 0xb0, 0x15, 0x7e, 0x80, 0x49, 0x7c, 0x75, 0x80, 0xad, 0x7a, 0x45, 0x81, 0x00, 0xb5,
+ 0x77, 0x4d, 0x68, 0xb1, 0x9d, 0x7f, 0x9d, 0x7f, 0xfd, 0x7e, 0x9d, 0x7f, 0x99, 0x7e, 0x80, 0x9d,
+ 0x7f, 0x65, 0x80, 0x9d, 0x7f, 0x05, 0x81, 0x80, 0x69, 0x81, 0x20, 0xa1, 0x82, 0xa1, 0x82, 0xa0,
+ 0xf1, 0x75, 0x85, 0x6e, 0x68, 0x09, 0x72, 0x68, 0x6c, 0xe7, 0xb0, 0xb0, 0x80, 0x05, 0x7c, 0x0d,
+ 0x7e, 0x81, 0x78, 0x11, 0x7b, 0x51, 0x76, 0xe2, 0x78, 0x64, 0xe7, 0x7c, 0xe8, 0x60, 0xe7, 0x84,
+ 0xe9, 0x84, 0xe3, 0x94, 0x80, 0xe7, 0x7c, 0xe8, 0x60, 0xe7, 0x84, 0xe9, 0x84, 0xe1,
+}
+
+var ActionAnnouncement = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x84, 0x7c, 0xe7, 0x78, 0xe8, 0x64, 0xe7, 0x88,
+ 0xe9, 0x98, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var ActionAspectRatio = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x80, 0xe7, 0x78,
+ 0xe9, 0x8c, 0xe7, 0x74, 0xe9, 0x88, 0xe7, 0x94, 0xe8, 0x80, 0xe3, 0x50, 0x74, 0xe7, 0x8c, 0xe9,
+ 0x78, 0xe6, 0x64, 0xe9, 0x94, 0xe7, 0x88, 0xe9, 0x74, 0xe2, 0xa4, 0x5c, 0xe6, 0x5c, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x09, 0xa0, 0xe6, 0x5c, 0xe8, 0xf9,
+ 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe1,
+}
+
+var ActionAssessment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x94, 0xe7, 0x78, 0xe8,
+ 0x78, 0xe7, 0x88, 0xe9, 0x9c, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0xa8,
+ 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var ActionAssignment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x80, 0x5c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x88, 0xb8, 0xe6,
+ 0x6c, 0xe9, 0x78, 0xe7, 0x9c, 0xe9, 0x88, 0xe3, 0x8c, 0x70, 0xe6, 0x6c, 0xe9, 0x78, 0xe7, 0xa8,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x6c, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var ActionAssignmentInd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x80, 0x5c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x80, 0x90, 0xb1,
+ 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74, 0x8c,
+ 0x90, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xb0, 0x80, 0xb1, 0x7c, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0xe3,
+ 0x98, 0xb0, 0xe6, 0x68, 0xe9, 0x35, 0x7d, 0xb0, 0x80, 0x78, 0x90, 0xcd, 0x79, 0x98, 0xcd, 0x79,
+ 0x90, 0x98, 0x35, 0x82, 0x98, 0x35, 0x86, 0xe8, 0x9c, 0xe1,
+}
+
+var ActionAssignmentLate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x84, 0x98, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78,
+ 0xe8, 0x70, 0xe7, 0x88, 0xe9, 0x98, 0xe3, 0x7c, 0x5c, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x92, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0xe1,
+}
+
+var ActionAssignmentReturn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x80, 0x5c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x90, 0xb0, 0xe7,
+ 0x70, 0xe9, 0x8c, 0x00, 0x6c, 0x84, 0x20, 0x94, 0x6c, 0xe9, 0x8c, 0xe7, 0x90, 0xe9, 0x90, 0xe1,
+}
+
+var ActionAssignmentReturned = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x80, 0x5c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x80, 0xbc, 0x00,
+ 0x6c, 0x84, 0xe7, 0x8c, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x8c, 0x00, 0x80, 0x98, 0xe1,
+}
+
+var ActionAssignmentTurnedIn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x80, 0x5c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x78, 0xb8, 0x21,
+ 0x70, 0x70, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x78, 0x59, 0x84, 0x20, 0x2d, 0x8d, 0xd5, 0x72, 0x01,
+ 0x98, 0x74, 0x78, 0x94, 0xe1,
+}
+
+var ActionAutorenew = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x68, 0xe9, 0x8c,
+ 0x21, 0x90, 0x70, 0x70, 0x70, 0xe9, 0x8c, 0xa0, 0x29, 0x77, 0x60, 0x60, 0x29, 0x77, 0x60, 0x80,
+ 0xb0, 0x80, 0x25, 0x83, 0xed, 0x80, 0x0d, 0x86, 0x7d, 0x82, 0x85, 0x88, 0x20, 0xed, 0x82, 0x15,
+ 0x7d, 0xb1, 0x1d, 0x7f, 0x55, 0x7e, 0x99, 0x7e, 0x71, 0x7c, 0x99, 0x7e, 0x69, 0x7a, 0x80, 0x61,
+ 0x79, 0x61, 0x85, 0x68, 0x98, 0x68, 0xe3, 0x85, 0x8d, 0x7d, 0x83, 0x00, 0x99, 0x8a, 0x69, 0x7a,
+ 0xb1, 0xe5, 0x80, 0xad, 0x81, 0x69, 0x81, 0x91, 0x83, 0x69, 0x81, 0x99, 0x85, 0x80, 0xa1, 0x86,
+ 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xe9, 0x74, 0x21, 0x70, 0x90, 0x90, 0x90, 0xe9, 0x74, 0xb1, 0xd9,
+ 0x88, 0x80, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0x80, 0xdd, 0x7c, 0x15, 0x7f, 0xf5, 0x79, 0x85, 0x7d,
+ 0x7d, 0x77, 0xe1,
+}
+
+var ActionBackup = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe2,
+ 0x88, 0x84, 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0x74, 0x21, 0x94, 0x6c, 0x94, 0x94, 0xe7,
+ 0x74, 0xe1,
+}
+
+var ActionBook = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x58, 0xe6, 0x68,
+ 0xa0, 0xcd, 0x71, 0x58, 0x60, 0xcd, 0x6d, 0x60, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0x60, 0xe7, 0x94, 0xe9,
+ 0xa0, 0x21, 0x76, 0x7a, 0x76, 0x86, 0xe8, 0x60, 0xe1,
+}
+
+var ActionBookmark = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x64, 0xa4, 0x21, 0x9c,
+ 0x74, 0x9c, 0x8c, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ActionBookmarkBorder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x64, 0xa4, 0x21, 0x9c,
+ 0x74, 0x9c, 0x8c, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80,
+ 0xbc, 0x20, 0x6c, 0xa9, 0x7b, 0x00, 0x6c, 0x98, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb4, 0xe1,
+}
+
+var ActionBugReport = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x70, 0xe7, 0x61,
+ 0x7a, 0xb0, 0x19, 0x7f, 0x71, 0x7e, 0xdd, 0x7d, 0x19, 0x7d, 0x61, 0x7c, 0x15, 0x7c, 0x01, 0x94,
+ 0xd5, 0x70, 0x2d, 0x87, 0x5c, 0x20, 0xa9, 0x7b, 0x59, 0x84, 0xb1, 0x19, 0x7f, 0xc9, 0x7f, 0x29,
+ 0x7e, 0xa9, 0x7f, 0x31, 0x7d, 0xa9, 0x7f, 0x09, 0x7f, 0x80, 0x15, 0x7e, 0x21, 0x80, 0x31, 0x7d,
+ 0x59, 0x80, 0x01, 0xd5, 0x78, 0x5c, 0x6c, 0xd5, 0x70, 0x20, 0x41, 0x83, 0x41, 0x83, 0xb0, 0x85,
+ 0x7e, 0x05, 0x81, 0x45, 0x7d, 0x5d, 0x82, 0x61, 0x7c, 0xed, 0x83, 0xe6, 0x60, 0xe9, 0x88, 0xe7,
+ 0x31, 0x84, 0xb0, 0xe5, 0x7f, 0xa9, 0x80, 0xd1, 0x7f, 0x51, 0x81, 0xd1, 0x7f, 0x84, 0xe9, 0x84,
+ 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x84, 0xb0, 0x80, 0xb1, 0x80, 0x11, 0x80, 0x59, 0x81,
+ 0x31, 0x80, 0x84, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0xa1, 0x85, 0xb0, 0x11, 0x82, 0x95, 0x83, 0xf1,
+ 0x85, 0x8c, 0x61, 0x8a, 0x8c, 0x90, 0x51, 0x88, 0x95, 0x7d, 0x61, 0x8a, 0x74, 0xe6, 0xa0, 0xe9,
+ 0x78, 0xe7, 0xd1, 0x7b, 0xb0, 0x1d, 0x80, 0x59, 0x7f, 0x31, 0x80, 0xb1, 0x7e, 0x31, 0x80, 0x7c,
+ 0xe9, 0x7c, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x7c, 0xb0, 0x80, 0x51, 0x7f, 0xf1, 0x7f,
+ 0xa9, 0x7e, 0xd1, 0x7f, 0x7c, 0xe6, 0xa0, 0xe9, 0x78, 0xe2, 0x88, 0x90, 0xe7, 0x70, 0xe9, 0x78,
+ 0xe7, 0x90, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var ActionBuild = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x95, 0xe9, 0x8d,
+ 0x00, 0x19, 0x83, 0x99, 0x7b, 0xb1, 0xcd, 0x81, 0x69, 0x7b, 0xcd, 0x80, 0xe9, 0x75, 0x19, 0x7d,
+ 0x35, 0x72, 0x78, 0x78, 0x6c, 0x35, 0x7b, 0x35, 0x71, 0x81, 0x7d, 0x22, 0xb5, 0x88, 0xb5, 0x88,
+ 0xe9, 0x79, 0x19, 0x86, 0x4d, 0x77, 0x4d, 0x77, 0xa0, 0x52, 0x35, 0x76, 0xcd, 0x69, 0x35, 0x7c,
+ 0xcd, 0x6d, 0x35, 0x80, 0xb0, 0xb5, 0x83, 0xb5, 0x83, 0x35, 0x89, 0xb5, 0x84, 0xcd, 0x8d, 0xe9,
+ 0x82, 0x20, 0x4d, 0x92, 0x4d, 0x92, 0xb0, 0xcd, 0x80, 0xcd, 0x80, 0x19, 0x82, 0xcd, 0x80, 0xcd,
+ 0x82, 0x80, 0x20, 0xb5, 0x84, 0x4d, 0x7b, 0xb0, 0xcd, 0x80, 0x4d, 0x7f, 0xcd, 0x80, 0x7c, 0x80,
+ 0x35, 0x7d, 0xe1,
+}
+
+var ActionCached = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x70, 0x20, 0x70,
+ 0x90, 0xe7, 0x8c, 0xb1, 0x80, 0xa1, 0x86, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xf9, 0x7d, 0x80, 0x11,
+ 0x7c, 0x7d, 0x7f, 0x65, 0x7a, 0x9d, 0x7e, 0x20, 0x15, 0x7d, 0xed, 0x82, 0xa0, 0xf5, 0x79, 0x15,
+ 0x8f, 0xdd, 0x7c, 0xa0, 0x80, 0xa0, 0xb0, 0xd9, 0x88, 0x80, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xe7,
+ 0x8c, 0x20, 0x70, 0x70, 0xe3, 0x4c, 0x90, 0xb1, 0x80, 0x61, 0x79, 0x61, 0x85, 0x68, 0x98, 0x68,
+ 0x09, 0x82, 0x80, 0xf1, 0x83, 0x85, 0x80, 0x9d, 0x85, 0x65, 0x81, 0x20, 0xed, 0x82, 0x15, 0x7d,
+ 0xa1, 0x0d, 0x86, 0xed, 0x70, 0x25, 0x83, 0x60, 0x80, 0x60, 0x29, 0x77, 0x60, 0x60, 0x29, 0x77,
+ 0x60, 0x80, 0xe6, 0x54, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe7, 0x74, 0xe1,
+}
+
+var ActionCameraEnhance = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x5c, 0x00, 0x59,
+ 0x76, 0x64, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x5c, 0xe6, 0x74, 0xe3, 0x8c, 0xbc, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b,
+ 0x6c, 0x6c, 0x92, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b, 0x94,
+ 0x6c, 0x94, 0xe3, 0x80, 0x7c, 0x20, 0x81, 0x82, 0x81, 0x7a, 0x00, 0x90, 0x84, 0x20, 0x81, 0x7a,
+ 0x81, 0x7d, 0x00, 0x80, 0x74, 0x20, 0x81, 0x7d, 0x81, 0x85, 0x00, 0x70, 0x84, 0x20, 0x81, 0x85,
+ 0x81, 0x82, 0xe1,
+}
+
+var ActionCardGiftcard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe7, 0xa1,
+ 0x7b, 0xb2, 0x39, 0x80, 0x61, 0x7f, 0x61, 0x80, 0xb5, 0x7e, 0x61, 0x80, 0x7c, 0x80, 0xb1, 0x7c,
+ 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe9, 0x7d, 0x80, 0x11, 0x7c, 0x11, 0x81, 0x76, 0xb1, 0x82, 0x21,
+ 0x7e, 0x5d, 0x81, 0x7e, 0xa5, 0x7e, 0xa0, 0xf1, 0x7d, 0x11, 0x6d, 0x19, 0x7c, 0x58, 0x74, 0x58,
+ 0xb1, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x80, 0xb5, 0x80, 0x25, 0x80, 0x61, 0x81,
+ 0x61, 0x80, 0x84, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x58, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe2, 0x8c, 0x60, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19,
+ 0x7f, 0x84, 0x7c, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe2, 0x74,
+ 0x60, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84, 0x7c, 0x84,
+ 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0xac, 0xbc, 0xe6, 0x60, 0xe9,
+ 0x78, 0xe7, 0xc0, 0xe9, 0x88, 0xe3, 0x80, 0x6c, 0xe6, 0x60, 0xe8, 0x70, 0xe7, 0x29, 0x8a, 0x02,
+ 0x6c, 0xad, 0x7d, 0x41, 0x79, 0x80, 0x7c, 0x89, 0x79, 0x21, 0x84, 0x49, 0x7d, 0x84, 0xb9, 0x82,
+ 0x02, 0xc1, 0x86, 0x80, 0x94, 0xad, 0x7d, 0xd9, 0x85, 0x70, 0xe6, 0xa0, 0xe9, 0x98, 0xe1,
+}
+
+var ActionCardMembership = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xac, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x94, 0x21, 0x90, 0x78, 0x90, 0x88, 0xe8, 0x94, 0xe7,
+ 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb4, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0xc0, 0xe9, 0x88,
+ 0xe3, 0x80, 0x6c, 0xe6, 0x60, 0xe8, 0x60, 0xe7, 0xc0, 0xe9, 0x98, 0xe1,
+}
+
+var ActionCardTravel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe7, 0x74,
+ 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x74, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe9, 0xac, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x60, 0xe7, 0x98, 0xe9, 0x88, 0xe6, 0x74, 0xe8, 0x60, 0xe3,
+ 0xac, 0xbc, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0xc0, 0xe9, 0x88, 0xe3, 0x80, 0x6c, 0xe6, 0x60, 0xe8,
+ 0x70, 0xe7, 0x8c, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x98, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x8c, 0xe9, 0x98, 0xe1,
+}
+
+var ActionChangeHistory = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x8d, 0x77, 0x00,
+ 0xc9, 0x8c, 0x98, 0xe6, 0x39, 0x73, 0x00, 0x80, 0x8d, 0x77, 0xe2, 0x80, 0x60, 0x00, 0x58, 0xa0,
+ 0xe7, 0xd0, 0x00, 0x80, 0x60, 0xe1,
+}
+
+var ActionCheckCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0xb2, 0x80, 0x0d, 0x8b, 0xf5, 0x88, 0xa8, 0xa8, 0xa8,
+ 0x0d, 0x8b, 0x80, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0xf5, 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58,
+ 0xe3, 0x78, 0xbc, 0x00, 0x64, 0x80, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x78, 0x59, 0x84, 0x20,
+ 0x2d, 0x8f, 0xd5, 0x70, 0x01, 0x9c, 0x70, 0x78, 0x94, 0xe1,
+}
+
+var ActionChromeReaderMode = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x80, 0xe7, 0x9c,
+ 0xe9, 0x86, 0xe6, 0x84, 0xe3, 0x80, 0x76, 0xe7, 0x9c, 0xe9, 0x86, 0xe6, 0x84, 0xe3, 0x80, 0x94,
+ 0xe7, 0x9c, 0xe9, 0x86, 0xe6, 0x84, 0xe2, 0xa4, 0x60, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb4, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xbc, 0xe6, 0x80, 0xe8, 0x68, 0xe7, 0xa4, 0xe9, 0xb4,
+ 0xe1,
+}
+
+var ActionClass = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x58, 0xe6, 0x68,
+ 0xa0, 0xcd, 0x71, 0x58, 0x60, 0xcd, 0x6d, 0x60, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0x60, 0xe7, 0x94, 0xe9,
+ 0xa0, 0x21, 0x76, 0x7a, 0x76, 0x86, 0xe8, 0x60, 0xe1,
+}
+
+var ActionCode = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x7a, 0x35, 0x89,
+ 0x00, 0xb5, 0x71, 0x80, 0x20, 0x35, 0x89, 0xcd, 0x76, 0x01, 0x70, 0x68, 0x58, 0x80, 0x21, 0x98,
+ 0x98, 0xcd, 0x82, 0x35, 0x7d, 0xe3, 0x69, 0x8a, 0x80, 0x21, 0x35, 0x89, 0xcd, 0x76, 0xcd, 0x76,
+ 0xcd, 0x76, 0x00, 0x90, 0x68, 0x22, 0x98, 0x98, 0x68, 0x98, 0x35, 0x7d, 0x35, 0x7d, 0xe1,
+}
+
+var ActionCompareArrows = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x7a, 0x88, 0xe6,
+ 0x58, 0xe9, 0x88, 0xe7, 0x05, 0x8e, 0xe9, 0x8c, 0x00, 0x84, 0x8c, 0x20, 0x05, 0x78, 0x70, 0xe9,
+ 0x8c, 0xe3, 0xf5, 0x8b, 0x7c, 0xe9, 0x74, 0xe6, 0xa8, 0xe9, 0x78, 0xe6, 0xfd, 0x85, 0xe9, 0x74,
+ 0x00, 0x7c, 0x74, 0x20, 0xfd, 0x87, 0x90, 0xe1,
+}
+
+var ActionCopyright = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60,
+ 0xd1, 0x78, 0x60, 0x60, 0x80, 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0,
+ 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x29, 0x7c, 0xbd, 0x6d, 0xb0, 0x1d, 0x80, 0x59, 0x7f, 0x51,
+ 0x80, 0xc5, 0x7e, 0x99, 0x80, 0x45, 0x7e, 0x90, 0xb1, 0x80, 0x15, 0x7f, 0x31, 0x81, 0xc5, 0x7e,
+ 0xb2, 0x79, 0x80, 0xb5, 0x7f, 0x11, 0x81, 0x8d, 0x7f, 0xcd, 0x81, 0x8d, 0x7f, 0x7d, 0x80, 0x05,
+ 0x80, 0xed, 0x80, 0x19, 0x80, 0x4d, 0x81, 0x45, 0x80, 0x69, 0x80, 0x31, 0x80, 0xc1, 0x80, 0x6d,
+ 0x80, 0x0d, 0x81, 0xb9, 0x80, 0x91, 0x85, 0x80, 0xa9, 0x80, 0xad, 0x80, 0x11, 0x81, 0x41, 0x80,
+ 0xd5, 0x80, 0x45, 0x80, 0x49, 0x81, 0xe7, 0x95, 0x83, 0xb0, 0xf9, 0x7f, 0x11, 0x7f, 0xc9, 0x7f,
+ 0x35, 0x7e, 0x75, 0x7f, 0x6d, 0x7d, 0x92, 0x31, 0x7f, 0x8d, 0x7e, 0x99, 0x7e, 0xfd, 0x7d, 0xb1,
+ 0x7e, 0x7e, 0xd9, 0x7d, 0xb1, 0x7e, 0x3d, 0x7e, 0x89, 0x7f, 0x35, 0x7d, 0x89, 0x7f, 0xb0, 0xb5,
+ 0x7e, 0x80, 0x91, 0x7d, 0x39, 0x80, 0x9d, 0x7c, 0xad, 0x80, 0x92, 0x3d, 0x7e, 0x11, 0x81, 0x99,
+ 0x7d, 0xd9, 0x81, 0xe1, 0x7e, 0xb1, 0x81, 0x91, 0x7e, 0xb5, 0x82, 0x8d, 0x7f, 0x21, 0x82, 0x8d,
+ 0x7f, 0x45, 0x83, 0xe9, 0x8d, 0x80, 0xb0, 0x80, 0x29, 0x81, 0x29, 0x80, 0x41, 0x82, 0x79, 0x80,
+ 0x49, 0x83, 0x92, 0xcd, 0x80, 0xf1, 0x81, 0x71, 0x81, 0xb5, 0x82, 0x71, 0x81, 0x61, 0x81, 0x69,
+ 0x82, 0xd5, 0x81, 0x19, 0x82, 0xad, 0x80, 0x69, 0x83, 0xad, 0x80, 0xb0, 0xf1, 0x80, 0x80, 0xd1,
+ 0x81, 0xd9, 0x7f, 0xa5, 0x82, 0x8d, 0x7f, 0x92, 0x8d, 0x81, 0x45, 0x7f, 0x29, 0x82, 0xbd, 0x7e,
+ 0x21, 0x81, 0xd9, 0x7e, 0x7d, 0x81, 0x21, 0x7e, 0x91, 0x80, 0x85, 0x7e, 0x99, 0x80, 0xb5, 0x7d,
+ 0xe7, 0x6d, 0x7c, 0xb0, 0xfd, 0x7f, 0x6d, 0x80, 0xe1, 0x7f, 0xcd, 0x80, 0xb5, 0x7f, 0x29, 0x81,
+ 0x91, 0x95, 0x7f, 0xa9, 0x80, 0x49, 0x7f, 0xe9, 0x80, 0x59, 0x7f, 0x75, 0x80, 0xf5, 0x7e, 0x99,
+ 0x80, 0xb2, 0xa1, 0x7f, 0x21, 0x80, 0x39, 0x7f, 0x35, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x49, 0x7f,
+ 0xfd, 0x7f, 0xb1, 0x7e, 0xd5, 0x7f, 0x35, 0x7e, 0x89, 0x7f, 0x81, 0x7f, 0xb1, 0x7f, 0x19, 0x7f,
+ 0x45, 0x7f, 0xd1, 0x7e, 0xc5, 0x7e, 0x91, 0x85, 0x7f, 0xe9, 0x7e, 0x69, 0x7f, 0x41, 0x7e, 0xd9,
+ 0x7f, 0xb5, 0x7e, 0xd9, 0x7f, 0x09, 0x7e, 0xe9, 0x75, 0x7f, 0xb0, 0x80, 0x51, 0x7f, 0x0d, 0x80,
+ 0xa9, 0x7e, 0x29, 0x80, 0x7c, 0xe1,
+}
+
+var ActionCreditCard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8,
+ 0xe6, 0x60, 0xe8, 0x80, 0xe7, 0xc0, 0xe9, 0x98, 0xe3, 0x80, 0x58, 0xe6, 0x60, 0xe9, 0x78, 0xe7,
+ 0xc0, 0xe9, 0x88, 0xe1,
+}
+
+var ActionDashboard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x84, 0xe7, 0xa0,
+ 0xe8, 0x5c, 0xe6, 0x5c, 0xe9, 0xa8, 0xe3, 0x80, 0xa0, 0xe7, 0xa0, 0xe8, 0x8c, 0xe6, 0x5c, 0xe9,
+ 0x98, 0xe3, 0xa8, 0x80, 0xe7, 0xa0, 0xe8, 0x7c, 0xe6, 0x84, 0xe9, 0xa8, 0xe3, 0x80, 0x38, 0xe9,
+ 0x98, 0xe7, 0xa0, 0xe8, 0x5c, 0xe6, 0x84, 0xe1,
+}
+
+var ActionDateRange = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x7c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x88, 0x64, 0xe7,
+ 0x7c, 0xe8, 0x58, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x70, 0xe8, 0x58, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x7c, 0xb0, 0xc9, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0xa0, 0xb0,
+ 0x80, 0x35, 0x82, 0xc9, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80,
+ 0xc0, 0xe6, 0x64, 0xe8, 0x74, 0xe7, 0xb8, 0xe9, 0xac, 0xe1,
+}
+
+var ActionDelete = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x9c, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x6c, 0xe6, 0x68, 0xe9, 0xb0, 0xe2, 0x9c, 0x60, 0xe7, 0x72, 0x20, 0x7c, 0x7c,
+ 0xe6, 0x76, 0x20, 0x7c, 0x84, 0xe7, 0x72, 0xe9, 0x88, 0xe7, 0xb8, 0xe8, 0x60, 0xe1,
+}
+
+var ActionDeleteForever = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x9c, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x6c, 0xe6, 0x68, 0xe9, 0xb0, 0xe3, 0xf1, 0x84, 0xc5, 0x71, 0x20, 0xd5, 0x82,
+ 0x2d, 0x7d, 0x00, 0x80, 0x2d, 0x81, 0x21, 0x3d, 0x84, 0xc5, 0x7b, 0xd5, 0x82, 0xd5, 0x82, 0x00,
+ 0xd5, 0x82, 0x88, 0x21, 0x3d, 0x84, 0x3d, 0x84, 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x80, 0xd5, 0x86,
+ 0x21, 0xc5, 0x7b, 0x3d, 0x84, 0x2d, 0x7d, 0x2d, 0x7d, 0x00, 0x2d, 0x7d, 0x88, 0x20, 0xc5, 0x7b,
+ 0xc5, 0x7b, 0xe2, 0x8e, 0x60, 0x20, 0x7c, 0x7c, 0xe6, 0x76, 0x20, 0x7c, 0x84, 0xe7, 0x72, 0xe9,
+ 0x88, 0xe7, 0xb8, 0xe8, 0x60, 0xe1,
+}
+
+var ActionDescription = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x58, 0xe6, 0x68,
+ 0xa0, 0xcd, 0x71, 0x58, 0x05, 0x70, 0xcd, 0x6d, 0x05, 0x70, 0x60, 0x00, 0x60, 0xa0, 0xb0, 0x80,
+ 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x70, 0x00, 0x88, 0x58, 0xe3, 0x88, 0xc0, 0xe6, 0x70, 0xe9, 0x78, 0xe7,
+ 0xa0, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x70, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe3, 0x74,
+ 0x6c, 0xe8, 0x5e, 0x20, 0x96, 0x96, 0xe6, 0x84, 0xe1,
+}
+
+var ActionDNS = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x84, 0xe6, 0x60,
+ 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x98, 0xb0, 0x80, 0x19, 0x81, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0xc0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8,
+ 0x88, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe2, 0x6c, 0x9c, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe2, 0xa0, 0x5c, 0xe6, 0x60, 0xb0, 0xe9, 0x7e, 0x80, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x98, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xc0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x60, 0xb0, 0x80, 0xe9, 0x7e,
+ 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe2, 0x6c, 0x74, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78,
+ 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0x88, 0xe1,
+}
+
+var ActionDone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x59, 0x88, 0x00,
+ 0xa9, 0x71, 0x80, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x74, 0x9c, 0x21, 0xb0, 0x50, 0x2d, 0x7d,
+ 0x2d, 0x7d, 0xe1,
+}
+
+var ActionDoneAll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x6c, 0x22, 0x2d,
+ 0x7d, 0x2d, 0x7d, 0x51, 0x73, 0xb1, 0x8c, 0xd5, 0x82, 0xd5, 0x82, 0x00, 0x98, 0x6c, 0xe3, 0x7d,
+ 0x88, 0x2d, 0x7d, 0x01, 0x51, 0x7f, 0x59, 0x88, 0xf9, 0x76, 0x80, 0x20, 0x2d, 0x7d, 0xd5, 0x82,
+ 0x00, 0x51, 0x7f, 0x9c, 0x21, 0xb0, 0x50, 0x31, 0x7d, 0x2d, 0x7d, 0xe2, 0xd5, 0x68, 0xd5, 0x82,
+ 0x00, 0x68, 0x9c, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0x01, 0xa9, 0x6b, 0x80, 0xd5, 0x68, 0xd5, 0x82,
+ 0xe1,
+}
+
+var ActionDonutLarge = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x29, 0x72, 0xe8,
+ 0x58, 0xa0, 0x68, 0x5a, 0x58, 0xa1, 0x75, 0x58, 0x80, 0x90, 0x90, 0xa6, 0xa4, 0xa8, 0xe9, 0xd9,
+ 0x79, 0xb0, 0x74, 0x0d, 0x7f, 0x68, 0x35, 0x79, 0x68, 0x29, 0x72, 0x90, 0x8c, 0x21, 0x73, 0x98,
+ 0x29, 0x72, 0xe2, 0xf1, 0x8d, 0x7c, 0xe6, 0xa8, 0xb0, 0x11, 0x7f, 0x6c, 0x70, 0xf1, 0x6e, 0x5c,
+ 0x5c, 0xe9, 0x29, 0x86, 0xa0, 0x90, 0x05, 0x73, 0x15, 0x8d, 0x70, 0xf1, 0x8d, 0x7c, 0xe2, 0x84,
+ 0xd9, 0x8d, 0xe8, 0xa8, 0xb0, 0x94, 0x11, 0x7f, 0x11, 0x91, 0x70, 0xa4, 0x5c, 0xe7, 0xf1, 0x79,
+ 0xa0, 0x15, 0x8d, 0x90, 0x90, 0xfd, 0x8c, 0x84, 0xd9, 0x8d, 0xe1,
+}
+
+var ActionDonutSmall = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x51, 0x7a, 0xe8,
+ 0x58, 0xa0, 0x68, 0x5a, 0x58, 0x95, 0x75, 0x58, 0x80, 0x90, 0x90, 0xa6, 0xa4, 0xa8, 0xe8, 0xb1,
+ 0x85, 0xb0, 0x7c, 0x31, 0x7f, 0x78, 0xf5, 0x7c, 0x78, 0x51, 0x7a, 0x90, 0x84, 0x25, 0x7b, 0x88,
+ 0x51, 0x7a, 0xe2, 0xb9, 0x85, 0x7c, 0xe6, 0xa8, 0xb0, 0x0d, 0x7f, 0x81, 0x76, 0x70, 0xf1, 0x6e,
+ 0x5c, 0x5c, 0xe9, 0x51, 0x8e, 0xb0, 0x84, 0x99, 0x80, 0x0d, 0x83, 0xf5, 0x81, 0xb9, 0x83, 0xb1,
+ 0x83, 0xe2, 0x84, 0xb1, 0x85, 0xe8, 0xa8, 0xb0, 0x94, 0x11, 0x7f, 0x0d, 0x91, 0x81, 0x77, 0xa4,
+ 0x5c, 0xe6, 0xb9, 0x85, 0xb0, 0x51, 0x7f, 0xb9, 0x81, 0x49, 0x7e, 0x15, 0x83, 0x49, 0x7c, 0xb1,
+ 0x83, 0xe1,
+}
+
+var ActionEject = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x94, 0xe7, 0xb8,
+ 0xe9, 0x88, 0xe6, 0x64, 0xe3, 0x9c, 0x50, 0x00, 0xa9, 0x72, 0x8c, 0xe7, 0xb1, 0x9a, 0xe1,
+}
+
+var ActionEuroSymbol = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x9a, 0xb0, 0xfd,
+ 0x7a, 0x80, 0xa5, 0x76, 0x29, 0x7d, 0x79, 0x74, 0x72, 0xe6, 0x8c, 0xe9, 0x78, 0xe6, 0x2d, 0x79,
+ 0xb0, 0xe9, 0x7f, 0x59, 0x7f, 0xd5, 0x7f, 0xb1, 0x7e, 0xd5, 0x7f, 0x7c, 0x90, 0x11, 0x80, 0xa9,
+ 0x7e, 0x2d, 0x80, 0x7c, 0xe6, 0x8c, 0xe9, 0x78, 0xe6, 0x79, 0x7a, 0xb1, 0x2d, 0x82, 0xd9, 0x7b,
+ 0x85, 0x86, 0x72, 0x89, 0x8b, 0x72, 0x3d, 0x83, 0x80, 0x31, 0x86, 0x31, 0x81, 0x75, 0x88, 0x21,
+ 0x83, 0x00, 0xa4, 0x99, 0x72, 0xa0, 0xd1, 0x8e, 0xc1, 0x6f, 0x9d, 0x8a, 0x5c, 0x8c, 0x5c, 0xb0,
+ 0x2d, 0x78, 0x80, 0x85, 0x71, 0x05, 0x85, 0x0d, 0x6f, 0x98, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x21,
+ 0x86, 0xb1, 0xed, 0x7f, 0xa9, 0x80, 0xe1, 0x7f, 0x51, 0x81, 0xe1, 0x7f, 0x84, 0x80, 0xb1, 0x80,
+ 0x0d, 0x80, 0x59, 0x81, 0x21, 0x80, 0x84, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x0d, 0x87, 0xb1, 0x79,
+ 0x82, 0xfd, 0x86, 0x21, 0x89, 0x98, 0xf5, 0x90, 0x98, 0x9d, 0x84, 0x80, 0xd1, 0x88, 0x41, 0x7e,
+ 0x98, 0x69, 0x7b, 0x20, 0x75, 0x7c, 0x75, 0x7c, 0xa0, 0x31, 0x8c, 0xd1, 0x8b, 0x3d, 0x89, 0x9a,
+ 0x8c, 0x9a, 0xe1,
+}
+
+var ActionEvent = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x80, 0xe6, 0x80,
+ 0xe9, 0x94, 0xe7, 0x94, 0xe8, 0x80, 0xe2, 0x90, 0x54, 0xe9, 0x88, 0xe6, 0x70, 0xe8, 0x54, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x5c, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe7, 0x7c, 0xe8, 0x54, 0xe7, 0x78, 0xe3, 0x8c, 0xc8, 0xe6, 0x64, 0xe8, 0x70, 0xe7,
+ 0xb8, 0xe9, 0xac, 0xe1,
+}
+
+var ActionEventSeat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x98, 0xe9, 0x8c,
+ 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0xa8, 0xe9, 0x8c, 0xe7, 0x8c, 0xe8, 0x8c, 0xe6, 0x60, 0xe3, 0xbc,
+ 0x60, 0xe7, 0x8c, 0xe9, 0x8c, 0xe7, 0x74, 0xe2, 0x58, 0x78, 0xe7, 0x8c, 0xe9, 0x8c, 0xe6, 0x58,
+ 0xe3, 0xbc, 0x8c, 0xe6, 0x6c, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78,
+ 0xe7, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0xa0, 0xe1,
+}
+
+var ActionExitToApp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x7c, 0x2d, 0x87,
+ 0x00, 0x7e, 0x94, 0x22, 0x94, 0x6c, 0x6c, 0x6c, 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x59, 0x81, 0x7c,
+ 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x59, 0x93, 0x20, 0xd5, 0x7a, 0x2d, 0x85, 0xe2, 0x9c, 0x5c, 0xe6,
+ 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70,
+ 0xe7, 0xb8, 0xe9, 0xb8, 0xe6, 0x64, 0xe9, 0x70, 0xe6, 0x5c, 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ActionExplore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xcd, 0x7d, 0xb0,
+ 0xcd, 0x7e, 0x80, 0xcd, 0x7d, 0xfd, 0x80, 0xcd, 0x7d, 0x35, 0x82, 0x90, 0xfd, 0x80, 0x35, 0x82,
+ 0x35, 0x82, 0x35, 0x82, 0xb0, 0x39, 0x81, 0x80, 0x35, 0x82, 0x05, 0x7f, 0x35, 0x82, 0xcd, 0x7d,
+ 0x90, 0x05, 0x7f, 0xcd, 0x7d, 0xcd, 0x7d, 0xcd, 0x7d, 0xe2, 0x80, 0x58, 0xa0, 0xf5, 0x74, 0x58,
+ 0x58, 0xf5, 0x74, 0x58, 0x80, 0xb0, 0x80, 0x0d, 0x8b, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0x90, 0xa8,
+ 0x0d, 0x77, 0xa8, 0x58, 0xb0, 0x80, 0xf5, 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58, 0xe3, 0x61, 0x84,
+ 0x61, 0x98, 0x00, 0x68, 0x98, 0x20, 0xa1, 0x87, 0xa1, 0x6f, 0x00, 0x98, 0x68, 0x20, 0x61, 0x78,
+ 0x61, 0x90, 0xe1,
+}
+
+var ActionExtension = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa2, 0x7c, 0xe7, 0x7a,
+ 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x70, 0xe8, 0x5e, 0xb0,
+ 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0xe9, 0x86,
+ 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x20, 0xfd, 0x7f,
+ 0x99, 0x87, 0xe6, 0x5e, 0xb0, 0xfd, 0x82, 0x80, 0x69, 0x85, 0x6d, 0x82, 0x69, 0x85, 0x69, 0x85,
+ 0x80, 0xfd, 0x71, 0x69, 0x88, 0x5e, 0x69, 0x88, 0xe6, 0x05, 0x6c, 0x00, 0x58, 0xa0, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x99, 0x87, 0xe9, 0x7a, 0xb1, 0x80, 0x05, 0x7d,
+ 0x6d, 0x82, 0x99, 0x7a, 0x69, 0x85, 0x99, 0x7a, 0xfd, 0x82, 0x80, 0x69, 0x85, 0x6d, 0x82, 0x69,
+ 0x85, 0x69, 0x85, 0xe9, 0x86, 0xe6, 0x94, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe9, 0x70, 0xe7, 0x86, 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x90, 0xc5, 0x7d,
+ 0x76, 0x76, 0x76, 0xe1,
+}
+
+var ActionFace = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x81, 0x7f, 0xb0,
+ 0xa1, 0x7e, 0x80, 0x81, 0x7d, 0x21, 0x81, 0x81, 0x7d, 0x81, 0x82, 0x92, 0x21, 0x81, 0x81, 0x82,
+ 0x81, 0x82, 0x81, 0x82, 0x81, 0x82, 0xe1, 0x7e, 0x81, 0x82, 0x81, 0x7d, 0xe1, 0x7e, 0x81, 0x7d,
+ 0x81, 0x7d, 0x81, 0x7d, 0xe3, 0x98, 0x80, 0xb0, 0xa1, 0x7e, 0x80, 0x81, 0x7d, 0x21, 0x81, 0x81,
+ 0x7d, 0x81, 0x82, 0x92, 0x21, 0x81, 0x81, 0x82, 0x81, 0x82, 0x81, 0x82, 0x81, 0x82, 0xe1, 0x7e,
+ 0x81, 0x82, 0x81, 0x7d, 0xe1, 0x7e, 0x81, 0x7d, 0x81, 0x7d, 0x81, 0x7d, 0xe2, 0x80, 0x58, 0xa0,
+ 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d,
+ 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb6, 0x31, 0x77, 0x80,
+ 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80, 0x6d, 0x7f, 0x0d, 0x80, 0xd9, 0x7e, 0x19, 0x80, 0x4d, 0x7e,
+ 0xb5, 0x84, 0xe9, 0x7d, 0x79, 0x88, 0x0d, 0x7a, 0x6d, 0x8a, 0x45, 0x75, 0xa1, 0x83, 0x19, 0x85,
+ 0x91, 0x89, 0x75, 0x88, 0x51, 0x90, 0x75, 0x88, 0x8d, 0x81, 0x80, 0x11, 0x83, 0xd1, 0x7f, 0x81,
+ 0x84, 0x79, 0x7f, 0x71, 0x80, 0x71, 0x81, 0xad, 0x80, 0xf5, 0x82, 0xad, 0x80, 0x89, 0x84, 0x80,
+ 0xd1, 0x88, 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ActionFavorite = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xb5, 0x92, 0x20,
+ 0x19, 0x7d, 0x61, 0x7d, 0xa1, 0xcd, 0x72, 0xb9, 0x86, 0x58, 0x8d, 0x80, 0x58, 0x72, 0x58, 0xd5,
+ 0x72, 0xd5, 0x70, 0x5c, 0x6e, 0x5c, 0xb0, 0x7d, 0x83, 0x80, 0xd1, 0x86, 0xa1, 0x81, 0x92, 0x2d,
+ 0x84, 0xa0, 0x31, 0x82, 0xa1, 0x6f, 0x85, 0x85, 0x5c, 0x92, 0x5c, 0xb1, 0x2d, 0x86, 0x80, 0x96,
+ 0xd5, 0x84, 0x96, 0x96, 0x80, 0x8d, 0x87, 0x35, 0x79, 0xb9, 0x8d, 0xe9, 0x6e, 0x11, 0x97, 0x00,
+ 0x80, 0xb5, 0x92, 0xe1,
+}
+
+var ActionFavoriteBorder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x92, 0x5c, 0xb0, 0x85,
+ 0x7c, 0x80, 0x31, 0x79, 0xa1, 0x81, 0x6e, 0x2d, 0x84, 0xa1, 0xd1, 0x7d, 0xa1, 0x6f, 0x7d, 0x7a,
+ 0x5c, 0x6e, 0x5c, 0xd5, 0x70, 0x5c, 0x58, 0xd5, 0x72, 0x58, 0x72, 0xb0, 0x80, 0x8d, 0x87, 0xcd,
+ 0x86, 0xb9, 0x8d, 0x19, 0x91, 0x11, 0x97, 0x00, 0x80, 0xb5, 0x92, 0x20, 0xe9, 0x82, 0x61, 0x7d,
+ 0xa0, 0x35, 0x8d, 0xb9, 0x86, 0xa8, 0x8d, 0x80, 0xa8, 0x72, 0xb0, 0x80, 0xd5, 0x79, 0x2d, 0x7b,
+ 0x6a, 0x6a, 0x6a, 0xe3, 0x35, 0x77, 0x1d, 0x9f, 0x21, 0xcd, 0x7f, 0x31, 0x80, 0xcd, 0x7f, 0xd1,
+ 0x7f, 0xa0, 0x49, 0x76, 0x7d, 0x84, 0x60, 0xc9, 0x7e, 0x60, 0x72, 0xb1, 0x80, 0x05, 0x7c, 0x05,
+ 0x83, 0x72, 0x8e, 0x72, 0x15, 0x83, 0x80, 0x15, 0x86, 0xfd, 0x81, 0x21, 0x87, 0xb9, 0x84, 0xe7,
+ 0xbd, 0x83, 0xa0, 0xed, 0x82, 0xfd, 0x73, 0xed, 0x85, 0x64, 0x92, 0x64, 0xb1, 0xfd, 0x83, 0x80,
+ 0x8e, 0x05, 0x83, 0x8e, 0x8e, 0x80, 0xc9, 0x85, 0xb9, 0x79, 0x7d, 0x8b, 0x35, 0x70, 0x1d, 0x94,
+ 0xe1,
+}
+
+var ActionFeedback = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x84, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var ActionFindInPage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x2d, 0x8f, 0xe8,
+ 0x70, 0x00, 0x88, 0x58, 0xe6, 0x68, 0xa0, 0xcd, 0x71, 0x58, 0x05, 0x70, 0xcd, 0x6d, 0x05, 0x70,
+ 0x60, 0x00, 0x60, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x98,
+ 0xb0, 0xe5, 0x80, 0x80, 0xb5, 0x81, 0xb5, 0x7f, 0x61, 0x82, 0x35, 0x7f, 0x20, 0x21, 0x77, 0x21,
+ 0x77, 0xa0, 0xf1, 0x83, 0x61, 0x8b, 0x0d, 0x82, 0x98, 0x80, 0x98, 0xb0, 0x7d, 0x7a, 0x80, 0x6c,
+ 0x85, 0x7b, 0x6c, 0x6c, 0x91, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xb0,
+ 0x80, 0x0d, 0x82, 0x61, 0x7f, 0xf1, 0x83, 0x59, 0x7e, 0x85, 0x85, 0x00, 0xa0, 0x2d, 0x8f, 0xe2,
+ 0x74, 0x84, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x92, 0x8c, 0x51, 0x7d, 0x8c,
+ 0x74, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe1,
+}
+
+var ActionFindReplace = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x68, 0xb0, 0xc5,
+ 0x82, 0x80, 0x45, 0x85, 0x21, 0x81, 0x11, 0x87, 0xf1, 0x82, 0x00, 0x80, 0x78, 0xe7, 0x98, 0xe8,
+ 0x60, 0x20, 0xe9, 0x7b, 0x19, 0x84, 0xa1, 0x61, 0x85, 0x91, 0x71, 0xe1, 0x81, 0x60, 0x7c, 0x60,
+ 0xf5, 0x76, 0x60, 0x21, 0x71, 0x39, 0x75, 0x29, 0x70, 0x78, 0xe7, 0x0d, 0x84, 0xb0, 0xf1, 0x80,
+ 0x71, 0x7b, 0xf5, 0x84, 0x70, 0xcd, 0x89, 0x70, 0xe3, 0x49, 0x8b, 0x45, 0x92, 0xb0, 0x55, 0x81,
+ 0x31, 0x7e, 0x3d, 0x82, 0x0d, 0x7c, 0x91, 0x82, 0xbd, 0x79, 0xe6, 0xcd, 0x87, 0xb1, 0x11, 0x7f,
+ 0x91, 0x84, 0x0d, 0x7b, 0x90, 0x35, 0x76, 0x90, 0x3d, 0x7d, 0x80, 0xbd, 0x7a, 0xe1, 0x7e, 0xf1,
+ 0x78, 0x11, 0x7d, 0x00, 0x78, 0x80, 0xe6, 0x60, 0xe9, 0x98, 0x20, 0x19, 0x84, 0xe9, 0x7b, 0xb1,
+ 0x89, 0x82, 0x89, 0x82, 0x09, 0x86, 0x19, 0x84, 0xe9, 0x89, 0x19, 0x84, 0x19, 0x83, 0x80, 0xf5,
+ 0x85, 0xfd, 0x7e, 0x49, 0x88, 0x45, 0x7d, 0x01, 0xa0, 0xfd, 0x92, 0xfd, 0x92, 0xa0, 0x20, 0x4d,
+ 0x76, 0x45, 0x76, 0xe1,
+}
+
+var ActionFingerprint = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa1, 0x8b, 0xf1, 0x70,
+ 0xb0, 0xd9, 0x7f, 0x80, 0xb1, 0x7f, 0xf5, 0x7f, 0x8d, 0x7f, 0xe5, 0x7f, 0xa0, 0x55, 0x87, 0xd9,
+ 0x6e, 0x88, 0x5c, 0x05, 0x80, 0x5c, 0xb2, 0x09, 0x7c, 0x80, 0x4d, 0x78, 0xf5, 0x80, 0xdd, 0x74,
+ 0xd1, 0x82, 0x85, 0x7f, 0x45, 0x80, 0xe9, 0x7e, 0x19, 0x80, 0xa5, 0x7e, 0x99, 0x7f, 0xbd, 0x7f,
+ 0x85, 0x7f, 0xe9, 0x7f, 0xe9, 0x7e, 0x69, 0x80, 0xa5, 0x7e, 0xa0, 0xa9, 0x77, 0x09, 0x6d, 0xb9,
+ 0x7b, 0x58, 0x05, 0x80, 0x58, 0xb2, 0x45, 0x84, 0x80, 0xfd, 0x87, 0xf1, 0x80, 0x11, 0x8c, 0x0d,
+ 0x83, 0x7d, 0x80, 0x41, 0x80, 0xb1, 0x80, 0xdd, 0x80, 0x71, 0x80, 0x59, 0x81, 0xd1, 0x7f, 0x59,
+ 0x80, 0x79, 0x7f, 0x8d, 0x80, 0x1d, 0x7f, 0x8d, 0x80, 0xe2, 0x5e, 0x71, 0x7b, 0xba, 0xcd, 0x7f,
+ 0x80, 0x99, 0x7f, 0xf1, 0x7f, 0x6d, 0x7f, 0xd1, 0x7f, 0x8d, 0x7f, 0xb1, 0x7f, 0x71, 0x7f, 0x11,
+ 0x7f, 0xc5, 0x7f, 0x9d, 0x7e, 0xfd, 0x81, 0x35, 0x7d, 0x85, 0x84, 0x76, 0x85, 0x87, 0x75, 0x79,
+ 0x4d, 0x86, 0xc1, 0x7c, 0x55, 0x8e, 0xbd, 0x7c, 0xa1, 0x94, 0xfd, 0x7f, 0xfd, 0x82, 0x8d, 0x81,
+ 0x85, 0x85, 0xb9, 0x83, 0x81, 0x87, 0x81, 0x86, 0x51, 0x80, 0x75, 0x80, 0x39, 0x80, 0x11, 0x81,
+ 0xc5, 0x7f, 0x65, 0x81, 0x8d, 0x7f, 0x51, 0x80, 0xed, 0x7e, 0x39, 0x80, 0x99, 0x7e, 0xc5, 0x7f,
+ 0x35, 0x7e, 0x7d, 0x7d, 0xed, 0x7b, 0x81, 0x7b, 0x39, 0x79, 0x21, 0x7a, 0x45, 0x7a, 0x0d, 0x7d,
+ 0xf1, 0x72, 0x11, 0x7d, 0x35, 0x6d, 0x05, 0x80, 0x4d, 0x7d, 0x69, 0x81, 0x76, 0x65, 0x83, 0x35,
+ 0x79, 0xf1, 0x85, 0xcd, 0x7f, 0x45, 0x80, 0x7d, 0x7f, 0x69, 0x80, 0x31, 0x7f, 0x69, 0x80, 0xe3,
+ 0x85, 0x8c, 0x21, 0x98, 0xb3, 0xbd, 0x7f, 0x80, 0x7d, 0x7f, 0xe9, 0x7f, 0x4d, 0x7f, 0xb5, 0x7f,
+ 0x45, 0x7e, 0x41, 0x7e, 0x55, 0x7d, 0x25, 0x7d, 0xfd, 0x7b, 0xbd, 0x7a, 0xa1, 0x7e, 0x8d, 0x7d,
+ 0xe5, 0x7d, 0x89, 0x7a, 0xe5, 0x7d, 0x51, 0x77, 0x80, 0x11, 0x7a, 0x15, 0x85, 0x39, 0x75, 0x55,
+ 0x8b, 0x39, 0x75, 0x90, 0x55, 0x8b, 0xd5, 0x84, 0x55, 0x8b, 0xc9, 0x8a, 0xb0, 0x80, 0x8d, 0x80,
+ 0x8d, 0x7f, 0x82, 0x7e, 0x82, 0x90, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0xb5, 0x80, 0x29, 0x7b, 0xd1,
+ 0x7b, 0x39, 0x77, 0xad, 0x76, 0x39, 0x77, 0xdd, 0x7a, 0x80, 0xad, 0x76, 0xf1, 0x83, 0xad, 0x76,
+ 0xc9, 0x88, 0x80, 0xe1, 0x82, 0xa5, 0x80, 0x8d, 0x85, 0xd9, 0x81, 0xb5, 0x87, 0x4d, 0x81, 0x4d,
+ 0x82, 0x29, 0x82, 0x4d, 0x83, 0xb1, 0x83, 0xd9, 0x84, 0x65, 0x80, 0x65, 0x80, 0x65, 0x80, 0x09,
+ 0x81, 0xfd, 0x7f, 0x69, 0x81, 0xd1, 0x7f, 0x35, 0x80, 0x91, 0x7f, 0x4d, 0x80, 0x51, 0x7f, 0x4d,
+ 0x80, 0xe3, 0x55, 0x8e, 0x4d, 0x7c, 0xb2, 0xa1, 0x7d, 0x80, 0x89, 0x7b, 0x69, 0x7f, 0xcd, 0x79,
+ 0x3d, 0x7e, 0x09, 0x7d, 0xfd, 0x7d, 0x41, 0x7b, 0xb5, 0x7a, 0x41, 0x7b, 0x39, 0x77, 0x80, 0x75,
+ 0x7f, 0x75, 0x80, 0x7e, 0x82, 0x7e, 0x90, 0x82, 0x75, 0x80, 0x82, 0x82, 0xb5, 0x80, 0xd1, 0x82,
+ 0x75, 0x81, 0x79, 0x85, 0xe1, 0x83, 0x21, 0x87, 0x69, 0x81, 0xf5, 0x80, 0x11, 0x83, 0x71, 0x81,
+ 0x11, 0x85, 0x71, 0x81, 0x7d, 0x80, 0x80, 0x4d, 0x81, 0xf5, 0x7f, 0x19, 0x82, 0xd1, 0x7f, 0x8d,
+ 0x80, 0xe9, 0x7f, 0x11, 0x81, 0x45, 0x80, 0x29, 0x81, 0xd1, 0x80, 0x19, 0x80, 0x8d, 0x80, 0xbd,
+ 0x7f, 0x11, 0x81, 0x31, 0x7f, 0x29, 0x81, 0xd5, 0x7e, 0x35, 0x80, 0xd9, 0x7d, 0x39, 0x80, 0x91,
+ 0x7d, 0x39, 0x80, 0xe2, 0xd1, 0x85, 0xa8, 0xb5, 0xe9, 0x7f, 0x80, 0xd1, 0x7f, 0xfd, 0x7f, 0xbd,
+ 0x7f, 0xf5, 0x7f, 0xd1, 0x7c, 0x21, 0x7f, 0xbd, 0x7a, 0xf5, 0x7d, 0x91, 0x78, 0xcd, 0x7b, 0x35,
+ 0x7d, 0x39, 0x7d, 0xad, 0x7b, 0x85, 0x79, 0xad, 0x7b, 0x91, 0x75, 0x80, 0xc1, 0x7c, 0xc5, 0x82,
+ 0x1d, 0x7a, 0x29, 0x86, 0x1d, 0x7a, 0x69, 0x83, 0x80, 0x29, 0x86, 0xa5, 0x82, 0x29, 0x86, 0xe5,
+ 0x85, 0x80, 0x25, 0x82, 0xe1, 0x81, 0xe5, 0x83, 0x29, 0x84, 0xe5, 0x83, 0x90, 0x29, 0x84, 0x45,
+ 0x7e, 0x29, 0x84, 0x1d, 0x7c, 0xba, 0x80, 0x75, 0x78, 0x81, 0x79, 0x55, 0x72, 0x85, 0x71, 0x55,
+ 0x72, 0x51, 0x7a, 0x80, 0x21, 0x75, 0x29, 0x83, 0xc9, 0x72, 0x11, 0x88, 0x39, 0x7f, 0xa1, 0x81,
+ 0xd5, 0x7e, 0x85, 0x83, 0xd5, 0x7e, 0x9d, 0x85, 0x80, 0x91, 0x81, 0x25, 0x80, 0x05, 0x84, 0x55,
+ 0x81, 0x35, 0x87, 0x31, 0x80, 0x85, 0x80, 0xf1, 0x7f, 0x19, 0x81, 0x69, 0x7f, 0x4d, 0x81, 0x7d,
+ 0x7f, 0x31, 0x80, 0xe9, 0x7e, 0xf1, 0x7f, 0xb5, 0x7e, 0x69, 0x7f, 0x05, 0x7f, 0x61, 0x7d, 0x8d,
+ 0x7e, 0xcd, 0x7a, 0x8d, 0x7e, 0x19, 0x78, 0x80, 0x99, 0x7d, 0x75, 0x80, 0x6d, 0x7b, 0x61, 0x81,
+ 0x89, 0x79, 0xad, 0x82, 0x6d, 0x7a, 0x91, 0x88, 0xd1, 0x76, 0x05, 0x8f, 0xd1, 0x76, 0x19, 0x89,
+ 0x80, 0x7d, 0x90, 0x09, 0x87, 0x7d, 0x90, 0xad, 0x8f, 0x80, 0x41, 0x83, 0x3d, 0x7d, 0xe5, 0x85,
+ 0xd9, 0x79, 0xe5, 0x85, 0x90, 0xd9, 0x79, 0x5d, 0x7d, 0xd9, 0x79, 0x1d, 0x7a, 0xb0, 0x80, 0xdd,
+ 0x7d, 0x21, 0x7e, 0x1d, 0x7c, 0xd9, 0x7b, 0x1d, 0x7c, 0x90, 0xd9, 0x7b, 0xbd, 0x81, 0xd9, 0x7b,
+ 0xe5, 0x83, 0xb3, 0x80, 0x69, 0x83, 0x55, 0x81, 0xa1, 0x86, 0xbd, 0x83, 0x05, 0x89, 0xe5, 0x81,
+ 0xe1, 0x81, 0xbd, 0x83, 0xed, 0x82, 0x8d, 0x86, 0xb1, 0x83, 0x89, 0x80, 0x29, 0x80, 0xd9, 0x80,
+ 0xb5, 0x80, 0xb5, 0x80, 0x3d, 0x81, 0xe1, 0x7f, 0x71, 0x80, 0x7d, 0x7f, 0xbd, 0x80, 0x0d, 0x7f,
+ 0xbd, 0x80, 0xe1,
+}
+
+var ActionFlightLand = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5a, 0x9c, 0xe7, 0xcc,
+ 0xe9, 0x88, 0xe6, 0x5a, 0xe3, 0x61, 0x8e, 0x8d, 0x74, 0x21, 0xb1, 0x88, 0x55, 0x82, 0xa1, 0x8a,
+ 0xd9, 0x82, 0xb1, 0x99, 0x81, 0x71, 0x80, 0x3d, 0x83, 0x7d, 0x7f, 0xad, 0x83, 0xe1, 0x7d, 0x71,
+ 0x80, 0x69, 0x7e, 0x7d, 0x7f, 0xc5, 0x7c, 0xe1, 0x7d, 0x55, 0x7c, 0x20, 0x61, 0x75, 0x29, 0x7d,
+ 0x01, 0x19, 0x80, 0x0d, 0x6d, 0x3d, 0x7c, 0x58, 0xe9, 0x91, 0x90, 0x00, 0x4d, 0x72, 0xe9, 0x79,
+ 0x21, 0x25, 0x7e, 0x5d, 0x7b, 0x19, 0x7d, 0x39, 0x7f, 0xe9, 0x59, 0x8a, 0x21, 0x35, 0x83, 0xdd,
+ 0x80, 0xa1, 0x8a, 0xd9, 0x82, 0xe1,
+}
+
+var ActionFlightTakeoff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5a, 0x9c, 0xe7, 0xcc,
+ 0xe9, 0x88, 0xe6, 0x5a, 0xe3, 0x25, 0xa7, 0x45, 0x6d, 0xb0, 0x91, 0x7f, 0x69, 0x7e, 0xf1, 0x7d,
+ 0x75, 0x7d, 0x55, 0x7c, 0xe1, 0x7d, 0x01, 0xd9, 0x85, 0x78, 0x0d, 0x78, 0x21, 0x6f, 0x2a, 0x25,
+ 0x7c, 0x0d, 0x81, 0x49, 0x88, 0x59, 0x8e, 0x11, 0x76, 0xa9, 0x82, 0x11, 0x7c, 0xe9, 0x7c, 0x19,
+ 0x7d, 0xc9, 0x80, 0xa5, 0x83, 0x51, 0x86, 0x89, 0x81, 0xa9, 0x82, 0x35, 0x83, 0x25, 0x7f, 0xa1,
+ 0x8a, 0x29, 0x7d, 0xb1, 0x88, 0xad, 0x7d, 0xa1, 0x8a, 0x29, 0x7d, 0xb0, 0x99, 0x81, 0x91, 0x7f,
+ 0x8d, 0x82, 0xf1, 0x7d, 0x21, 0x82, 0x55, 0x7c, 0xe1,
+}
+
+var ActionFlipToBack = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x80, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe7, 0x88, 0xe8,
+ 0x5c, 0xe3, 0x90, 0xb0, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe2, 0x9c, 0x5c, 0xe9,
+ 0x88, 0xe7, 0x88, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x84, 0x5c, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x70, 0xb8, 0xe9, 0x78, 0xe7, 0x78, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0xa8, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x64, 0x6c,
+ 0xe6, 0x5c, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe6, 0x64, 0xe8, 0x6c, 0xe3, 0xa8, 0x78, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x80, 0xb0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var ActionFlipToFront = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x84, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x88, 0x90, 0xe9, 0x78, 0xe6, 0x5c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe2, 0x5c, 0x74, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0xb0, 0xb0, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x38, 0xe6, 0x74, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb0, 0xe6, 0x74, 0xe8, 0x64, 0xe7, 0xa8, 0xe9,
+ 0xa8, 0xe2, 0x7c, 0xa4, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x80, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var ActionGTranslate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x64, 0xe6, 0xc5,
+ 0x7d, 0x00, 0x78, 0x58, 0xe6, 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9,
+ 0xb4, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x9c, 0x20, 0x84, 0x8c, 0xe7,
+ 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x55, 0x76, 0x2d, 0x85, 0xb0, 0x7d, 0x7b, 0x80, 0xd5, 0x77,
+ 0x55, 0x7c, 0xd5, 0x77, 0xd5, 0x77, 0x90, 0xad, 0x83, 0xd5, 0x77, 0x2d, 0x88, 0xd5, 0x77, 0xb0,
+ 0x15, 0x82, 0x80, 0xf9, 0x83, 0xbd, 0x80, 0x79, 0x85, 0x21, 0x82, 0x22, 0x21, 0x80, 0x21, 0x80,
+ 0x91, 0x7d, 0x5d, 0x82, 0xe1, 0x7f, 0xe5, 0x7f, 0xb1, 0x71, 0x7f, 0x75, 0x7f, 0x71, 0x7e, 0xd5,
+ 0x7e, 0xf5, 0x7c, 0xd5, 0x7e, 0x61, 0x7d, 0x80, 0x41, 0x7b, 0x2d, 0x82, 0x41, 0x7b, 0xd9, 0x84,
+ 0x90, 0x21, 0x82, 0xd9, 0x84, 0xc1, 0x84, 0xd9, 0x84, 0xb0, 0xbd, 0x82, 0x80, 0xf1, 0x83, 0x41,
+ 0x7e, 0x41, 0x84, 0x15, 0x7d, 0xe7, 0x95, 0x7b, 0xe9, 0xe9, 0x7c, 0xe7, 0xe9, 0x87, 0x20, 0x09,
+ 0x80, 0x25, 0x80, 0xb1, 0x15, 0x80, 0x6d, 0x80, 0x1d, 0x80, 0xcd, 0x80, 0x1d, 0x80, 0x35, 0x81,
+ 0xfd, 0x7f, 0xb5, 0x84, 0xc5, 0x7c, 0xfd, 0x87, 0x21, 0x78, 0xfd, 0x87, 0xe3, 0x11, 0x8c, 0x99,
+ 0x7c, 0xb0, 0xad, 0x80, 0x35, 0x81, 0x7d, 0x81, 0x59, 0x82, 0x61, 0x82, 0x69, 0x83, 0x21, 0xf1,
+ 0x7e, 0x11, 0x81, 0xb1, 0x7e, 0x8d, 0x7b, 0xe3, 0x8d, 0x81, 0x75, 0x7e, 0xe7, 0x05, 0x7e, 0x20,
+ 0x65, 0x7f, 0xed, 0x7d, 0xe7, 0xfd, 0x87, 0x90, 0x51, 0x7f, 0xa1, 0x82, 0xe1, 0x7c, 0x79, 0x85,
+ 0xb0, 0xf1, 0x7e, 0xc5, 0x7e, 0x31, 0x7e, 0x91, 0x7d, 0xb9, 0x7d, 0x9d, 0x7c, 0xe2, 0xa4, 0xa0,
+ 0xb0, 0x80, 0x19, 0x81, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe6, 0x84, 0x22, 0x88, 0x78, 0x61, 0x7e,
+ 0x79, 0x7a, 0xd9, 0x81, 0x29, 0x7e, 0x00, 0x95, 0x8b, 0x98, 0x21, 0x75, 0x81, 0x8d, 0x7e, 0x99,
+ 0x7a, 0xa1, 0x7a, 0xb0, 0xcd, 0x81, 0xf1, 0x7d, 0x35, 0x83, 0x81, 0x7b, 0xd5, 0x83, 0xfd, 0x78,
+ 0xe6, 0x9c, 0xe9, 0xed, 0x7d, 0xe7, 0xbd, 0x78, 0xe8, 0x74, 0xe7, 0xed, 0x7d, 0xe9, 0x15, 0x82,
+ 0xe7, 0x15, 0x7c, 0x00, 0x59, 0x7e, 0x68, 0xe6, 0xa0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80,
+ 0x84, 0x84, 0xe9, 0xb4, 0xe1,
+}
+
+var ActionGavel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0xa4, 0xe7, 0xb0,
+ 0xe9, 0x88, 0xe6, 0x54, 0xe3, 0x7d, 0x88, 0x25, 0x66, 0x20, 0xa9, 0x85, 0x59, 0x7a, 0x00, 0x71,
+ 0x94, 0xc5, 0x8e, 0x20, 0x59, 0x7a, 0xa9, 0x85, 0xe2, 0xa1, 0x80, 0x54, 0x00, 0xf1, 0x8b, 0x51,
+ 0x75, 0x20, 0x59, 0x7a, 0xa9, 0x85, 0x00, 0xf9, 0x7a, 0xa9, 0x6f, 0xe3, 0x09, 0x6f, 0xf9, 0x90,
+ 0x00, 0xf9, 0x7a, 0x4d, 0x86, 0x20, 0x59, 0x7a, 0xa9, 0x85, 0x00, 0x54, 0xa5, 0x80, 0xe1,
+}
+
+var ActionGetApp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x74, 0xe7, 0x70,
+ 0xe8, 0x5c, 0xe6, 0x74, 0xe9, 0x98, 0xe7, 0x70, 0x21, 0x9c, 0x9c, 0x9c, 0x64, 0xe2, 0x64, 0x98,
+ 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x64, 0xe1,
+}
+
+var ActionGIF = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7e, 0x74, 0xe6, 0x84,
+ 0xe9, 0x98, 0xe7, 0x7a, 0xe2, 0x74, 0x74, 0xe6, 0x68, 0xb0, 0xcd, 0x7e, 0x80, 0x7c, 0x82, 0x7c,
+ 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x82, 0xcd, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x8c, 0xb0, 0x35, 0x81,
+ 0x80, 0x84, 0x7e, 0x84, 0x7c, 0xe9, 0x78, 0xe6, 0x72, 0xe9, 0x86, 0xe7, 0x78, 0xe9, 0x74, 0xe6,
+ 0x78, 0xe8, 0x78, 0xb0, 0x80, 0x7e, 0x35, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0xa8, 0x86, 0xe8, 0x74,
+ 0xe7, 0x6e, 0xe9, 0x98, 0xe6, 0x90, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x7a, 0xe7, 0x78, 0xe9, 0x7c,
+ 0xe1,
+}
+
+var ActionGrade = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x8d, 0x8a, 0x00,
+ 0x5d, 0x8c, 0xa4, 0x20, 0xbd, 0x7c, 0xf1, 0x71, 0x00, 0xa8, 0x7d, 0x7a, 0x20, 0xa1, 0x71, 0xc5,
+ 0x7e, 0x00, 0x80, 0x58, 0x20, 0x61, 0x7a, 0x41, 0x8d, 0x00, 0x58, 0x7d, 0x7a, 0x20, 0xe9, 0x8a,
+ 0x75, 0x89, 0x00, 0xa5, 0x73, 0xa4, 0xe1,
+}
+
+var ActionGroupWork = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0xb0, 0x80, 0x0d, 0x8b, 0xf5, 0x88, 0xa8, 0xa8, 0xa8,
+ 0x90, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0xb0, 0x80, 0xf5, 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58, 0xe3,
+ 0x70, 0xbe, 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x92, 0x3d, 0x82, 0x76, 0x8a,
+ 0x76, 0x8a, 0x3d, 0x82, 0x8a, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x8a, 0xe3, 0x86, 0x5a, 0xb0, 0x80,
+ 0x3d, 0x7d, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x92, 0x8a, 0x3d, 0x82, 0x8a, 0x8a, 0xc5, 0x7d, 0x8a,
+ 0x76, 0x8a, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0xe3, 0x9a, 0xa6, 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0xc5,
+ 0x7d, 0x76, 0x76, 0x92, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x8a, 0x3d, 0x82, 0x8a, 0x8a, 0xc5, 0x7d,
+ 0x8a, 0x76, 0x8a, 0xe1,
+}
+
+var ActionHelp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x84, 0xc4, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x88, 0xe9, 0x88, 0xe3, 0x21, 0x84, 0x85, 0x70, 0x20, 0x35, 0x7e, 0xd9, 0x81, 0xa0, 0xe9, 0x82,
+ 0xcd, 0x81, 0x84, 0x86, 0x84, 0x8c, 0xe7, 0x78, 0xe9, 0x7e, 0xb0, 0x80, 0xcd, 0x7d, 0xe9, 0x80,
+ 0xcd, 0x7b, 0x59, 0x82, 0x59, 0x7a, 0x20, 0x7d, 0x82, 0x7d, 0x7d, 0xa0, 0x8d, 0x83, 0x19, 0x7c,
+ 0x88, 0x19, 0x7b, 0x88, 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x90, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe7, 0x78, 0xb0, 0x80, 0x95, 0x7b, 0x95, 0x83, 0x70, 0x90, 0x70, 0x90,
+ 0x90, 0x95, 0x83, 0x90, 0x90, 0xb0, 0x80, 0xc5, 0x81, 0x4d, 0x7f, 0x59, 0x83, 0x21, 0x7e, 0x85,
+ 0x84, 0xe1,
+}
+
+var ActionHelpOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x98, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x84, 0x40, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74,
+ 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b,
+ 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x92,
+ 0x31, 0x87, 0x60, 0xa0, 0x60, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe3,
+ 0x80, 0x48, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0xe7, 0x88, 0xb0, 0x80, 0xcd,
+ 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x90, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xb0, 0x80, 0x88, 0x74,
+ 0x81, 0x83, 0x74, 0x94, 0xe7, 0x88, 0xb1, 0x80, 0x81, 0x7b, 0x8c, 0x76, 0x8c, 0x6c, 0x80, 0x95,
+ 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe1,
+}
+
+var ActionHighlightOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x85, 0x70, 0x0b,
+ 0x80, 0x2d, 0x7d, 0xd5, 0x7a, 0x70, 0x70, 0xd5, 0x7a, 0x2d, 0x7d, 0x80, 0x70, 0x2d, 0x85, 0xd5,
+ 0x7a, 0x90, 0x80, 0xd5, 0x82, 0x2d, 0x85, 0x90, 0x90, 0x2d, 0x85, 0xd5, 0x82, 0x80, 0x90, 0xd5,
+ 0x7a, 0x2d, 0x85, 0x70, 0xe2, 0x80, 0x58, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80,
+ 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80,
+ 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80, 0x31, 0x77,
+ 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ActionHistory = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x81, 0x5c, 0xa0,
+ 0x0d, 0x78, 0x5c, 0x60, 0x11, 0x76, 0x60, 0x80, 0xe6, 0x54, 0x21, 0xcd, 0x87, 0xcd, 0x87, 0x25,
+ 0x80, 0x4d, 0x80, 0x00, 0x74, 0x80, 0xe7, 0x74, 0xb0, 0x80, 0x45, 0x78, 0x45, 0x86, 0x64, 0x9c,
+ 0x64, 0x91, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xb0, 0x21, 0x7c, 0x80,
+ 0xa5, 0x78, 0x6d, 0x7e, 0x1d, 0x76, 0xe5, 0x7b, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0xa1, 0x89, 0x78,
+ 0xfd, 0x8f, 0x05, 0x7d, 0xa4, 0xfd, 0x81, 0xa4, 0xf1, 0x8b, 0xa4, 0xa8, 0xf1, 0x89, 0xa8, 0x80,
+ 0x80, 0xf1, 0x8b, 0x5c, 0xfd, 0x81, 0x5c, 0xe2, 0x80, 0x70, 0xe9, 0x94, 0x20, 0x91, 0x88, 0x15,
+ 0x85, 0x00, 0x94, 0xa9, 0x84, 0x20, 0x72, 0xd9, 0x7b, 0xe8, 0x70, 0xe7, 0x7a, 0xe1,
+}
+
+var ActionHome = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0xa0, 0xe8, 0x88,
+ 0xe7, 0x90, 0xe9, 0x98, 0xe7, 0x94, 0xe8, 0x80, 0xe7, 0x8c, 0x01, 0x80, 0x5c, 0x58, 0x80, 0xe7,
+ 0x8c, 0xe9, 0xa0, 0xe1,
+}
+
+var ActionHourglassEmpty = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x5a, 0xe9, 0x94,
+ 0x21, 0x92, 0x92, 0x6e, 0x92, 0xe9, 0x94, 0xe7, 0xb0, 0xe8, 0x92, 0x21, 0x6e, 0x6e, 0x92, 0x6e,
+ 0xe8, 0x5a, 0xe6, 0x68, 0xe3, 0xa8, 0xba, 0xe9, 0x8a, 0xe6, 0x70, 0xe9, 0x76, 0x21, 0x90, 0x70,
+ 0x90, 0x90, 0xe3, 0x70, 0x68, 0x20, 0x70, 0x70, 0xe8, 0x62, 0xe7, 0xa0, 0xe9, 0x8a, 0x20, 0x70,
+ 0x90, 0xe1,
+}
+
+var ActionHourglassFull = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x58, 0xe6, 0x68,
+ 0xe9, 0x96, 0xe7, 0x05, 0x80, 0x20, 0xfd, 0x7f, 0x05, 0x80, 0x01, 0xfd, 0x7c, 0x80, 0x68, 0xfd,
+ 0x88, 0x20, 0x05, 0x80, 0x05, 0x80, 0xe6, 0x68, 0xe9, 0x96, 0xe7, 0xb0, 0xe8, 0x92, 0xe7, 0xfd,
+ 0x7f, 0x20, 0x05, 0x80, 0xfd, 0x7f, 0x01, 0x05, 0x83, 0x80, 0x98, 0x05, 0x77, 0x20, 0xfd, 0x7f,
+ 0xfd, 0x7f, 0xe6, 0x98, 0xe8, 0x58, 0xe7, 0x78, 0xe1,
+}
+
+var ActionHTTP = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x62, 0x7c, 0xe6, 0x5a,
+ 0xe9, 0x78, 0xe6, 0x54, 0xe9, 0x98, 0xe7, 0x86, 0xe9, 0x76, 0xe7, 0x88, 0xe9, 0x8a, 0xe7, 0x86,
+ 0xe8, 0x74, 0xe6, 0x62, 0xe9, 0x88, 0xe3, 0x8a, 0x7e, 0xe7, 0x86, 0xe9, 0x92, 0xe7, 0x86, 0xe9,
+ 0x6e, 0xe7, 0x86, 0xe9, 0x7a, 0xe7, 0x6e, 0xe9, 0x86, 0xe3, 0x96, 0x80, 0xe7, 0x86, 0xe9, 0x92,
+ 0xe7, 0x86, 0xe9, 0x6e, 0xe7, 0x86, 0xe9, 0x7a, 0xe7, 0x6e, 0xe9, 0x86, 0xe3, 0xa4, 0x7a, 0xe7,
+ 0x72, 0xe9, 0x98, 0xe7, 0x86, 0xe9, 0x78, 0xe7, 0x88, 0xb0, 0xb5, 0x81, 0x80, 0x86, 0xb5, 0x7e,
+ 0x86, 0x7a, 0xe9, 0x7c, 0xb0, 0x80, 0x4d, 0x7e, 0xb5, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0x80, 0x8a,
+ 0xe7, 0x78, 0xe9, 0x7c, 0xe7, 0x88, 0xe9, 0x84, 0xe1,
+}
+
+var ActionHTTPS = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x70, 0xe7, 0x7c,
+ 0xe9, 0x78, 0xb0, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x80, 0x6c, 0x7d, 0x6e, 0x6c,
+ 0x68, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2,
+ 0x80, 0x94, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x35, 0x86, 0x5c, 0xe6,
+ 0xcd, 0x79, 0xe9, 0x78, 0xb1, 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79,
+ 0x6d, 0x83, 0x80, 0x35, 0x86, 0xc9, 0x82, 0x35, 0x86, 0x35, 0x86, 0xe9, 0x88, 0xe1,
+}
+
+var ActionImportantDevices = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x74, 0xe7, 0xf1,
+ 0x79, 0x00, 0x7c, 0x68, 0x20, 0x11, 0x7e, 0x8c, 0xe6, 0x6c, 0x24, 0xf1, 0x84, 0x85, 0x83, 0x21,
+ 0x7e, 0xd1, 0x85, 0xf1, 0x84, 0x69, 0x7c, 0xf1, 0x84, 0x99, 0x83, 0x21, 0x7e, 0x31, 0x7a, 0xe3,
+ 0x80, 0x80, 0xe7, 0xf1, 0x79, 0x00, 0x7c, 0x68, 0x20, 0x11, 0x7e, 0x8c, 0xe6, 0x6c, 0x24, 0xf1,
+ 0x84, 0x85, 0x83, 0x21, 0x7e, 0xd1, 0x85, 0xf1, 0x84, 0x69, 0x7c, 0xf1, 0x84, 0x99, 0x83, 0x21,
+ 0x7e, 0x31, 0x7a, 0xe3, 0xa0, 0x05, 0x84, 0x00, 0x98, 0x7c, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0xe9, 0xa4, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x94,
+ 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x80, 0xb0, 0x80, 0xe9, 0x7e, 0x19,
+ 0x7f, 0x05, 0x7e, 0x7c, 0x05, 0x7e, 0xe2, 0xac, 0xa0, 0xe6, 0x98, 0xe8, 0x84, 0xe7, 0x94, 0xe9,
+ 0x9c, 0xe2, 0xa0, 0x58, 0xe6, 0x58, 0xa0, 0xc9, 0x69, 0x58, 0x50, 0xc9, 0x6d, 0x50, 0x60, 0xe9,
+ 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xc9, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x9c, 0xe9, 0x88, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x58,
+ 0xe8, 0x60, 0xe7, 0xc8, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x60, 0xb0, 0x80, 0xc9, 0x7d, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe2, 0xf1, 0x7f, 0x74, 0x00, 0x7c, 0x68, 0x20, 0x11, 0x7e, 0x8c, 0xe6, 0x6c,
+ 0x24, 0xf1, 0x84, 0x85, 0x83, 0x21, 0x7e, 0xd1, 0x85, 0xf1, 0x84, 0x69, 0x7c, 0xf1, 0x84, 0x99,
+ 0x83, 0x21, 0x7e, 0x31, 0x7a, 0x00, 0x8c, 0x74, 0xe7, 0xf1, 0x79, 0xe1,
+}
+
+var ActionInfo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x84, 0xbc, 0xe7, 0x78, 0xe8, 0x7c, 0xe7,
+ 0x88, 0xe9, 0x98, 0xe3, 0x80, 0x60, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var ActionInfoOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x94, 0xe7, 0x88,
+ 0xe8, 0x7c, 0xe7, 0x78, 0xe9, 0x98, 0xe3, 0x84, 0x44, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74,
+ 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b,
+ 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80,
+ 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0,
+ 0xe3, 0x7c, 0x54, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var ActionInput = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x05, 0x6e, 0xe6,
+ 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe8, 0x74, 0xe7, 0x88, 0xe8, 0xfd,
+ 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe6, 0x5c, 0xe8, 0x8c, 0xe6, 0x54, 0xe9, 0x05, 0x88, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0xf5, 0x83, 0x88, 0xf5, 0x83, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x3d, 0x7e, 0x88, 0x0d, 0x7c, 0xe9, 0x48, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe2, 0x7c, 0x90, 0x21, 0x90, 0x70, 0x70, 0x70, 0xe9, 0x8c, 0xe6, 0x54, 0xe9, 0x88, 0xe7,
+ 0xa8, 0xe9, 0x8c, 0xe1,
+}
+
+var ActionInvertColors = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x51, 0x8b, 0xdd, 0x77,
+ 0x01, 0x80, 0x8d, 0x6c, 0xb1, 0x74, 0xdd, 0x77, 0xb1, 0xc1, 0x79, 0x41, 0x86, 0xc1, 0x79, 0x61,
+ 0x90, 0x80, 0xa1, 0x96, 0x21, 0x83, 0x21, 0x83, 0x39, 0x87, 0xb1, 0x84, 0x51, 0x8b, 0xb1, 0x84,
+ 0x90, 0x31, 0x88, 0x71, 0x7e, 0x51, 0x8b, 0x51, 0x7b, 0xb0, 0x41, 0x86, 0xc1, 0x79, 0x41, 0x86,
+ 0xa1, 0x6f, 0x80, 0x61, 0x69, 0xe2, 0x80, 0x2d, 0x8f, 0xb1, 0xcd, 0x7c, 0x80, 0xc9, 0x79, 0xc1,
+ 0x7e, 0x85, 0x77, 0x7d, 0x7c, 0xbd, 0x7d, 0xbd, 0x7d, 0x7d, 0x7c, 0xbd, 0x7a, 0x7d, 0x7c, 0x85,
+ 0x77, 0x90, 0x41, 0x81, 0xc9, 0x79, 0x85, 0x83, 0x85, 0x77, 0x00, 0x80, 0x35, 0x72, 0xe9, 0xf9,
+ 0x9c, 0xe1,
+}
+
+var ActionLabel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x45, 0x8b, 0xb1, 0x73,
+ 0xa0, 0x8d, 0x8a, 0xad, 0x72, 0x59, 0x89, 0x64, 0x90, 0x64, 0x20, 0x54, 0x05, 0x80, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xc5, 0x81, 0x78, 0xfd, 0x83, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0xfd, 0x83, 0x88, 0xfd, 0x83, 0x00, 0x90, 0x9c, 0xb0, 0x59, 0x81, 0x80, 0x8d, 0x82, 0x55, 0x7f,
+ 0x45, 0x83, 0x51, 0x7e, 0x00, 0xa8, 0x80, 0x20, 0x45, 0x77, 0xb1, 0x73, 0xe1,
+}
+
+var ActionLabelOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x45, 0x8b, 0xb1, 0x73,
+ 0xa0, 0x8d, 0x8a, 0xad, 0x72, 0x59, 0x89, 0x64, 0x90, 0x64, 0x20, 0x54, 0x05, 0x80, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xc5, 0x81, 0x78, 0xfd, 0x83, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0xfd, 0x83, 0x88, 0xfd, 0x83, 0x00, 0x90, 0x9c, 0xb0, 0x59, 0x81, 0x80, 0x8d, 0x82, 0x55, 0x7f,
+ 0x45, 0x83, 0x51, 0x7e, 0x00, 0xa8, 0x80, 0x20, 0x45, 0x77, 0xb1, 0x73, 0xe2, 0x90, 0x94, 0xe6,
+ 0x64, 0xe8, 0x6c, 0xe7, 0xac, 0x20, 0x19, 0x87, 0x94, 0x00, 0x90, 0x94, 0xe1,
+}
+
+var ActionLanguage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe3,
+ 0xd9, 0x8d, 0x98, 0xe7, 0x19, 0x7a, 0xb1, 0x59, 0x7f, 0x81, 0x7d, 0x71, 0x7e, 0x19, 0x7b, 0x3d,
+ 0x7d, 0xe1, 0x78, 0xb1, 0x83, 0x45, 0x81, 0xbd, 0x86, 0xd1, 0x83, 0xa9, 0x88, 0x21, 0x87, 0xe2,
+ 0x80, 0x11, 0x70, 0xb0, 0xad, 0x81, 0x69, 0x82, 0xf9, 0x82, 0x11, 0x85, 0xd1, 0x83, 0xf1, 0x87,
+ 0xe7, 0x5d, 0x78, 0xb0, 0xd9, 0x80, 0x25, 0x7d, 0x29, 0x82, 0x79, 0x7a, 0xd1, 0x83, 0x11, 0x78,
+ 0xe2, 0x85, 0x70, 0x88, 0xa0, 0x31, 0x70, 0xb9, 0x82, 0x60, 0x61, 0x81, 0x60, 0x80, 0x90, 0x31,
+ 0x80, 0x49, 0x7d, 0x85, 0x80, 0x78, 0xe7, 0xc1, 0x86, 0xb1, 0xd9, 0x7f, 0x51, 0x81, 0xbd, 0x7f,
+ 0xa5, 0x82, 0xbd, 0x7f, 0x88, 0x80, 0x5d, 0x81, 0x1d, 0x80, 0xb1, 0x82, 0x49, 0x80, 0x88, 0xe6,
+ 0x85, 0x70, 0xe3, 0xa1, 0x81, 0x88, 0xe7, 0xe9, 0x85, 0xb1, 0xa9, 0x80, 0x81, 0x82, 0x91, 0x81,
+ 0xe9, 0x84, 0xc5, 0x82, 0x21, 0x87, 0x51, 0x7c, 0xbd, 0x7e, 0x45, 0x79, 0x31, 0x7c, 0x59, 0x77,
+ 0xe1, 0x78, 0xe3, 0xe9, 0x85, 0x60, 0xe7, 0x19, 0x7a, 0xb1, 0xed, 0x81, 0xb1, 0x7c, 0xfd, 0x84,
+ 0x21, 0x7a, 0xa9, 0x88, 0xe1, 0x78, 0xcd, 0x7e, 0x3d, 0x82, 0xe5, 0x7d, 0xa1, 0x84, 0x3d, 0x7d,
+ 0x21, 0x87, 0xe2, 0x80, 0xf1, 0x8f, 0xb0, 0x59, 0x7e, 0x99, 0x7d, 0x0d, 0x7d, 0xf1, 0x7a, 0x31,
+ 0x7c, 0x11, 0x78, 0xe7, 0xa5, 0x87, 0xb0, 0x25, 0x7f, 0xdd, 0x82, 0xd9, 0x7d, 0x89, 0x85, 0x31,
+ 0x7c, 0xf1, 0x87, 0xe2, 0xb1, 0x84, 0x88, 0xe7, 0xa5, 0x76, 0xb1, 0xd1, 0x7f, 0xb1, 0x7e, 0xb1,
+ 0x7f, 0x5d, 0x7d, 0xb1, 0x7f, 0x78, 0x80, 0xa5, 0x7e, 0x21, 0x80, 0x51, 0x7d, 0x51, 0x80, 0x78,
+ 0xe7, 0x5d, 0x89, 0xb1, 0x31, 0x80, 0x51, 0x81, 0x51, 0x80, 0xa5, 0x82, 0x51, 0x80, 0x88, 0x80,
+ 0x5d, 0x81, 0xe1, 0x7f, 0xb1, 0x82, 0xb1, 0x7f, 0x88, 0xe3, 0x85, 0x80, 0x21, 0x8b, 0xb0, 0x35,
+ 0x81, 0xc5, 0x7d, 0x1d, 0x82, 0x61, 0x7b, 0xc5, 0x82, 0xe1, 0x78, 0xe7, 0xe9, 0x85, 0xb0, 0x11,
+ 0x7e, 0x51, 0x83, 0x05, 0x7b, 0xdd, 0x85, 0x59, 0x77, 0x21, 0x87, 0xe2, 0xb9, 0x88, 0x88, 0xb1,
+ 0x29, 0x80, 0xb1, 0x7e, 0x49, 0x80, 0x5d, 0x7d, 0x49, 0x80, 0x78, 0x80, 0xa5, 0x7e, 0xe5, 0x7f,
+ 0x51, 0x7d, 0xb9, 0x7f, 0x78, 0xe7, 0xc1, 0x86, 0xb0, 0x55, 0x80, 0x49, 0x81, 0x89, 0x80, 0xa1,
+ 0x82, 0x89, 0x80, 0x88, 0x90, 0xd1, 0x7f, 0xb9, 0x82, 0x79, 0x7f, 0x88, 0xe7, 0x41, 0x79, 0xe1,
+}
+
+var ActionLaunch = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x9c, 0xe6, 0x64,
+ 0xe8, 0x64, 0xe7, 0x9c, 0xe8, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x80, 0xe7, 0x78, 0xe9, 0x9c, 0xe2, 0x88, 0x5c,
+ 0xe9, 0x88, 0xe7, 0x2d, 0x87, 0x00, 0x85, 0x77, 0xa9, 0x85, 0x20, 0xd5, 0x82, 0xd5, 0x82, 0x00,
+ 0x9c, 0xd5, 0x74, 0xe8, 0x78, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x88, 0xe1,
+}
+
+var ActionLightbulbOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0xa4, 0xb0, 0x80,
+ 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x90, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f,
+ 0x84, 0x7c, 0xe9, 0x7c, 0xe6, 0x74, 0xe9, 0x84, 0xe3, 0x8c, 0x34, 0xb1, 0x45, 0x78, 0x80, 0x64,
+ 0x45, 0x86, 0x64, 0x9c, 0x80, 0xc5, 0x84, 0x61, 0x82, 0xf5, 0x88, 0x8c, 0x7d, 0x8b, 0xe8, 0x94,
+ 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x98, 0xb0, 0x19, 0x81, 0x80, 0x84,
+ 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x7d, 0x7b, 0xb1, 0xa1, 0x83, 0x79, 0x7d, 0x8c, 0x49, 0x79, 0x8c,
+ 0x85, 0x74, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0xe3, 0xb5, 0x85, 0x35, 0x96, 0x00,
+ 0x88, 0x65, 0x83, 0xe8, 0x90, 0xe7, 0x70, 0xe9, 0x69, 0x7b, 0x20, 0x4d, 0x7e, 0xd1, 0x7e, 0xa0,
+ 0x99, 0x77, 0x55, 0x80, 0x6c, 0x45, 0x7d, 0x6c, 0x05, 0x7a, 0xb0, 0x80, 0x7d, 0x7a, 0x7d, 0x84,
+ 0x6c, 0x94, 0x6c, 0x90, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xb0, 0x80, 0x41, 0x83, 0x69, 0x7e, 0x51,
+ 0x86, 0xb5, 0x7b, 0x31, 0x88, 0xe1,
+}
+
+var ActionLineStyle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x90, 0xe7, 0x94,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x9a, 0x80, 0xe7, 0x94, 0xe9, 0x78, 0xe6, 0x76, 0xe9,
+ 0x88, 0xe3, 0x9a, 0x80, 0xe7, 0x94, 0xe9, 0x78, 0xe6, 0x90, 0xe9, 0x88, 0xe2, 0x5c, 0xa0, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x80, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0xa8,
+ 0x80, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x84, 0xe9, 0x88, 0xe2, 0x5c, 0x60, 0xe9, 0x90, 0xe7, 0xc8,
+ 0xe8, 0x60, 0xe6, 0x5c, 0xe1,
+}
+
+var ActionLineWeight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x94, 0xe7, 0xc8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x8c, 0xe7, 0xc8, 0xe9, 0x7c, 0xe6, 0x5c, 0xe9,
+ 0x84, 0xe3, 0x80, 0x64, 0xe7, 0xc8, 0xe9, 0x74, 0xe6, 0x5c, 0xe9, 0x8c, 0xe2, 0x5c, 0x60, 0xe9,
+ 0x90, 0xe7, 0xc8, 0xe8, 0x60, 0xe6, 0x5c, 0xe1,
+}
+
+var ActionList = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x84, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x90, 0xe7,
+ 0xb8, 0xe9, 0x78, 0xe6, 0x6c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c,
+ 0xe9, 0x88, 0xe3, 0x80, 0x58, 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c, 0xe1,
+}
+
+var ActionLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x70, 0xe7, 0x7c,
+ 0xe9, 0x78, 0xb0, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x80, 0x6c, 0x7d, 0x6e, 0x6c,
+ 0x68, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2,
+ 0x80, 0x94, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x35, 0x86, 0x5c, 0xe6,
+ 0xcd, 0x79, 0xe9, 0x78, 0xb1, 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79,
+ 0x6d, 0x83, 0x80, 0x35, 0x86, 0xc9, 0x82, 0x35, 0x86, 0x35, 0x86, 0xe9, 0x88, 0xe1,
+}
+
+var ActionLockOpen = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x98, 0x5c, 0xe7, 0x7c, 0xe9, 0x78, 0xb0, 0x80,
+ 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x80, 0x6c, 0x7d, 0x6e, 0x6c, 0x68, 0xe7, 0xcd, 0x83,
+ 0xb1, 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79, 0x6d, 0x83, 0x80, 0x35,
+ 0x86, 0xc9, 0x82, 0x35, 0x86, 0x35, 0x86, 0xe9, 0x88, 0xe6, 0x68, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x78, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb0, 0xe6, 0x68, 0xe8, 0x78, 0xe7, 0xb0, 0xe9, 0xa8,
+ 0xe1,
+}
+
+var ActionLockOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x98, 0x5c, 0xe7, 0x7c, 0xe9, 0x78, 0xb0, 0x80,
+ 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x80, 0x6c, 0x7d, 0x6e, 0x6c, 0x68, 0xe9, 0x88, 0xe7,
+ 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0xcd, 0x6d, 0x78, 0xb0,
+ 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79, 0x90, 0x35, 0x86, 0xc9, 0x82,
+ 0x35, 0x86, 0x35, 0x86, 0xe9, 0x88, 0xe6, 0xcd, 0x79, 0xe9, 0x78, 0xe2, 0x98, 0xa0, 0xe6, 0x68,
+ 0xe8, 0x78, 0xe7, 0xb0, 0xe9, 0xa8, 0xe1,
+}
+
+var ActionLoyalty = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd1, 0x92, 0x29, 0x7f,
+ 0x00, 0xd5, 0x80, 0x2d, 0x6d, 0xa0, 0x1d, 0x80, 0x75, 0x6c, 0x1d, 0x7f, 0x58, 0x7c, 0x58, 0xe6,
+ 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0x9c, 0xb0, 0x80, 0x1d, 0x81,
+ 0x75, 0x80, 0x1d, 0x82, 0x31, 0x81, 0xd5, 0x82, 0x20, 0xa4, 0xa4, 0xa0, 0xe9, 0x7f, 0x8d, 0x93,
+ 0xe9, 0x80, 0xa8, 0x84, 0xa8, 0xb0, 0x1d, 0x81, 0x80, 0x1d, 0x82, 0x8d, 0x7f, 0xd5, 0x82, 0xd5,
+ 0x7e, 0x20, 0x9c, 0x64, 0xa0, 0x8d, 0x93, 0x19, 0x84, 0xa8, 0x19, 0x83, 0xa8, 0x84, 0xb0, 0x80,
+ 0xe5, 0x7e, 0x8d, 0x7f, 0xe5, 0x7d, 0xd1, 0x7e, 0x29, 0x7d, 0xe2, 0x66, 0x6c, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8d, 0x97, 0x8d, 0x90, 0x00, 0x84, 0x15, 0x8f, 0x20,
+ 0x75, 0x77, 0x75, 0x77, 0xa0, 0x91, 0x78, 0xa1, 0x85, 0x70, 0x61, 0x84, 0x70, 0x86, 0xb1, 0x80,
+ 0x3d, 0x7d, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x61, 0x81, 0x80, 0xa5, 0x82, 0x91, 0x80, 0x8d, 0x83,
+ 0x79, 0x81, 0x00, 0x84, 0xf1, 0x80, 0x20, 0x75, 0x81, 0x8d, 0x7e, 0xa0, 0x61, 0x84, 0x91, 0x7e,
+ 0xa1, 0x85, 0x7c, 0x8e, 0x7c, 0xb1, 0xc5, 0x82, 0x80, 0x8a, 0x3d, 0x82, 0x8a, 0x8a, 0x80, 0x61,
+ 0x81, 0x71, 0x7f, 0xa1, 0x82, 0x8d, 0x7e, 0x8d, 0x83, 0xe1,
+}
+
+var ActionMarkUnreadMailbox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe6, 0x78,
+ 0xe9, 0x98, 0xe7, 0x78, 0xe8, 0x60, 0xe7, 0x98, 0xe8, 0x50, 0xe6, 0x68, 0xe9, 0x98, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ActionMotorcycle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x8e, 0x11, 0x7a,
+ 0x00, 0xd1, 0x86, 0x64, 0xe6, 0x7c, 0xe9, 0x88, 0xe7, 0x31, 0x87, 0x20, 0x88, 0x88, 0xe6, 0x64,
+ 0xa0, 0x69, 0x6c, 0x74, 0x50, 0x69, 0x7e, 0x50, 0x88, 0x90, 0x69, 0x84, 0x94, 0x94, 0x94, 0xb0,
+ 0xed, 0x84, 0x80, 0xe9, 0x88, 0xa1, 0x7c, 0xcd, 0x89, 0x70, 0xe7, 0x4d, 0x83, 0x20, 0x8d, 0x85,
+ 0x75, 0x7a, 0xa0, 0x39, 0x84, 0x8d, 0x81, 0x88, 0xbd, 0x82, 0x88, 0x88, 0xb0, 0x80, 0x99, 0x85,
+ 0x69, 0x84, 0x94, 0x94, 0x94, 0x90, 0x94, 0x99, 0x7b, 0x94, 0x6c, 0xb0, 0x80, 0xb5, 0x7a, 0x11,
+ 0x7c, 0x75, 0x76, 0xe1, 0x76, 0x11, 0x76, 0xe2, 0xa5, 0x77, 0x8c, 0xb1, 0x29, 0x7f, 0x4d, 0x82,
+ 0xed, 0x7c, 0x88, 0x5d, 0x7a, 0x88, 0xbd, 0x7c, 0x80, 0x74, 0x45, 0x7d, 0x74, 0x74, 0x90, 0xbd,
+ 0x82, 0x74, 0x8c, 0x74, 0xb0, 0x91, 0x82, 0x80, 0xcd, 0x84, 0xb5, 0x81, 0xa5, 0x85, 0x88, 0xe6,
+ 0x64, 0xe9, 0x88, 0xe7, 0xa5, 0x85, 0xe2, 0x9c, 0x94, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d,
+ 0x74, 0x74, 0x92, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x51, 0x7d, 0x8c,
+ 0x74, 0x8c, 0xe1,
+}
+
+var ActionNoteAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x58, 0xe6, 0x68,
+ 0xa0, 0xcd, 0x71, 0x58, 0x05, 0x70, 0xcd, 0x6d, 0x05, 0x70, 0x60, 0x00, 0x60, 0xa0, 0xb0, 0x80,
+ 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x70, 0x00, 0x88, 0x58, 0xe3, 0x88, 0xb8, 0xe7, 0x74, 0xe9, 0x8c, 0xe7,
+ 0x78, 0xe9, 0x74, 0xe7, 0x74, 0xe9, 0x78, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7,
+ 0x8c, 0xe9, 0x88, 0xe3, 0x74, 0x64, 0xe8, 0x5e, 0x20, 0x96, 0x96, 0xe6, 0x84, 0xe1,
+}
+
+var ActionOfflinePin = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0x6a,
+ 0x58, 0x58, 0x6a, 0x58, 0x80, 0x91, 0x92, 0xa8, 0xa8, 0xa8, 0xa8, 0x6e, 0xa8, 0x58, 0x80, 0x96,
+ 0x58, 0x80, 0x58, 0xe3, 0x94, 0xc0, 0xe6, 0x6c, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe3, 0x99,
+ 0x72, 0x70, 0x00, 0x6c, 0x69, 0x7d, 0x22, 0xcd, 0x82, 0x35, 0x7d, 0xcd, 0x83, 0xcd, 0x83, 0x99,
+ 0x8a, 0x69, 0x75, 0x01, 0x94, 0x99, 0x76, 0x99, 0x7c, 0x88, 0xe1,
+}
+
+var ActionOpacity = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x51, 0x8b, 0xfd, 0x77,
+ 0x00, 0x80, 0xb1, 0x6c, 0x20, 0xb1, 0x74, 0x4d, 0x8b, 0xa0, 0x91, 0x71, 0x1d, 0x7b, 0x60, 0x45,
+ 0x7f, 0x60, 0x45, 0x83, 0x92, 0x91, 0x81, 0x39, 0x88, 0xb1, 0x84, 0x59, 0x8b, 0x39, 0x87, 0xb5,
+ 0x84, 0x51, 0x8b, 0xb5, 0x84, 0x35, 0x88, 0x6d, 0x7e, 0x51, 0x8b, 0x4d, 0x7b, 0x80, 0xa0, 0x45,
+ 0x87, 0xa0, 0x45, 0x83, 0x90, 0x71, 0x7e, 0xd9, 0x77, 0x51, 0x7b, 0xb9, 0x74, 0xe2, 0x68, 0x88,
+ 0xb0, 0x05, 0x80, 0x78, 0x3d, 0x81, 0x75, 0x79, 0x85, 0x83, 0x31, 0x77, 0x00, 0x80, 0x89, 0x72,
+ 0x20, 0x7d, 0x88, 0xc1, 0x88, 0xa0, 0xc5, 0x8a, 0x8d, 0x7d, 0xfd, 0x8b, 0x80, 0x98, 0x88, 0xe6,
+ 0x68, 0xe1,
+}
+
+var ActionOpenInBrowser = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x60, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe8, 0x70, 0xe7, 0xb8, 0xe9, 0xa8,
+ 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80, 0x78, 0x20, 0x70, 0x90,
+ 0xe7, 0x8c, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0x88, 0xe7, 0x8c, 0x20, 0x70, 0x70, 0xe1,
+}
+
+var ActionOpenInNew = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x9c, 0xe6, 0x64,
+ 0xe8, 0x64, 0xe7, 0x9c, 0xe8, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x80, 0xe7, 0x78, 0xe9, 0x9c, 0xe2, 0x88, 0x5c,
+ 0xe9, 0x88, 0xe7, 0x2d, 0x87, 0x00, 0x85, 0x77, 0xa9, 0x85, 0x20, 0xd5, 0x82, 0xd5, 0x82, 0x00,
+ 0x9c, 0xd5, 0x74, 0xe8, 0x78, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x88, 0xe1,
+}
+
+var ActionOpenWith = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x74, 0xe7, 0x90,
+ 0xe9, 0x74, 0xe7, 0x8c, 0x01, 0x80, 0x54, 0x6c, 0x68, 0xe7, 0x8c, 0xe9, 0x8c, 0xe3, 0x7c, 0x84,
+ 0xe7, 0x74, 0xe9, 0x74, 0x00, 0x54, 0x80, 0x20, 0x94, 0x94, 0xe9, 0x74, 0xe7, 0x8c, 0xe9, 0x70,
+ 0xe3, 0xb8, 0x88, 0x00, 0x98, 0x6c, 0xe9, 0x8c, 0xe7, 0x74, 0xe9, 0x90, 0xe7, 0x8c, 0xe9, 0x8c,
+ 0x20, 0x94, 0x6c, 0xe3, 0x5c, 0x8c, 0xe7, 0x70, 0xe9, 0x8c, 0xe7, 0x74, 0x21, 0x94, 0x94, 0x94,
+ 0x6c, 0xe7, 0x74, 0xe9, 0x74, 0xe1,
+}
+
+var ActionPageview = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7e, 0x74, 0xb0, 0x3d,
+ 0x7d, 0x80, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0x92, 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0x8a, 0xc5, 0x7d,
+ 0x8a, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe2, 0xa0, 0x60, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x99, 0x79, 0x69, 0x9c, 0x20, 0x31, 0x7a, 0x31, 0x7a,
+ 0xb1, 0x9d, 0x7e, 0xe1, 0x80, 0xfd, 0x7c, 0x69, 0x81, 0x39, 0x7b, 0x69, 0x81, 0x09, 0x7b, 0x80,
+ 0x6e, 0xf9, 0x7b, 0x6e, 0x6e, 0x91, 0x09, 0x84, 0x6e, 0x92, 0x6e, 0x92, 0x09, 0x84, 0x92, 0x92,
+ 0xb0, 0x80, 0xc5, 0x81, 0x7d, 0x7f, 0x65, 0x83, 0x99, 0x7e, 0xc5, 0x84, 0x21, 0xd1, 0x85, 0xcd,
+ 0x85, 0x2d, 0x7d, 0xd9, 0x82, 0xe1,
+}
+
+var ActionPanTool = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x66, 0xe9, 0xba,
+ 0xb0, 0x80, 0x69, 0x84, 0x69, 0x7c, 0x90, 0x70, 0x90, 0xe6, 0x69, 0x7f, 0xb0, 0xd9, 0x7d, 0x80,
+ 0xcd, 0x7b, 0x25, 0x7f, 0x4d, 0x7a, 0xa1, 0x7d, 0x00, 0x54, 0xa9, 0x85, 0x90, 0x85, 0x82, 0x8d,
+ 0x7d, 0x99, 0x82, 0x81, 0x7d, 0xb2, 0x71, 0x80, 0xa1, 0x7f, 0xfd, 0x80, 0x6d, 0x7f, 0x95, 0x81,
+ 0x6d, 0x7f, 0x71, 0x80, 0x80, 0xd9, 0x80, 0x21, 0x80, 0x35, 0x81, 0x51, 0x80, 0x15, 0x80, 0x05,
+ 0x80, 0xa1, 0x88, 0xed, 0x84, 0xa1, 0x88, 0xed, 0x84, 0xe8, 0x60, 0xb0, 0x80, 0x59, 0x7e, 0x59,
+ 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe9, 0x9c, 0xe7, 0x84, 0xe8, 0x56,
+ 0xb0, 0x80, 0x59, 0x7e, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe9,
+ 0xa6, 0xe7, 0x84, 0xe8, 0x5a, 0xb0, 0x80, 0x59, 0x7e, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86,
+ 0x59, 0x81, 0x86, 0x86, 0xe9, 0xa2, 0xe7, 0x84, 0xe8, 0x66, 0xb0, 0x80, 0x59, 0x7e, 0x59, 0x81,
+ 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe1,
+}
+
+var ActionPayment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8,
+ 0xe6, 0x60, 0xe8, 0x80, 0xe7, 0xc0, 0xe9, 0x98, 0xe3, 0x80, 0x58, 0xe6, 0x60, 0xe9, 0x78, 0xe7,
+ 0xc0, 0xe9, 0x88, 0xe1,
+}
+
+var ActionPermCameraMic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x64, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x5c, 0xe6, 0x74, 0x20, 0x59, 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0x9c, 0xe9, 0xd1, 0x7b, 0xb0, 0x55, 0x7a, 0x0d, 0x7f, 0x6c, 0x1d, 0x7a, 0x6c, 0x31, 0x74,
+ 0xe7, 0x88, 0xb0, 0x80, 0x69, 0x84, 0x99, 0x83, 0x90, 0x90, 0x90, 0x90, 0x90, 0x69, 0x7c, 0x90,
+ 0x70, 0xe7, 0x88, 0xb0, 0x80, 0xf1, 0x85, 0xad, 0x7b, 0xdd, 0x8a, 0x6c, 0xd1, 0x8b, 0xe8, 0xa4,
+ 0xe7, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x88, 0x84, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88,
+ 0x78, 0x88, 0x90, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81,
+ 0x78, 0x88, 0x78, 0x90, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var ActionPermContactCalendar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0x7c,
+ 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x70, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x64, 0x8c, 0xb1, 0x51, 0x83,
+ 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0x90, 0x74,
+ 0x51, 0x7d, 0x74, 0x74, 0xb0, 0x80, 0xb1, 0x7c, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0xe3, 0x98, 0xb0,
+ 0xe6, 0x68, 0xe9, 0x7c, 0xb0, 0x80, 0x78, 0x90, 0xcd, 0x79, 0x98, 0xcd, 0x79, 0x80, 0x98, 0x8c,
+ 0x98, 0x94, 0xe9, 0x84, 0xe1,
+}
+
+var ActionPermDataSetting = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x8d, 0x7e, 0xb0,
+ 0xb1, 0x80, 0x80, 0x5d, 0x81, 0x11, 0x80, 0x05, 0x82, 0x29, 0x80, 0xe8, 0x50, 0x00, 0x50, 0xa0,
+ 0xe7, 0x21, 0x97, 0xb1, 0xe9, 0x7f, 0x59, 0x7f, 0xd9, 0x7f, 0xb1, 0x7e, 0xd9, 0x7f, 0x7c, 0x80,
+ 0xb9, 0x77, 0xb9, 0x86, 0x62, 0x9e, 0x62, 0xe3, 0x71, 0x87, 0xfd, 0x8f, 0xb1, 0x0d, 0x80, 0xb1,
+ 0x7f, 0x11, 0x80, 0x5d, 0x7f, 0x11, 0x80, 0x05, 0x7f, 0x80, 0xad, 0x7f, 0xf9, 0x7f, 0x59, 0x7f,
+ 0xf1, 0x7f, 0x05, 0x7f, 0x20, 0x1d, 0x82, 0x59, 0x7e, 0xb0, 0x31, 0x80, 0xd9, 0x7f, 0x3d, 0x80,
+ 0x95, 0x7f, 0x21, 0x80, 0x5d, 0x7f, 0x20, 0x7c, 0x8d, 0x7c, 0xb0, 0xe1, 0x7f, 0xc9, 0x7f, 0x9d,
+ 0x7f, 0xb5, 0x7f, 0x65, 0x7f, 0xc9, 0x7f, 0x20, 0x85, 0x7d, 0x82, 0xb0, 0x7d, 0x7f, 0x99, 0x7f,
+ 0xed, 0x7e, 0x45, 0x7f, 0x51, 0x7e, 0x05, 0x7f, 0x20, 0xa1, 0x7f, 0x59, 0x7d, 0xb0, 0xf5, 0x7f,
+ 0xc5, 0x7f, 0xc1, 0x7f, 0x95, 0x7f, 0x81, 0x7f, 0x95, 0x7f, 0xe7, 0x78, 0xb0, 0xc1, 0x7f, 0x80,
+ 0x8d, 0x7f, 0x31, 0x80, 0x85, 0x7f, 0x6d, 0x80, 0x20, 0xa1, 0x7f, 0xa9, 0x82, 0xb0, 0x65, 0x7f,
+ 0x41, 0x80, 0xd5, 0x7e, 0x99, 0x80, 0x51, 0x7e, 0xfd, 0x80, 0x20, 0x85, 0x7d, 0x7e, 0xb0, 0xc5,
+ 0x7f, 0xe9, 0x7f, 0x85, 0x7f, 0x80, 0x65, 0x7f, 0x39, 0x80, 0x20, 0x7c, 0x75, 0x83, 0xb0, 0xe1,
+ 0x7f, 0x39, 0x80, 0xf1, 0x7f, 0x7d, 0x80, 0x21, 0x80, 0xa5, 0x80, 0x20, 0x1d, 0x82, 0xa9, 0x81,
+ 0xb1, 0xf5, 0x7f, 0x51, 0x80, 0xf1, 0x7f, 0xa9, 0x80, 0xf1, 0x7f, 0xfd, 0x80, 0x80, 0x55, 0x80,
+ 0x09, 0x80, 0xa9, 0x80, 0x11, 0x80, 0xfd, 0x80, 0x20, 0xe5, 0x7d, 0xa9, 0x81, 0xb0, 0xd1, 0x7f,
+ 0x29, 0x80, 0xc5, 0x7f, 0x6d, 0x80, 0xe1, 0x7f, 0xa5, 0x80, 0x20, 0x84, 0x75, 0x83, 0xb0, 0x21,
+ 0x80, 0x39, 0x80, 0x65, 0x80, 0x4d, 0x80, 0x9d, 0x80, 0x39, 0x80, 0x20, 0x7d, 0x82, 0x7e, 0xb0,
+ 0x85, 0x80, 0x69, 0x80, 0x15, 0x81, 0xbd, 0x80, 0xb1, 0x81, 0xfd, 0x80, 0x20, 0x61, 0x80, 0xa9,
+ 0x82, 0xb0, 0x0d, 0x80, 0x3d, 0x80, 0x41, 0x80, 0x6d, 0x80, 0x7d, 0x80, 0x6d, 0x80, 0xe7, 0x88,
+ 0xb0, 0x41, 0x80, 0x80, 0x75, 0x80, 0xd1, 0x7f, 0x7d, 0x80, 0x95, 0x7f, 0x20, 0x61, 0x80, 0x59,
+ 0x7d, 0xb0, 0x9d, 0x80, 0xc1, 0x7f, 0x2d, 0x81, 0x69, 0x7f, 0xb1, 0x81, 0x05, 0x7f, 0x20, 0x7d,
+ 0x82, 0x82, 0xb0, 0x3d, 0x80, 0x19, 0x80, 0x7d, 0x80, 0x80, 0x9d, 0x80, 0xc9, 0x7f, 0x20, 0x84,
+ 0x8d, 0x7c, 0xb0, 0x21, 0x80, 0xc9, 0x7f, 0x11, 0x80, 0x85, 0x7f, 0xe1, 0x7f, 0x5d, 0x7f, 0x20,
+ 0xe9, 0x7d, 0x59, 0x7e, 0xe2, 0xfd, 0x8d, 0xa2, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a,
+ 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a,
+ 0x86, 0xe1,
+}
+
+var ActionPermDeviceInformation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x98, 0xe7, 0x88, 0xe8,
+ 0x7c, 0xe3, 0x90, 0x05, 0x6c, 0x00, 0x6c, 0x54, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xc8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05,
+ 0x7c, 0x78, 0x05, 0x7c, 0xe2, 0x94, 0x9c, 0xe6, 0x6c, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe1,
+}
+
+var ActionPermIdentity = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xcd, 0x73, 0xb0,
+ 0x51, 0x82, 0x80, 0x35, 0x84, 0xe1, 0x81, 0x35, 0x84, 0x35, 0x84, 0x92, 0x21, 0x7e, 0x35, 0x84,
+ 0xcd, 0x7b, 0x35, 0x84, 0xcd, 0x7b, 0x21, 0x7e, 0xcd, 0x7b, 0xcd, 0x7b, 0xe1, 0x81, 0xcd, 0x7b,
+ 0x35, 0x84, 0xcd, 0x7b, 0xe3, 0x80, 0xa4, 0xb0, 0xf5, 0x85, 0x80, 0x35, 0x8c, 0xe9, 0x82, 0x35,
+ 0x8c, 0x35, 0x84, 0xe9, 0x35, 0x82, 0xe6, 0xcd, 0x73, 0xe8, 0x94, 0xb0, 0x80, 0xb5, 0x7e, 0x41,
+ 0x86, 0xcd, 0x7b, 0x35, 0x8c, 0xcd, 0x7b, 0xe2, 0x80, 0x60, 0xb1, 0x95, 0x7b, 0x80, 0x70, 0x95,
+ 0x83, 0x70, 0x90, 0x80, 0x69, 0x84, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x90, 0x69, 0x7c, 0x90,
+ 0x70, 0xb0, 0x80, 0x95, 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe3, 0x80, 0xa4, 0xb0, 0xad, 0x7a,
+ 0x80, 0x60, 0xad, 0x82, 0x60, 0x90, 0xe9, 0x8c, 0xe7, 0xc0, 0xe9, 0x74, 0xb0, 0x80, 0xad, 0x7a,
+ 0x55, 0x75, 0x70, 0x60, 0x70, 0xe1,
+}
+
+var ActionPermMedia = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x68, 0xe6, 0x50,
+ 0xe9, 0x94, 0xe7, 0x05, 0x80, 0x00, 0x50, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x58, 0xe8, 0x68, 0xe3, 0xd0, 0x78, 0xe6, 0x88, 0x20, 0x78,
+ 0x78, 0xe6, 0x68, 0xa0, 0xcd, 0x71, 0x58, 0x05, 0x70, 0xcd, 0x6d, 0x05, 0x70, 0x60, 0x00, 0x60,
+ 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x6c, 0x8c, 0x21, 0x92, 0x68, 0x8e, 0x05, 0x89, 0x00, 0x96, 0x7a, 0x20, 0x8e, 0x92, 0xe6,
+ 0x6c, 0xe1,
+}
+
+var ActionPermPhoneMsg = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x8e, 0xb1, 0x85,
+ 0x7d, 0x80, 0x19, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9, 0x7f, 0x85, 0x7e,
+ 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x55, 0x7a, 0x21, 0x7d,
+ 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x95, 0x7b, 0xb0, 0x8d, 0x80,
+ 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79, 0xe9, 0x74, 0x72,
+ 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb2,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f, 0xc4, 0xc4, 0xc4,
+ 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe2, 0x80, 0x5c, 0xe9, 0xa8, 0x20, 0x8c, 0x74, 0xe7, 0x98, 0xe8, 0x5c, 0xe6,
+ 0x80, 0xe1,
+}
+
+var ActionPermScanWiFi = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x5c, 0xa0, 0xe9,
+ 0x75, 0x5c, 0x4d, 0x6e, 0xb5, 0x71, 0x50, 0x79, 0x76, 0x00, 0x80, 0xa8, 0x20, 0xb0, 0x81, 0x62,
+ 0xa0, 0xb5, 0x91, 0xbd, 0x71, 0x19, 0x8a, 0x5c, 0x80, 0x5c, 0xe3, 0x84, 0xb4, 0xe7, 0x78, 0xe8,
+ 0x78, 0xe7, 0x88, 0xe9, 0x98, 0xe3, 0x78, 0x60, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78,
+ 0xe1,
+}
+
+var ActionPets = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb1, 0x8a, 0xb9, 0x85,
+ 0xb3, 0x41, 0x7e, 0xf9, 0x7d, 0xcd, 0x7c, 0x39, 0x7c, 0x0d, 0x7b, 0x31, 0x7a, 0x11, 0x7f, 0xed,
+ 0x7e, 0xe9, 0x7d, 0xd5, 0x7d, 0x85, 0x7c, 0x5d, 0x7d, 0xcd, 0x7f, 0xf1, 0x7f, 0x91, 0x7f, 0xe1,
+ 0x7f, 0x59, 0x7f, 0xd5, 0x7f, 0x7d, 0x7f, 0xe9, 0x7f, 0xf5, 0x7e, 0xe9, 0x7f, 0x71, 0x7e, 0xe9,
+ 0x7f, 0x90, 0xf1, 0x7e, 0x80, 0x71, 0x7e, 0x19, 0x80, 0xb6, 0xc9, 0x7f, 0x0d, 0x80, 0x91, 0x7f,
+ 0x19, 0x80, 0x59, 0x7f, 0x2d, 0x80, 0x9d, 0x7e, 0x79, 0x80, 0x71, 0x7d, 0x91, 0x81, 0x85, 0x7c,
+ 0xa5, 0x82, 0x41, 0x7e, 0x09, 0x82, 0xcd, 0x7c, 0xc9, 0x83, 0x0d, 0x7b, 0xd1, 0x85, 0x61, 0x7d,
+ 0x9d, 0x82, 0x2d, 0x7a, 0x85, 0x85, 0xc1, 0x7a, 0x99, 0x89, 0x95, 0x80, 0x09, 0x82, 0x0d, 0x82,
+ 0x11, 0x84, 0xad, 0x84, 0xa9, 0x84, 0x75, 0x81, 0x4d, 0x80, 0x21, 0x86, 0x21, 0x7f, 0x15, 0x8b,
+ 0x21, 0x7f, 0x11, 0x80, 0x80, 0x21, 0x80, 0x05, 0x80, 0x31, 0x80, 0x05, 0x80, 0x90, 0x21, 0x80,
+ 0xfd, 0x7f, 0x31, 0x80, 0xfd, 0x7f, 0xb2, 0xf5, 0x84, 0x80, 0xa1, 0x89, 0x29, 0x81, 0x15, 0x8b,
+ 0xe1, 0x80, 0xa1, 0x82, 0x6d, 0x7f, 0x15, 0x84, 0x65, 0x7d, 0xad, 0x84, 0x59, 0x7b, 0x95, 0x80,
+ 0xf1, 0x7b, 0x61, 0x7d, 0x05, 0x79, 0xc1, 0x7a, 0x69, 0x76, 0xe2, 0x58, 0x76, 0xd1, 0x8a, 0x8a,
+ 0x00, 0x04, 0x94, 0x80, 0x8a, 0x8a, 0x00, 0x04, 0x6c, 0x80, 0xe2, 0x6a, 0x66, 0xd1, 0x8a, 0x8a,
+ 0x00, 0x04, 0x94, 0x80, 0x8a, 0x8a, 0x00, 0x04, 0x6c, 0x80, 0xe2, 0x82, 0x66, 0xd1, 0x8a, 0x8a,
+ 0x00, 0x04, 0x94, 0x80, 0x8a, 0x8a, 0x00, 0x04, 0x6c, 0x80, 0xe2, 0x94, 0x76, 0xd1, 0x8a, 0x8a,
+ 0x00, 0x04, 0x94, 0x80, 0x8a, 0x8a, 0x00, 0x04, 0x6c, 0x80, 0xe1,
+}
+
+var ActionPictureInPicture = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x6c, 0xe6, 0x7c,
+ 0xe9, 0x98, 0xe7, 0xa0, 0xe8, 0x6c, 0xe3, 0x88, 0x70, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0xf5, 0x83, 0x88, 0xf5,
+ 0x83, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x3d, 0x7e, 0x88, 0x0d, 0x7c, 0xe8, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x09, 0xa0, 0xe6, 0x5c, 0xe8, 0xf9,
+ 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe1,
+}
+
+var ActionPictureInPictureAlt = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x7c, 0xe6, 0x7c,
+ 0xe9, 0xfd, 0x8b, 0xe7, 0xa0, 0xe8, 0x7c, 0xe3, 0x90, 0xa0, 0xe8, 0xf5, 0x71, 0xa0, 0xac, 0xc5,
+ 0x6f, 0x35, 0x94, 0x5c, 0xa4, 0x5c, 0xe6, 0x5c, 0xa0, 0xcd, 0x6b, 0x5c, 0x54, 0xc5, 0x6f, 0x54,
+ 0xf5, 0x71, 0xe8, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0,
+ 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe3, 0x78, 0x0d, 0x80, 0xe6, 0x5c, 0xe8, 0xf1,
+ 0x71, 0xe7, 0xc8, 0xe9, 0x19, 0x9c, 0xe1,
+}
+
+var ActionPlayForWork = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x64, 0xe9, 0x2d,
+ 0x8b, 0xe7, 0x72, 0x21, 0x92, 0x92, 0x92, 0x6e, 0xe7, 0x72, 0xe8, 0x64, 0xe7, 0x78, 0xe2, 0x68,
+ 0x88, 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0x90, 0x98, 0xa1, 0x7a, 0x98, 0x68,
+ 0xe7, 0x78, 0xb0, 0x80, 0x6d, 0x84, 0x6d, 0x7c, 0x90, 0x70, 0x90, 0x90, 0x70, 0x6d, 0x7c, 0x70,
+ 0x70, 0xe7, 0x78, 0xe1,
+}
+
+var ActionPolymer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x60, 0xe7, 0x70,
+ 0x01, 0x35, 0x76, 0x45, 0x89, 0x62, 0x80, 0x20, 0x92, 0x60, 0xe7, 0x70, 0x00, 0x52, 0x80, 0x20,
+ 0x92, 0xa0, 0xe7, 0x90, 0x20, 0xcd, 0x8f, 0xbd, 0x66, 0x00, 0x9e, 0x80, 0x20, 0x6e, 0xa0, 0xe7,
+ 0x90, 0x20, 0x92, 0x60, 0xe1,
+}
+
+var ActionPowerSettingsNew = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x5c, 0xe7, 0x78,
+ 0xe9, 0xa8, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0xad, 0x89, 0x55, 0x84, 0x20, 0x2d, 0x7d, 0xd5, 0x82,
+ 0xa0, 0xfd, 0x8b, 0xbd, 0x77, 0x9c, 0xa1, 0x7b, 0x9c, 0x80, 0xb0, 0x80, 0xbd, 0x87, 0xbd, 0x79,
+ 0x9c, 0x64, 0x9c, 0x90, 0x64, 0xbd, 0x79, 0x64, 0x64, 0xb0, 0x80, 0xa1, 0x7b, 0x05, 0x82, 0xbd,
+ 0x77, 0x29, 0x85, 0x29, 0x75, 0x20, 0x2d, 0x7d, 0x2d, 0x7d, 0xa0, 0x79, 0x70, 0xa1, 0x75, 0x5c,
+ 0x85, 0x7a, 0x5c, 0x80, 0xb0, 0x80, 0xf1, 0x89, 0x11, 0x88, 0xa4, 0xa4, 0xa4, 0x90, 0xa4, 0xf1,
+ 0x77, 0xa4, 0x5c, 0xb0, 0x80, 0x85, 0x7a, 0x89, 0x7d, 0xa1, 0x75, 0xad, 0x79, 0x55, 0x72, 0xe1,
+}
+
+var ActionPregnantWoman = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x60, 0xb0, 0x80,
+ 0xc9, 0x7d, 0xc9, 0x81, 0x78, 0x88, 0x78, 0x92, 0x88, 0xc9, 0x81, 0x88, 0x88, 0x39, 0x7e, 0x88,
+ 0x78, 0x88, 0x78, 0x39, 0x7e, 0x78, 0x78, 0xe3, 0x9c, 0xa4, 0xb1, 0xfd, 0x7f, 0x51, 0x7d, 0x59,
+ 0x7e, 0xfd, 0x7a, 0x78, 0x74, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x90, 0x74, 0xb1,
+ 0x82, 0x74, 0x8c, 0xe9, 0x9c, 0xe7, 0x88, 0xe9, 0x94, 0xe7, 0x8c, 0xe8, 0x94, 0xe7, 0x8c, 0xe9,
+ 0x70, 0xe1,
+}
+
+var ActionPrint = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x70, 0xe6, 0x64,
+ 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x98, 0xe7, 0x90, 0xe9, 0x90, 0xe7,
+ 0xb0, 0xe9, 0x70, 0xe7, 0x90, 0xe8, 0x7c, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74,
+ 0xe3, 0x74, 0xac, 0xe6, 0x70, 0xe8, 0x88, 0xe7, 0xa0, 0xe9, 0x94, 0xe3, 0x8c, 0x64, 0xb0, 0xe5,
+ 0x7e, 0x80, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0x90, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xb0, 0x1d, 0x81,
+ 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x90, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe2, 0x98, 0x5c, 0xe6,
+ 0x68, 0xe9, 0x90, 0xe7, 0xb0, 0xe8, 0x5c, 0xe1,
+}
+
+var ActionQueryBuilder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2,
+ 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80,
+ 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x82, 0x4c, 0xe7,
+ 0x7a, 0xe9, 0x98, 0x20, 0x7d, 0x8a, 0x4d, 0x86, 0x00, 0x94, 0xd9, 0x85, 0x20, 0x6e, 0xa9, 0x7a,
+ 0xe1,
+}
+
+var ActionQuestionAnswer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x68, 0xe7, 0x78,
+ 0xe9, 0xa4, 0xe6, 0x68, 0xe9, 0x88, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xac, 0x20, 0x90, 0x90, 0xe8, 0x6c, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3,
+ 0x70, 0x98, 0xe8, 0x5c, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x5c, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb8, 0x20, 0x90, 0x70, 0xe7, 0xa8, 0xb0,
+ 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe1,
+}
+
+var ActionReceipt = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x94, 0xe6, 0x68,
+ 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9,
+ 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe2, 0x5c, 0xa8, 0x2b,
+ 0x86, 0x7a, 0x86, 0x86, 0x86, 0x7a, 0x86, 0x86, 0x86, 0x7a, 0x86, 0x86, 0x86, 0x7a, 0x86, 0x86,
+ 0x86, 0x7a, 0x86, 0x86, 0x86, 0x7a, 0x86, 0x86, 0xe8, 0x58, 0x2b, 0x7a, 0x86, 0x7a, 0x7a, 0x7a,
+ 0x86, 0x7a, 0x7a, 0x7a, 0x86, 0x7a, 0x7a, 0x7a, 0x86, 0x7a, 0x7a, 0x7a, 0x86, 0x7a, 0x7a, 0x7a,
+ 0x86, 0x7a, 0x7a, 0xe9, 0xd0, 0xe1,
+}
+
+var ActionRecordVoiceOver = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x8c, 0xb0, 0xa9,
+ 0x7a, 0x80, 0x60, 0xb1, 0x82, 0x60, 0x90, 0xe9, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xb0, 0x80, 0xb1,
+ 0x7a, 0x59, 0x75, 0x70, 0x60, 0x70, 0xe3, 0x85, 0x8f, 0xbd, 0x6c, 0x20, 0xa1, 0x7c, 0x61, 0x83,
+ 0xb0, 0xb1, 0x81, 0x61, 0x82, 0xb1, 0x81, 0x69, 0x85, 0x80, 0xc9, 0x87, 0x20, 0x61, 0x83, 0x61,
+ 0x83, 0xb0, 0x0d, 0x84, 0xf1, 0x7b, 0x0d, 0x84, 0xd9, 0x75, 0x80, 0x75, 0x71, 0xe2, 0x29, 0x90,
+ 0x58, 0x20, 0xbd, 0x7c, 0x45, 0x83, 0xb0, 0x8d, 0x85, 0x0d, 0x86, 0x8d, 0x85, 0x1d, 0x8f, 0xfd,
+ 0x7f, 0x79, 0x95, 0x00, 0x29, 0x90, 0x90, 0xb0, 0xcd, 0x87, 0x3d, 0x78, 0xcd, 0x87, 0x19, 0x6c,
+ 0x80, 0x48, 0xe2, 0x64, 0x74, 0xd1, 0x90, 0x90, 0x00, 0x04, 0xa0, 0x80, 0x90, 0x90, 0x00, 0x04,
+ 0x60, 0x80, 0xe1,
+}
+
+var ActionRedeem = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe7, 0xa1,
+ 0x7b, 0xb2, 0x39, 0x80, 0x61, 0x7f, 0x61, 0x80, 0xb5, 0x7e, 0x61, 0x80, 0x7c, 0x80, 0xb1, 0x7c,
+ 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe9, 0x7d, 0x80, 0x11, 0x7c, 0x11, 0x81, 0x76, 0xb1, 0x82, 0x21,
+ 0x7e, 0x5d, 0x81, 0x7e, 0xa5, 0x7e, 0xa0, 0xf1, 0x7d, 0x11, 0x6d, 0x19, 0x7c, 0x58, 0x74, 0x58,
+ 0xb1, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x80, 0xb5, 0x80, 0x25, 0x80, 0x61, 0x81,
+ 0x61, 0x80, 0x84, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x58, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe2, 0x8c, 0x60, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19,
+ 0x7f, 0x84, 0x7c, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe2, 0x74,
+ 0x60, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84, 0x7c, 0x84,
+ 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0xac, 0xbc, 0xe6, 0x60, 0xe9,
+ 0x78, 0xe7, 0xc0, 0xe9, 0x88, 0xe3, 0x80, 0x6c, 0xe6, 0x60, 0xe8, 0x70, 0xe7, 0x29, 0x8a, 0x02,
+ 0x6c, 0xad, 0x7d, 0x41, 0x79, 0x80, 0x7c, 0x89, 0x79, 0x21, 0x84, 0x49, 0x7d, 0x84, 0xb9, 0x82,
+ 0x02, 0xc1, 0x86, 0x80, 0x94, 0xad, 0x7d, 0xd9, 0x85, 0x70, 0xe6, 0xa0, 0xe9, 0x98, 0xe1,
+}
+
+var ActionRemoveShoppingCart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x75, 0x95, 0x75, 0x95,
+ 0x03, 0x8d, 0x6d, 0x8d, 0x6d, 0x58, 0x58, 0x8d, 0x6a, 0x8d, 0x6a, 0x50, 0x15, 0x6d, 0x22, 0xc9,
+ 0x88, 0xc9, 0x88, 0x6d, 0x84, 0x51, 0x89, 0x4d, 0x7d, 0xe9, 0x84, 0xb1, 0xb1, 0x7f, 0x91, 0x80,
+ 0x81, 0x7f, 0x39, 0x81, 0x81, 0x7f, 0xed, 0x81, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xed, 0x8e, 0x20, 0xc5, 0x82, 0xc5, 0x82, 0xb2, 0x7e, 0xbd, 0x80, 0x59, 0x7e, 0xe9, 0x81,
+ 0x59, 0x7e, 0x3d, 0x83, 0x80, 0x35, 0x82, 0xc9, 0x81, 0x88, 0xfd, 0x83, 0x88, 0x55, 0x81, 0x80,
+ 0x85, 0x82, 0x55, 0x7f, 0x3d, 0x83, 0x51, 0x7e, 0x00, 0xed, 0x92, 0xb0, 0x20, 0x8d, 0x82, 0x75,
+ 0x7d, 0xe2, 0xd9, 0x76, 0x8c, 0xb0, 0xb9, 0x7f, 0x80, 0x81, 0x7f, 0xc9, 0x7f, 0x81, 0x7f, 0x81,
+ 0x7f, 0x20, 0x11, 0x80, 0xc5, 0x7f, 0x00, 0x35, 0x78, 0x84, 0xe7, 0xb9, 0x84, 0x20, 0x88, 0x88,
+ 0xe6, 0xd9, 0x76, 0xe3, 0x45, 0x90, 0x78, 0xb0, 0x81, 0x81, 0x80, 0xd1, 0x82, 0x31, 0x7f, 0x81,
+ 0x83, 0xf1, 0x7d, 0x20, 0x29, 0x87, 0x05, 0x73, 0xb1, 0x29, 0x80, 0xb9, 0x7f, 0x3d, 0x80, 0x61,
+ 0x7f, 0x3d, 0x80, 0x0d, 0x7f, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x15, 0x75,
+ 0x20, 0xa4, 0xa4, 0xe7, 0x05, 0x80, 0xe2, 0x6c, 0x98, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd,
+ 0x81, 0x05, 0x7c, 0x88, 0x92, 0xc9, 0x81, 0x88, 0xfd, 0x83, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ActionReorder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x8c, 0xe7, 0xc8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x60, 0xe7, 0xc8, 0xe8, 0x74, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe9,
+ 0x88, 0xe7, 0xc8, 0xe8, 0x64, 0xe6, 0x5c, 0xe1,
+}
+
+var ActionReportProblem = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0xa4, 0xe7, 0xd8,
+ 0x01, 0x80, 0x58, 0x54, 0xa4, 0xe3, 0xb0, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var ActionRestore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x81, 0x5c, 0xa0,
+ 0x0d, 0x78, 0x5c, 0x60, 0x11, 0x76, 0x60, 0x80, 0xe6, 0x54, 0x21, 0xcd, 0x87, 0xcd, 0x87, 0x25,
+ 0x80, 0x4d, 0x80, 0x00, 0x74, 0x80, 0xe7, 0x74, 0xb0, 0x80, 0x45, 0x78, 0x45, 0x86, 0x64, 0x9c,
+ 0x64, 0x91, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xb0, 0x21, 0x7c, 0x80,
+ 0xa5, 0x78, 0x6d, 0x7e, 0x1d, 0x76, 0xe5, 0x7b, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0xa1, 0x89, 0x78,
+ 0xfd, 0x8f, 0x05, 0x7d, 0xa4, 0xfd, 0x81, 0xa4, 0xf1, 0x8b, 0xa4, 0xa8, 0xf1, 0x89, 0xa8, 0x80,
+ 0x80, 0xf1, 0x8b, 0x5c, 0xfd, 0x81, 0x5c, 0xe2, 0x80, 0x70, 0xe9, 0x94, 0x20, 0x91, 0x88, 0x15,
+ 0x85, 0x00, 0x94, 0xa9, 0x84, 0x20, 0x72, 0xd9, 0x7b, 0xe8, 0x70, 0xe7, 0x7a, 0xe1,
+}
+
+var ActionRestorePage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x58, 0xe6, 0x68,
+ 0xa0, 0xcd, 0x71, 0x58, 0x05, 0x70, 0xcd, 0x6d, 0x05, 0x70, 0x60, 0x00, 0x60, 0xa0, 0xb0, 0x80,
+ 0x35, 0x82, 0xc9, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x70, 0x00, 0x88, 0x58, 0xe3, 0x78, 0xc0, 0xb0, 0xe9, 0x7b, 0x80, 0x65,
+ 0x78, 0x89, 0x7d, 0xd9, 0x76, 0x74, 0xe7, 0x6d, 0x83, 0xb1, 0x45, 0x81, 0xd1, 0x81, 0x5d, 0x83,
+ 0x86, 0xbd, 0x85, 0x86, 0xe1, 0x83, 0x80, 0x8e, 0xe1, 0x7c, 0x8e, 0x72, 0x90, 0xe1, 0x7c, 0x72,
+ 0x72, 0x72, 0xb0, 0x4d, 0x7d, 0x80, 0xf9, 0x7a, 0x8d, 0x81, 0xd1, 0x79, 0xd1, 0x83, 0x00, 0x7a,
+ 0x84, 0xe7, 0x70, 0xe9, 0x70, 0x20, 0x99, 0x82, 0x99, 0x82, 0xb1, 0xc9, 0x81, 0x3d, 0x7d, 0xe1,
+ 0x84, 0x69, 0x7b, 0x69, 0x88, 0x69, 0x7b, 0x85, 0x85, 0x80, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x90,
+ 0x85, 0x7b, 0x94, 0x6c, 0x94, 0xe1,
+}
+
+var ActionRoom = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb1, 0x45,
+ 0x78, 0x80, 0x64, 0x45, 0x86, 0x64, 0x9c, 0x80, 0x81, 0x8a, 0x9c, 0xb4, 0x9c, 0xb4, 0x90, 0x9c,
+ 0x81, 0x70, 0x9c, 0x4c, 0xb0, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0xe3, 0x80, 0xa6,
+ 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x92, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x8a,
+ 0x3d, 0x82, 0x8a, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x8a, 0xe1,
+}
+
+var ActionRoundedCorner = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x9c, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe2, 0x5c, 0x84, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xa0, 0xc0, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90,
+ 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x60, 0x80, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3,
+ 0xc8, 0x4c, 0xb0, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0xe6, 0x7c, 0xe9, 0x88, 0xe7,
+ 0x94, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x70,
+ 0xe1,
+}
+
+var ActionRowing = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x72, 0x8a, 0x22, 0x6e,
+ 0x92, 0x86, 0x86, 0x8e, 0x72, 0xe7, 0x88, 0x20, 0x76, 0x76, 0xe2, 0x8c, 0x54, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x98, 0x05, 0xa8, 0x00, 0x98, 0xb0, 0x20, 0x05, 0x7a,
+ 0xfd, 0x79, 0xe8, 0x9e, 0x20, 0xcd, 0x71, 0xd1, 0x71, 0xb0, 0x61, 0x7f, 0x19, 0x80, 0xc9, 0x7e,
+ 0x25, 0x80, 0x31, 0x7e, 0x25, 0x80, 0xe9, 0xb1, 0x7b, 0xb0, 0x51, 0x83, 0x11, 0x80, 0x39, 0x87,
+ 0x45, 0x7e, 0x59, 0x89, 0xed, 0x7b, 0x20, 0xcd, 0x82, 0xe9, 0x7c, 0xb1, 0x61, 0x80, 0x95, 0x7f,
+ 0xdd, 0x80, 0x3d, 0x7f, 0x61, 0x81, 0x7e, 0x95, 0x80, 0xb9, 0x7f, 0x3d, 0x81, 0x8d, 0x7f, 0xed,
+ 0x81, 0x8d, 0x7f, 0xe7, 0x11, 0x80, 0xb0, 0x7d, 0x82, 0x05, 0x80, 0x81, 0x84, 0x0d, 0x82, 0x81,
+ 0x84, 0x85, 0x84, 0xe9, 0x81, 0x8b, 0xb0, 0x80, 0xb1, 0x81, 0x4d, 0x7f, 0x39, 0x83, 0x29, 0x7e,
+ 0x51, 0x84, 0x00, 0x82, 0x31, 0x81, 0xe9, 0x75, 0x7b, 0xb0, 0xbd, 0x7e, 0x0d, 0x81, 0x25, 0x7d,
+ 0x0d, 0x82, 0x6d, 0x7b, 0xc9, 0x82, 0x00, 0x92, 0x98, 0xe7, 0x86, 0x20, 0x8c, 0x05, 0x86, 0xe1,
+}
+
+var ActionSchedule = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2,
+ 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80,
+ 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x82, 0x4c, 0xe7,
+ 0x7a, 0xe9, 0x98, 0x20, 0x7d, 0x8a, 0x4d, 0x86, 0x00, 0x94, 0xd9, 0x85, 0x20, 0x6e, 0xa9, 0x7a,
+ 0xe1,
+}
+
+var ActionSearch = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x88, 0xe7, 0x69,
+ 0x7e, 0x20, 0x75, 0x7f, 0x75, 0x7f, 0xa0, 0xd1, 0x86, 0x31, 0x81, 0x90, 0x3d, 0x7e, 0x90, 0x76,
+ 0xb0, 0x80, 0xd1, 0x78, 0x31, 0x7a, 0x66, 0x66, 0x66, 0x80, 0x5c, 0xd1, 0x73, 0x5c, 0x76, 0x90,
+ 0xd1, 0x85, 0x9a, 0x9a, 0x9a, 0xb0, 0x3d, 0x83, 0x80, 0x31, 0x86, 0xd1, 0x7e, 0x75, 0x88, 0xe1,
+ 0x7c, 0x20, 0x8d, 0x80, 0x8d, 0x80, 0xe8, 0x8e, 0x20, 0x94, 0xfd, 0x89, 0x01, 0xfd, 0x90, 0x9c,
+ 0x8e, 0x88, 0xe3, 0x68, 0x80, 0xb0, 0x09, 0x7b, 0x80, 0x6e, 0xf9, 0x7b, 0x6e, 0x6e, 0x92, 0x09,
+ 0x84, 0x6e, 0x92, 0x6e, 0x92, 0x09, 0x84, 0x92, 0x92, 0xf9, 0x7b, 0x92, 0x6e, 0x92, 0xe1,
+}
+
+var ActionSettings = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xdd, 0x8e, 0xf5, 0x81,
+ 0xb0, 0x15, 0x80, 0x5d, 0x7f, 0x25, 0x80, 0xb5, 0x7e, 0x25, 0x80, 0x0d, 0x7e, 0x90, 0xf1, 0x7f,
+ 0xb1, 0x7e, 0xdd, 0x7f, 0x0d, 0x7e, 0x20, 0x3d, 0x84, 0xb1, 0x7c, 0xb0, 0x61, 0x80, 0xb5, 0x7f,
+ 0x7d, 0x80, 0x29, 0x7f, 0x3d, 0x80, 0xb9, 0x7e, 0x20, 0x78, 0x11, 0x79, 0xb0, 0xc1, 0x7f, 0x91,
+ 0x7f, 0x3d, 0x7f, 0x65, 0x7f, 0xc9, 0x7e, 0x91, 0x7f, 0x20, 0x05, 0x7b, 0x05, 0x82, 0xb0, 0xf9,
+ 0x7e, 0x35, 0x7f, 0xd9, 0x7d, 0x8d, 0x7e, 0xa1, 0x7c, 0x09, 0x7e, 0x00, 0x8a, 0xd9, 0x6c, 0xb0,
+ 0xe9, 0x7f, 0x89, 0x7f, 0x81, 0x7f, 0x29, 0x7f, 0x7e, 0x29, 0x7f, 0xe7, 0x70, 0xb0, 0x81, 0x7f,
+ 0x80, 0x19, 0x7f, 0x61, 0x80, 0x05, 0x7f, 0xd9, 0x80, 0x20, 0x41, 0x7f, 0x4d, 0x85, 0xb0, 0xc9,
+ 0x7e, 0x85, 0x80, 0xa9, 0x7d, 0x2d, 0x81, 0xa1, 0x7c, 0xf9, 0x81, 0x00, 0xe9, 0x71, 0x19, 0x72,
+ 0xb0, 0x8d, 0x7f, 0xd5, 0x7f, 0x09, 0x7f, 0x80, 0xc9, 0x7e, 0x71, 0x80, 0x20, 0x78, 0xf1, 0x86,
+ 0xb0, 0xc1, 0x7f, 0x71, 0x80, 0xdd, 0x7f, 0xf9, 0x80, 0x3d, 0x80, 0x49, 0x81, 0x20, 0x39, 0x84,
+ 0x51, 0x83, 0xa0, 0x11, 0x71, 0xb1, 0x7e, 0x62, 0x59, 0x7f, 0x62, 0x80, 0x90, 0x11, 0x80, 0x51,
+ 0x81, 0x25, 0x80, 0xf5, 0x81, 0x20, 0xc9, 0x7b, 0x51, 0x83, 0xb0, 0xa1, 0x7f, 0x4d, 0x80, 0x85,
+ 0x7f, 0xd9, 0x80, 0xc5, 0x7f, 0x49, 0x81, 0x20, 0x88, 0xf1, 0x86, 0xb0, 0x41, 0x80, 0x71, 0x80,
+ 0xc5, 0x80, 0x9d, 0x80, 0x39, 0x81, 0x71, 0x80, 0x20, 0xfd, 0x84, 0xfd, 0x7d, 0xb0, 0x09, 0x81,
+ 0xcd, 0x80, 0x29, 0x82, 0x75, 0x81, 0x61, 0x83, 0xf9, 0x81, 0x20, 0xc1, 0x80, 0x4d, 0x85, 0xb0,
+ 0x15, 0x80, 0x79, 0x80, 0x7d, 0x80, 0xd9, 0x80, 0xfd, 0x80, 0xd9, 0x80, 0xe7, 0x90, 0xb0, 0x81,
+ 0x80, 0x80, 0xe9, 0x80, 0xa1, 0x7f, 0xfd, 0x80, 0x29, 0x7f, 0x20, 0xc1, 0x80, 0xb5, 0x7a, 0xb0,
+ 0x39, 0x81, 0x7d, 0x7f, 0x59, 0x82, 0xd5, 0x7e, 0x61, 0x83, 0x09, 0x7e, 0x20, 0xfd, 0x84, 0x05,
+ 0x82, 0xb0, 0x75, 0x80, 0x2d, 0x80, 0xf9, 0x80, 0x80, 0x39, 0x81, 0x91, 0x7f, 0x20, 0x88, 0x11,
+ 0x79, 0xb0, 0x41, 0x80, 0x91, 0x7f, 0x25, 0x80, 0x09, 0x7f, 0xc5, 0x7f, 0xb9, 0x7e, 0x20, 0xc9,
+ 0x7b, 0xb1, 0x7c, 0xe2, 0x80, 0x8e, 0xb0, 0x21, 0x7c, 0x80, 0x72, 0xe1, 0x7c, 0x72, 0x72, 0x92,
+ 0x21, 0x83, 0x72, 0x8e, 0x72, 0x8e, 0x21, 0x83, 0x8e, 0x8e, 0xe1, 0x7c, 0x8e, 0x72, 0x8e, 0xe1,
+}
+
+var ActionSettingsApplications = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x78, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x9c, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x81, 0x7c, 0xa4, 0xb0, 0x80, 0x75, 0x80, 0xf5, 0x7f,
+ 0xed, 0x80, 0xe9, 0x7f, 0x61, 0x81, 0x20, 0xf5, 0x82, 0x51, 0x82, 0xb0, 0x45, 0x80, 0x35, 0x80,
+ 0x59, 0x80, 0x99, 0x80, 0x29, 0x80, 0xe5, 0x80, 0x20, 0x35, 0x7d, 0xd9, 0x84, 0xb0, 0xd5, 0x7f,
+ 0x4d, 0x80, 0x75, 0x7f, 0x6d, 0x80, 0x25, 0x7f, 0x4d, 0x80, 0x20, 0x85, 0x7c, 0x99, 0x7e, 0xb0,
+ 0x49, 0x7f, 0x91, 0x80, 0x7d, 0x7e, 0x05, 0x81, 0xa1, 0x7d, 0x61, 0x81, 0x20, 0x7d, 0x7f, 0xb5,
+ 0x83, 0xb0, 0xf5, 0x7f, 0x55, 0x80, 0xad, 0x7f, 0x99, 0x80, 0x51, 0x7f, 0x99, 0x80, 0xe7, 0x69,
+ 0x7a, 0xb0, 0xa9, 0x7f, 0x80, 0x5d, 0x7f, 0xbd, 0x7f, 0x51, 0x7f, 0x69, 0x7f, 0x20, 0x7d, 0x7f,
+ 0x4d, 0x7c, 0xb0, 0x29, 0x7f, 0xa9, 0x7f, 0x5d, 0x7e, 0x31, 0x7f, 0xa1, 0x7d, 0xa1, 0x7e, 0x20,
+ 0x85, 0x7c, 0x69, 0x81, 0xb0, 0xb1, 0x7f, 0x21, 0x80, 0x51, 0x7f, 0x80, 0x25, 0x7f, 0xb5, 0x7f,
+ 0x20, 0x35, 0x7d, 0x29, 0x7b, 0xb0, 0xd1, 0x7f, 0xb5, 0x7f, 0xe9, 0x7f, 0x51, 0x7f, 0x29, 0x80,
+ 0x1d, 0x7f, 0x20, 0xf5, 0x82, 0xb1, 0x7d, 0xb1, 0xf1, 0x7f, 0x8d, 0x7f, 0xe9, 0x7f, 0x19, 0x7f,
+ 0xe9, 0x7f, 0xa1, 0x7e, 0x80, 0x8d, 0x7f, 0x0d, 0x80, 0x15, 0x7f, 0x19, 0x80, 0xa1, 0x7e, 0x20,
+ 0x0d, 0x7d, 0xb1, 0x7d, 0xb0, 0xbd, 0x7f, 0xcd, 0x7f, 0xa9, 0x7f, 0x69, 0x7f, 0xd9, 0x7f, 0x1d,
+ 0x7f, 0x20, 0xcd, 0x82, 0x29, 0x7b, 0xb0, 0x31, 0x80, 0xb5, 0x7f, 0x8d, 0x80, 0x95, 0x7f, 0xdd,
+ 0x80, 0xb5, 0x7f, 0x20, 0x7d, 0x83, 0x69, 0x81, 0xb0, 0xb9, 0x80, 0x75, 0x7f, 0x85, 0x81, 0xfd,
+ 0x7e, 0x61, 0x82, 0xa1, 0x7e, 0x20, 0x85, 0x80, 0x4d, 0x7c, 0xb0, 0x0d, 0x80, 0xad, 0x7f, 0x59,
+ 0x80, 0x69, 0x7f, 0xb1, 0x80, 0x69, 0x7f, 0xe7, 0x99, 0x85, 0xb0, 0x59, 0x80, 0x80, 0xa5, 0x80,
+ 0x45, 0x80, 0xb1, 0x80, 0x99, 0x80, 0x20, 0x85, 0x80, 0xb5, 0x83, 0xb0, 0xd9, 0x80, 0x59, 0x80,
+ 0xa5, 0x81, 0xd1, 0x80, 0x61, 0x82, 0x61, 0x81, 0x20, 0x7d, 0x83, 0x99, 0x7e, 0xb0, 0x51, 0x80,
+ 0xe1, 0x7f, 0xb1, 0x80, 0x80, 0xdd, 0x80, 0x4d, 0x80, 0x20, 0xcd, 0x82, 0xd9, 0x84, 0xb0, 0x31,
+ 0x80, 0x4d, 0x80, 0x19, 0x80, 0xb1, 0x80, 0xd9, 0x7f, 0xe5, 0x80, 0x20, 0x0d, 0x7d, 0x51, 0x82,
+ 0xb0, 0x11, 0x80, 0x71, 0x80, 0x19, 0x80, 0xe9, 0x80, 0x19, 0x80, 0x5d, 0x81, 0xe1,
+}
+
+var ActionSettingsBackupRestore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x80, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x92, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe2, 0x80, 0x5c, 0xa0, 0x11, 0x76, 0x5c, 0x5c, 0x11,
+ 0x76, 0x5c, 0x80, 0xe6, 0x50, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe7, 0x74, 0xb0, 0x80, 0x45, 0x78,
+ 0x45, 0x86, 0x64, 0x9c, 0x64, 0x91, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c,
+ 0xb0, 0xf9, 0x7c, 0x80, 0x31, 0x7a, 0x09, 0x7f, 0xe1, 0x77, 0x65, 0x7d, 0x20, 0x2d, 0x7d, 0xe1,
+ 0x82, 0xa0, 0x19, 0x78, 0x99, 0x90, 0xe1, 0x7b, 0xa4, 0x80, 0xa4, 0xb0, 0xf1, 0x89, 0x80, 0xa4,
+ 0xf1, 0x77, 0xa4, 0x5c, 0x80, 0xf1, 0x89, 0x5c, 0x80, 0x5c, 0xe1,
+}
+
+var ActionSettingsBluetooth = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0xb0, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0xa0, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x69, 0x85, 0x69,
+ 0x5b, 0x00, 0x80, 0x50, 0xe7, 0x7c, 0xe9, 0x2d, 0x8f, 0x05, 0xd5, 0x74, 0x5c, 0x64, 0xd5, 0x70,
+ 0x2d, 0x7d, 0x78, 0x64, 0x2d, 0x87, 0xd5, 0x74, 0x94, 0x7c, 0xd5, 0x80, 0xe8, 0xa0, 0xe7, 0x84,
+ 0x20, 0x69, 0x8b, 0x99, 0x74, 0x00, 0xd5, 0x82, 0x78, 0x20, 0x95, 0x88, 0x69, 0x77, 0xe2, 0x84,
+ 0xa9, 0x6f, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0x00, 0x84, 0x2d, 0x77, 0xe8, 0xa9, 0x6f, 0xe3, 0xc5,
+ 0x83, 0xf1, 0x94, 0x00, 0x84, 0x59, 0x88, 0xe9, 0x7d, 0x78, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0xe1,
+}
+
+var ActionSettingsBrightness = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x09, 0xa0, 0xe6, 0x5c,
+ 0xe8, 0xf9, 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe2, 0x70, 0x90, 0xe7, 0x8a, 0x21, 0x86, 0x86,
+ 0x86, 0x7a, 0xe7, 0x8a, 0xe9, 0x76, 0x21, 0x86, 0x7a, 0x7a, 0x7a, 0xe9, 0x76, 0xe7, 0x76, 0x21,
+ 0x7a, 0x7a, 0x7a, 0x86, 0xe7, 0x76, 0xe9, 0x8a, 0x21, 0x7a, 0x86, 0x86, 0x86, 0xe9, 0x8a, 0xe3,
+ 0x90, 0x64, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x90, 0x51, 0x7d, 0x8c, 0x74,
+ 0x8c, 0xe8, 0x74, 0xe1,
+}
+
+var ActionSettingsCell = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0xb0, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x90, 0x05, 0x68,
+ 0x00, 0x70, 0x50, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc0, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x58, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe2,
+ 0x90, 0x90, 0xe6, 0x70, 0xe8, 0x60, 0xe7, 0xa0, 0xe9, 0xb0, 0xe1,
+}
+
+var ActionSettingsEthernet = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8d, 0x77, 0x85, 0x75,
+ 0x20, 0xed, 0x7c, 0x75, 0x7d, 0x00, 0xa5, 0x69, 0x80, 0x21, 0xd1, 0x8a, 0x0d, 0x8d, 0x15, 0x83,
+ 0x75, 0x7d, 0x00, 0xd9, 0x6e, 0x80, 0x20, 0xb5, 0x88, 0x85, 0x75, 0xe2, 0x6c, 0x84, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xa8, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x68, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x8d, 0x8d, 0xf5,
+ 0x70, 0x20, 0xed, 0x7c, 0x8d, 0x82, 0x00, 0x29, 0x91, 0x80, 0x21, 0x4d, 0x77, 0x7d, 0x8a, 0x15,
+ 0x83, 0x8d, 0x82, 0x01, 0x5d, 0x96, 0x80, 0x8d, 0x8b, 0xf5, 0x72, 0xe1,
+}
+
+var ActionSettingsInputAntenna = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x64, 0xb0, 0x45,
+ 0x78, 0x80, 0x64, 0x45, 0x86, 0x64, 0x9c, 0xe7, 0x88, 0xb0, 0x80, 0x7d, 0x7a, 0x7d, 0x84, 0x6c,
+ 0x94, 0x6c, 0x90, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xe7, 0x88, 0xb0, 0x80, 0x45, 0x78, 0xbd, 0x79,
+ 0x64, 0x64, 0x64, 0xe3, 0x84, 0x95, 0x92, 0xb1, 0xc5, 0x81, 0x3d, 0x7f, 0x86, 0x79, 0x7d, 0x86,
+ 0x6d, 0x7b, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76, 0x8a,
+ 0xb0, 0x80, 0x0d, 0x82, 0x3d, 0x81, 0xd1, 0x83, 0x86, 0x95, 0x84, 0xe9, 0x99, 0x86, 0x01, 0x2d,
+ 0x77, 0xa4, 0x74, 0xd5, 0x94, 0x21, 0x8c, 0x74, 0x8c, 0x8c, 0x01, 0xd5, 0x88, 0xa4, 0x84, 0x2d,
+ 0x8b, 0xe9, 0x69, 0x79, 0xe2, 0x80, 0x54, 0xa0, 0xd9, 0x73, 0x54, 0x54, 0xd9, 0x73, 0x54, 0x80,
+ 0xe7, 0x88, 0xb0, 0x80, 0x11, 0x76, 0x11, 0x88, 0x5c, 0xa4, 0x5c, 0x90, 0xa4, 0x11, 0x88, 0xa4,
+ 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0xd9, 0x73, 0x29, 0x76, 0x54, 0x54, 0x54, 0xe1,
+}
+
+var ActionSettingsInputComponent = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x58, 0xb0, 0x80,
+ 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0x90, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe6,
+ 0x54, 0xe9, 0x98, 0xe7, 0x98, 0xe8, 0x68, 0xe7, 0x78, 0xe8, 0x58, 0xe3, 0x90, 0xb8, 0xb0, 0x80,
+ 0x9d, 0x82, 0xb1, 0x81, 0xd1, 0x84, 0x88, 0xa1, 0x85, 0xe8, 0xac, 0xe7, 0x88, 0xe9, 0xa1, 0x77,
+ 0xb0, 0x51, 0x82, 0x2d, 0x7f, 0x88, 0xfd, 0x7c, 0x88, 0x61, 0x7a, 0xe9, 0x78, 0xe6, 0x74, 0xe9,
+ 0x88, 0xe2, 0x54, 0x90, 0xb0, 0x80, 0x9d, 0x82, 0xb1, 0x81, 0xd1, 0x84, 0x88, 0xa1, 0x85, 0xe8,
+ 0xac, 0xe7, 0x88, 0xe9, 0xa1, 0x77, 0xb0, 0x51, 0x82, 0x2d, 0x7f, 0x88, 0xfd, 0x7c, 0x88, 0x61,
+ 0x7a, 0xe9, 0x78, 0xe6, 0x54, 0xe9, 0x88, 0xe3, 0xd0, 0x58, 0xe8, 0x58, 0xb0, 0x80, 0xe9, 0x7e,
+ 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0x90, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe7, 0x78, 0xe9,
+ 0x98, 0xe7, 0x98, 0xe8, 0x68, 0xe7, 0x78, 0xe2, 0x84, 0x58, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0x90, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe7, 0x78, 0xe9, 0x98, 0xe7,
+ 0x98, 0xe8, 0x68, 0xe7, 0x78, 0xe8, 0x58, 0xe3, 0x90, 0xb8, 0xb0, 0x80, 0x9d, 0x82, 0xb1, 0x81,
+ 0xd1, 0x84, 0x88, 0xa1, 0x85, 0xe8, 0xac, 0xe7, 0x88, 0xe9, 0xa1, 0x77, 0xb0, 0x51, 0x82, 0x2d,
+ 0x7f, 0x88, 0xfd, 0x7c, 0x88, 0x61, 0x7a, 0xe9, 0x78, 0xe6, 0x94, 0xe9, 0x88, 0xe1,
+}
+
+var ActionSettingsInputComposite = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x58, 0xb0, 0x80,
+ 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0x90, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe6,
+ 0x54, 0xe9, 0x98, 0xe7, 0x98, 0xe8, 0x68, 0xe7, 0x78, 0xe8, 0x58, 0xe3, 0x90, 0xb8, 0xb0, 0x80,
+ 0x9d, 0x82, 0xb1, 0x81, 0xd1, 0x84, 0x88, 0xa1, 0x85, 0xe8, 0xac, 0xe7, 0x88, 0xe9, 0xa1, 0x77,
+ 0xb0, 0x51, 0x82, 0x2d, 0x7f, 0x88, 0xfd, 0x7c, 0x88, 0x61, 0x7a, 0xe9, 0x78, 0xe6, 0x74, 0xe9,
+ 0x88, 0xe2, 0x54, 0x90, 0xb0, 0x80, 0x9d, 0x82, 0xb1, 0x81, 0xd1, 0x84, 0x88, 0xa1, 0x85, 0xe8,
+ 0xac, 0xe7, 0x88, 0xe9, 0xa1, 0x77, 0xb0, 0x51, 0x82, 0x2d, 0x7f, 0x88, 0xfd, 0x7c, 0x88, 0x61,
+ 0x7a, 0xe9, 0x78, 0xe6, 0x54, 0xe9, 0x88, 0xe3, 0xd0, 0x58, 0xe8, 0x58, 0xb0, 0x80, 0xe9, 0x7e,
+ 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0x90, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe7, 0x78, 0xe9,
+ 0x98, 0xe7, 0x98, 0xe8, 0x68, 0xe7, 0x78, 0xe2, 0x84, 0x58, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0x90, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe7, 0x78, 0xe9, 0x98, 0xe7,
+ 0x98, 0xe8, 0x68, 0xe7, 0x78, 0xe8, 0x58, 0xe3, 0x90, 0xb8, 0xb0, 0x80, 0x9d, 0x82, 0xb1, 0x81,
+ 0xd1, 0x84, 0x88, 0xa1, 0x85, 0xe8, 0xac, 0xe7, 0x88, 0xe9, 0xa1, 0x77, 0xb0, 0x51, 0x82, 0x2d,
+ 0x7f, 0x88, 0xfd, 0x7c, 0x88, 0x61, 0x7a, 0xe9, 0x78, 0xe6, 0x94, 0xe9, 0x88, 0xe1,
+}
+
+var ActionSettingsInputHDMI = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x6c, 0xe8, 0x60,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c, 0xe7, 0x7c, 0xe9, 0x98, 0x20, 0x8c, 0x98, 0xe9, 0x8c, 0xe7,
+ 0xa0, 0xe9, 0x74, 0x20, 0x8c, 0x68, 0xe8, 0x6c, 0xe7, 0x7c, 0xe2, 0x70, 0x60, 0xe7, 0xa0, 0xe9,
+ 0x8c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x7c, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x7c, 0xe9,
+ 0x88, 0xe7, 0x78, 0xe8, 0x60, 0xe1,
+}
+
+var ActionSettingsInputSVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x7e, 0xb0, 0x80,
+ 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x92, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe3, 0x9c, 0x6c, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e,
+ 0x7a, 0x7a, 0x7a, 0xe7, 0x74, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x90, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0xe7, 0x8c, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe2,
+ 0x72, 0x8c, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86,
+ 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0x8e, 0x48, 0xa0, 0xe1,
+ 0x73, 0x54, 0x54, 0xe1, 0x73, 0x54, 0x80, 0x91, 0xe1, 0x89, 0xac, 0xac, 0xac, 0xac, 0x21, 0x76,
+ 0xac, 0x54, 0x80, 0x21, 0x8c, 0x54, 0x80, 0x54, 0xe3, 0x80, 0xd0, 0xb0, 0x11, 0x76, 0x80, 0x5c,
+ 0xed, 0x77, 0x5c, 0x5c, 0x80, 0x11, 0x76, 0x5c, 0x80, 0x5c, 0x91, 0xa4, 0x15, 0x88, 0xa4, 0xa4,
+ 0xf1, 0x77, 0xa4, 0x5c, 0xa4, 0xe3, 0x96, 0x54, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a,
+ 0x86, 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x7a, 0xe3, 0x78, 0x94, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81,
+ 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe1,
+}
+
+var ActionSettingsOverscan = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x80, 0x66, 0x00,
+ 0x78, 0x70, 0xe7, 0x90, 0x20, 0x05, 0x7c, 0x76, 0xe2, 0x98, 0x78, 0xe9, 0x90, 0x20, 0x8a, 0x05,
+ 0x7c, 0x00, 0x98, 0x78, 0xe3, 0x50, 0x80, 0x20, 0x76, 0x05, 0x84, 0x00, 0x68, 0x88, 0xe9, 0x70,
+ 0xe3, 0xa0, 0x98, 0xe7, 0x70, 0x20, 0x05, 0x84, 0x8a, 0x00, 0x88, 0x90, 0xe2, 0xa4, 0x5c, 0xe6,
+ 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x09, 0xa0, 0xe6,
+ 0x5c, 0xe8, 0xf9, 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe1,
+}
+
+var ActionSettingsPhone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x74, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x8c, 0x9a, 0xb1, 0x85, 0x7d, 0x80, 0x1d, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e,
+ 0x51, 0x7f, 0xc9, 0x7f, 0x85, 0x7e, 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69,
+ 0x84, 0xb0, 0x55, 0x7a, 0x21, 0x7d, 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69,
+ 0x84, 0x99, 0x7b, 0xb0, 0x8d, 0x80, 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d,
+ 0xa0, 0x69, 0x79, 0xe9, 0x74, 0x72, 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb2, 0xe5, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9,
+ 0x92, 0x39, 0x8f, 0xc4, 0xc4, 0xc4, 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72,
+ 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x66, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe1,
+}
+
+var ActionSettingsPower = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0xb0, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x88, 0x28, 0xe7, 0x78, 0xe9, 0xa8, 0xe7, 0x88, 0xe8, 0x58, 0xe3, 0x21, 0x87, 0xe1,
+ 0x84, 0x20, 0x1d, 0x7d, 0xe5, 0x82, 0xa0, 0xb1, 0x89, 0xe1, 0x75, 0x98, 0xa9, 0x79, 0x98, 0x7c,
+ 0xb0, 0x80, 0xa1, 0x86, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0x90, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0xb0,
+ 0x80, 0xa9, 0x7b, 0x51, 0x82, 0xe1, 0x77, 0xc5, 0x85, 0xc5, 0x75, 0x20, 0x1d, 0x7d, 0x1d, 0x7d,
+ 0xa0, 0xb9, 0x72, 0xc5, 0x73, 0x60, 0x91, 0x78, 0x60, 0x7c, 0xb0, 0x80, 0xd9, 0x88, 0x29, 0x87,
+ 0xa0, 0xa0, 0xa0, 0x90, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xb0, 0x80, 0x91, 0x7a, 0x49, 0x7d, 0xc5,
+ 0x75, 0x21, 0x79, 0xe1, 0x72, 0xe2, 0x8c, 0xb0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe1,
+}
+
+var ActionSettingsRemote = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x74, 0xe6, 0x74,
+ 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb0, 0xb0, 0x80, 0x19, 0x81, 0xe5,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0x98, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8,
+ 0x78, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x74, 0x98, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x19, 0x76, 0x19, 0x6e, 0x20, 0xd5, 0x82, 0xd5, 0x82,
+ 0xa0, 0xbd, 0x7a, 0x21, 0x75, 0x3d, 0x7d, 0x68, 0x80, 0x68, 0x90, 0x45, 0x85, 0x21, 0x81, 0x11,
+ 0x87, 0xf1, 0x82, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0xa0, 0x61, 0x87, 0x91, 0x71, 0xe1, 0x83, 0x60,
+ 0x80, 0x60, 0x90, 0xa1, 0x78, 0x91, 0x81, 0x19, 0x76, 0x19, 0x84, 0xe2, 0x80, 0x50, 0xa0, 0xf1,
+ 0x79, 0x50, 0x71, 0x74, 0x75, 0x6a, 0x71, 0x70, 0x71, 0x6e, 0x20, 0xd5, 0x82, 0xd5, 0x82, 0xa0,
+ 0x89, 0x76, 0x05, 0x6e, 0x09, 0x7b, 0x58, 0x80, 0x58, 0x90, 0x79, 0x89, 0x05, 0x82, 0xbd, 0x8c,
+ 0x45, 0x85, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0xa0, 0x91, 0x8b, 0x75, 0x6a, 0x11, 0x86, 0x50, 0x80,
+ 0x50, 0xe1,
+}
+
+var ActionSettingsVoice = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0xb0, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x94, 0x54, 0xb0, 0x51, 0x83, 0x80, 0xfd, 0x85, 0x51,
+ 0x7d, 0xfd, 0x85, 0x74, 0x00, 0x8c, 0x60, 0xb1, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74,
+ 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x98, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82,
+ 0x8c, 0x8c, 0x8c, 0xe3, 0x7c, 0xac, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90,
+ 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x48, 0xe7, 0x99, 0x7c, 0xb1,
+ 0x80, 0x8c, 0xf1, 0x7a, 0x35, 0x8a, 0x69, 0x75, 0x35, 0x8a, 0x7d, 0x7a, 0x80, 0x69, 0x75, 0xcd,
+ 0x7b, 0x69, 0x75, 0xcd, 0x75, 0xe6, 0x64, 0xb0, 0x80, 0xd5, 0x86, 0x71, 0x85, 0x79, 0x8c, 0x98,
+ 0x71, 0x8d, 0xe8, 0xa0, 0xe7, 0x88, 0xe9, 0x71, 0x79, 0xb0, 0x91, 0x86, 0x09, 0x7f, 0x98, 0x65,
+ 0x79, 0x98, 0x91, 0x72, 0xe1,
+}
+
+var ActionShop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x68, 0xe8, 0x60,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x88, 0xe6, 0x58, 0xe9, 0xb4, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68,
+ 0xe6, 0x90, 0xe2, 0x78, 0x60, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x70, 0xe8, 0x60, 0xe3, 0x7c, 0xb8,
+ 0xe8, 0x74, 0x21, 0x9e, 0x90, 0x62, 0x94, 0xe1,
+}
+
+var ActionShopTwo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x64, 0xe8, 0x5c,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x88, 0xe6, 0x64, 0xe9, 0xac, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64,
+ 0xe6, 0x98, 0xe2, 0x80, 0x5c, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x70, 0xe8, 0x5c, 0xe3, 0x80, 0xb0,
+ 0xe8, 0x70, 0x21, 0x96, 0x8c, 0x6a, 0x90, 0xe2, 0x5c, 0x74, 0xe6, 0x54, 0xe9, 0xac, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe6, 0x5c, 0xe8, 0x74, 0xe1,
+}
+
+var ActionShoppingBasket = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6d, 0x8a, 0x74, 0x00,
+ 0xa9, 0x81, 0xe5, 0x6c, 0xb1, 0xa1, 0x7f, 0x6d, 0x7f, 0xfd, 0x7e, 0x29, 0x7f, 0x59, 0x7e, 0x29,
+ 0x7f, 0x5d, 0x7f, 0x80, 0xb9, 0x7e, 0x49, 0x80, 0x59, 0x7e, 0xd9, 0x80, 0x00, 0x95, 0x75, 0x74,
+ 0xe6, 0x58, 0xb1, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x80, 0x31, 0x80, 0x09, 0x80,
+ 0x61, 0x80, 0x11, 0x80, 0x8d, 0x80, 0x20, 0x11, 0x85, 0x8d, 0x92, 0xa0, 0x9d, 0x6f, 0xc5, 0x90,
+ 0x29, 0x71, 0xa4, 0x66, 0xa4, 0xe7, 0xb4, 0xb0, 0xd9, 0x81, 0x80, 0x65, 0x83, 0xc5, 0x7e, 0xd9,
+ 0x83, 0x11, 0x7d, 0x20, 0x11, 0x85, 0x75, 0x6d, 0xb1, 0x0d, 0x80, 0xd9, 0x7f, 0x15, 0x80, 0xa9,
+ 0x7f, 0x15, 0x80, 0x79, 0x7f, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe7, 0x6d, 0x76,
+ 0xe2, 0x74, 0x74, 0x21, 0x8c, 0x35, 0x77, 0x8c, 0xcd, 0x88, 0xe6, 0x74, 0xe3, 0x8c, 0xa0, 0xb0,
+ 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd,
+ 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe1,
+}
+
+var ActionShoppingCart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x98, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x92, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x54, 0x58, 0xe9, 0x88, 0xe7,
+ 0x88, 0x21, 0x31, 0x87, 0x2d, 0x8f, 0x4d, 0x7d, 0xe9, 0x84, 0xb1, 0xb1, 0x7f, 0x95, 0x80, 0x85,
+ 0x7f, 0x3d, 0x81, 0x85, 0x7f, 0xf1, 0x81, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xe9, 0x78, 0xe6, 0xd9, 0x76, 0xb1, 0xb9, 0x7f, 0x80, 0x81, 0x7f, 0xc9, 0x7f, 0x81, 0x7f,
+ 0x81, 0x7f, 0x80, 0xe9, 0x7f, 0x05, 0x80, 0xd5, 0x7f, 0x11, 0x80, 0xc5, 0x7f, 0x00, 0x35, 0x78,
+ 0x84, 0xe7, 0xe9, 0x8e, 0xb0, 0x81, 0x81, 0x80, 0xd1, 0x82, 0x2d, 0x7f, 0x81, 0x83, 0xf1, 0x7d,
+ 0x20, 0x29, 0x87, 0x05, 0x73, 0xb1, 0x29, 0x80, 0xb9, 0x7f, 0x41, 0x80, 0x65, 0x7f, 0x41, 0x80,
+ 0x0d, 0x7f, 0x80, 0xe5, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x71, 0x72, 0x20, 0x19, 0x7e,
+ 0x78, 0xe6, 0x54, 0xe3, 0xc0, 0xc0, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c,
+ 0x88, 0x92, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe1,
+}
+
+var ActionSpeakerNotes = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x70, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x9c, 0x98, 0xe6, 0x78, 0xe9, 0x78, 0xe7,
+ 0x94, 0xe9, 0x88, 0xe3, 0x8c, 0x74, 0xe6, 0x78, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe3, 0x80,
+ 0x74, 0xe6, 0x78, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe1,
+}
+
+var ActionSpeakerNotesOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x15, 0x7d, 0x7c, 0x05,
+ 0x78, 0xed, 0x7c, 0x15, 0x77, 0x70, 0x68, 0xed, 0x74, 0xc5, 0x6c, 0xb1, 0x6d, 0x8d, 0x6a, 0x75,
+ 0x6b, 0x50, 0x5c, 0x20, 0x05, 0x84, 0x05, 0x84, 0x00, 0x58, 0xa8, 0x20, 0x90, 0x70, 0xe7, 0xa4,
+ 0x20, 0x75, 0x8b, 0x75, 0x8b, 0x01, 0xa8, 0xed, 0x94, 0x15, 0x8b, 0x98, 0x20, 0x64, 0x64, 0xe2,
+ 0x70, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x78, 0x74, 0xe9, 0x78, 0x20,
+ 0x88, 0x88, 0xe7, 0x78, 0xe2, 0xa0, 0x58, 0xe6, 0x29, 0x70, 0x00, 0x78, 0xd9, 0x77, 0xe8, 0x68,
+ 0xe7, 0xa0, 0xe9, 0x88, 0xe6, 0x29, 0x7c, 0x20, 0x84, 0x84, 0xe6, 0x98, 0xe9, 0x88, 0xe7, 0x29,
+ 0x76, 0x20, 0xfd, 0x8d, 0xfd, 0x8d, 0xa0, 0x49, 0x92, 0xe9, 0x8b, 0xa8, 0x29, 0x8a, 0xa8, 0x90,
+ 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ActionSpellcheck = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe5, 0x80, 0x90, 0xe7,
+ 0x31, 0x84, 0x00, 0xdd, 0x7a, 0x5c, 0xe7, 0x4d, 0x7c, 0x00, 0xf1, 0x6c, 0x90, 0xe7, 0x31, 0x84,
+ 0x20, 0x41, 0x82, 0x74, 0xe7, 0x4d, 0x8b, 0x20, 0x3d, 0x82, 0x8c, 0xe2, 0xdd, 0x74, 0x7c, 0x01,
+ 0x72, 0xf5, 0x72, 0x25, 0x7d, 0x7c, 0xe7, 0xb9, 0x77, 0xe3, 0x51, 0x9e, 0x2d, 0x81, 0x01, 0x86,
+ 0x59, 0x8f, 0xa9, 0x7b, 0x90, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x86, 0xaa, 0x21, 0xa6, 0x5a,
+ 0x2d, 0x7d, 0x2d, 0x7d, 0xe1,
+}
+
+var ActionStarRate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x25, 0x86, 0x21,
+ 0xe5, 0x89, 0x35, 0x87, 0x39, 0x7c, 0x61, 0x74, 0x00, 0xa0, 0xad, 0x7a, 0xe7, 0xdd, 0x73, 0x01,
+ 0x80, 0xad, 0x6e, 0x25, 0x7c, 0xad, 0x7a, 0xe6, 0x60, 0x20, 0xe5, 0x89, 0x0d, 0x87, 0x00, 0x1d,
+ 0x76, 0x55, 0x8d, 0xe1,
+}
+
+var ActionStars = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe3,
+ 0x79, 0x88, 0xc0, 0x01, 0x80, 0xe9, 0x86, 0x8d, 0x77, 0x98, 0x22, 0x3d, 0x82, 0x61, 0x76, 0x8d,
+ 0x78, 0x89, 0x79, 0xd9, 0x89, 0x29, 0x7f, 0x00, 0x80, 0x64, 0x22, 0xd9, 0x83, 0x11, 0x89, 0xd9,
+ 0x89, 0xd9, 0x80, 0x8d, 0x78, 0x79, 0x86, 0x00, 0x75, 0x88, 0x98, 0xe1,
+}
+
+var ActionStore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xe9, 0x88, 0xe7, 0xc0, 0xe8, 0x60, 0xe3, 0x84, 0xa8, 0xe9, 0x78, 0x20, 0x7c, 0x6c, 0xe6, 0x60,
+ 0x00, 0x5c, 0x80, 0xe9, 0x88, 0xe7, 0x84, 0xe9, 0x98, 0xe7, 0xa8, 0xe8, 0x88, 0xe7, 0x90, 0xe9,
+ 0x98, 0xe7, 0x88, 0xe8, 0x88, 0xe7, 0x84, 0xe3, 0x5c, 0x90, 0xe6, 0x68, 0xe9, 0x70, 0xe7, 0x98,
+ 0xe9, 0x90, 0xe1,
+}
+
+var ActionSubject = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x94, 0xe6, 0x60,
+ 0xe9, 0x88, 0xe7, 0xa8, 0xe9, 0x78, 0xe3, 0x98, 0x60, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0xc0, 0xe9,
+ 0x78, 0xe2, 0x60, 0x8c, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x60, 0xe9, 0x88, 0xe3, 0x80, 0x58, 0xe9,
+ 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x60, 0xe1,
+}
+
+var ActionSupervisorAccount = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x92, 0x80, 0xb0, 0xc5,
+ 0x82, 0x80, 0xfd, 0x84, 0xc5, 0x7d, 0xfd, 0x84, 0x76, 0x90, 0xc9, 0x7d, 0x76, 0x05, 0x7b, 0x76,
+ 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0x90, 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0xe3,
+ 0x62, 0x7c, 0xb0, 0x51, 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d,
+ 0x74, 0x05, 0x7a, 0x74, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90, 0xb1, 0x82,
+ 0x8c, 0x8c, 0x8c, 0xe3, 0x9e, 0x8c, 0xb0, 0x55, 0x7c, 0x80, 0x6a, 0xd9, 0x81, 0x6a, 0x81, 0x85,
+ 0xe8, 0x9c, 0xe7, 0xac, 0xe9, 0x81, 0x7b, 0xb0, 0x80, 0x59, 0x7c, 0xad, 0x78, 0x81, 0x7a, 0x6a,
+ 0x81, 0x7a, 0xe3, 0x62, 0x7c, 0xb0, 0x55, 0x7b, 0x80, 0x64, 0x59, 0x82, 0x64, 0x8e, 0xe9, 0x8a,
+ 0xe7, 0x9c, 0xe9, 0x81, 0x7b, 0xb0, 0x80, 0x4d, 0x7e, 0xad, 0x80, 0x55, 0x7b, 0xbd, 0x84, 0x11,
+ 0x79, 0xa0, 0x7a, 0x31, 0x82, 0x51, 0x7b, 0x84, 0x74, 0x84, 0xe1,
+}
+
+var ActionSwapHoriz = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x75, 0x7c, 0x00,
+ 0x5c, 0x8c, 0x20, 0xfd, 0x87, 0x90, 0xe9, 0x74, 0xe6, 0x88, 0xe9, 0x78, 0xe6, 0xfd, 0x75, 0xe9,
+ 0x74, 0xe2, 0xa4, 0x74, 0x20, 0x05, 0x78, 0x70, 0xe9, 0x8c, 0xe6, 0x78, 0xe9, 0x88, 0xe7, 0x05,
+ 0x8e, 0xe9, 0x8c, 0x00, 0xa4, 0x74, 0xe1,
+}
+
+var ActionSwapVert = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x05, 0x8a, 0xe8,
+ 0x78, 0xe7, 0x78, 0xe9, 0x05, 0x8e, 0xe7, 0x74, 0x00, 0x8c, 0xa4, 0x20, 0x90, 0x05, 0x78, 0xe7,
+ 0x74, 0xe2, 0x74, 0x5c, 0x20, 0x70, 0xfd, 0x87, 0xe7, 0x8c, 0xe8, 0x88, 0xe7, 0x88, 0xe8, 0xfd,
+ 0x75, 0xe7, 0x8c, 0x00, 0x74, 0x5c, 0xe1,
+}
+
+var ActionSwapVerticalCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe2, 0x6a, 0x74, 0x21, 0x8e, 0x72, 0x8e, 0x8e,
+ 0xe7, 0x76, 0xe9, 0x90, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x76, 0xe3, 0xac, 0x98, 0x21, 0x72, 0x8e,
+ 0x72, 0x72, 0xe7, 0x8a, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x8a, 0xe1,
+}
+
+var ActionSystemUpdateAlt = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x81, 0x88, 0x20,
+ 0x90, 0x70, 0xe7, 0x74, 0xe9, 0x5c, 0xe7, 0x78, 0xe9, 0xa4, 0xe7, 0x74, 0x20, 0x90, 0x90, 0xe3,
+ 0xa4, 0x4c, 0xe6, 0x8c, 0xe9, 0xf9, 0x83, 0xe7, 0x98, 0xe9, 0x11, 0x9c, 0xe6, 0x5c, 0xe8, 0x79,
+ 0x72, 0xe7, 0x98, 0xe8, 0x81, 0x6e, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x48, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe1,
+}
+
+var ActionTab = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x5c, 0xe8,
+ 0x64, 0xe7, 0xa8, 0xe9, 0x90, 0xe7, 0xa0, 0xe9, 0xa8, 0xe1,
+}
+
+var ActionTabUnselected = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0x74, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x54, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x54, 0xe9,
+ 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe8, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe3, 0xa0, 0xc0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x54, 0x94, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe6, 0x54, 0xe9, 0x88, 0xe3, 0x88, 0x90, 0xe9, 0x78, 0xe6, 0x54, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe2, 0xa4, 0x5c, 0xe6, 0x84, 0xe9, 0x98, 0xe7, 0xa8,
+ 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x74, 0x64, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x70, 0xc0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x40, 0xe7,
+ 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xc0, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe2, 0x84, 0xa4, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var ActionTheaters = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x5c, 0xe9, 0x88,
+ 0xe7, 0x78, 0xe8, 0x5c, 0xe6, 0x70, 0xe9, 0x88, 0xe7, 0x78, 0xe8, 0x5c, 0xe6, 0x60, 0xe9, 0xc8,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe2, 0x70, 0x94, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0xa8, 0xa0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var ActionThumbDown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x5c, 0xe6, 0x68,
+ 0xb0, 0x59, 0x7e, 0x80, 0xed, 0x7c, 0x05, 0x81, 0x51, 0x7c, 0x71, 0x82, 0x20, 0xf9, 0x79, 0x19,
+ 0x8e, 0xa0, 0x1d, 0x6a, 0x7e, 0x54, 0x7d, 0x7f, 0x54, 0x80, 0xe9, 0xd5, 0x83, 0x20, 0x05, 0x80,
+ 0x05, 0x80, 0x00, 0x54, 0x88, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa1,
+ 0x8c, 0x20, 0x19, 0x7e, 0x25, 0x89, 0xb1, 0xf5, 0x7f, 0x35, 0x80, 0xf1, 0x7f, 0x69, 0x80, 0xf1,
+ 0x7f, 0xa1, 0x80, 0x80, 0xd5, 0x80, 0x59, 0x80, 0x95, 0x81, 0xe1, 0x80, 0x21, 0x82, 0x00, 0xa9,
+ 0x7b, 0xac, 0x20, 0x2d, 0x8d, 0xd5, 0x72, 0xa0, 0x8d, 0x89, 0x19, 0x88, 0x94, 0x19, 0x87, 0x94,
+ 0x8c, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x90, 0x80, 0xe9,
+ 0xb0, 0xe7, 0x90, 0xe8, 0x5c, 0xe7, 0x70, 0xe1,
+}
+
+var ActionThumbUp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0xa4, 0xe7, 0x90,
+ 0xe8, 0x74, 0xe6, 0x54, 0xe9, 0xb0, 0xe3, 0xd8, 0x54, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe6, 0x61, 0x85, 0x20, 0xe9, 0x81, 0xdd, 0x76, 0xb1, 0x0d, 0x80, 0xcd, 0x7f, 0x11,
+ 0x80, 0x99, 0x7f, 0x11, 0x80, 0x61, 0x7f, 0x80, 0x2d, 0x7f, 0xa9, 0x7f, 0x6d, 0x7e, 0x21, 0x7f,
+ 0xe1, 0x7d, 0x01, 0x59, 0x84, 0x54, 0x2d, 0x77, 0x2d, 0x77, 0xa0, 0x75, 0x76, 0xe9, 0x77, 0x6c,
+ 0xe9, 0x78, 0x6c, 0x74, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xa4, 0xb0, 0xa9, 0x81, 0x80, 0x15, 0x83, 0xfd, 0x7e, 0xb1, 0x83, 0x91, 0x7d, 0x20, 0x09, 0x86,
+ 0xe9, 0x71, 0xb0, 0x31, 0x80, 0x8d, 0x7f, 0x4d, 0x80, 0x0d, 0x7f, 0x4d, 0x80, 0x8d, 0x7e, 0xe9,
+ 0x2d, 0x7c, 0x20, 0xfd, 0x7f, 0xfd, 0x7f, 0x00, 0xac, 0x78, 0xe1,
+}
+
+var ActionThumbsUpDown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x68, 0xb0, 0x80,
+ 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0xa1, 0x73, 0x20, 0x55, 0x81, 0xa9, 0x79, 0xb1,
+ 0x09, 0x80, 0xd9, 0x7f, 0x0d, 0x80, 0xb1, 0x7f, 0x0d, 0x80, 0x89, 0x7f, 0x80, 0x61, 0x7f, 0xc1,
+ 0x7f, 0xd1, 0x7e, 0x59, 0x7f, 0x69, 0x7e, 0x01, 0xc5, 0x72, 0x50, 0xe1, 0x68, 0xe1, 0x71, 0xa0,
+ 0x59, 0x68, 0x6d, 0x72, 0x50, 0x2d, 0x73, 0x50, 0x68, 0xe9, 0x9a, 0xb0, 0x80, 0xa9, 0x81, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0xe7, 0x81, 0x8d, 0xb0, 0x3d, 0x81, 0x80, 0x51, 0x82, 0x41, 0x7f, 0xc5,
+ 0x82, 0x2d, 0x7e, 0x20, 0x89, 0x84, 0x6d, 0x75, 0xb0, 0x21, 0x80, 0xa9, 0x7f, 0x35, 0x80, 0x4d,
+ 0x7f, 0x35, 0x80, 0xe9, 0x7e, 0xe8, 0x68, 0xe3, 0xaa, 0x90, 0xe6, 0x81, 0x87, 0xb0, 0xc5, 0x7e,
+ 0x80, 0xb1, 0x7d, 0xc1, 0x80, 0x3d, 0x7d, 0xd5, 0x81, 0x20, 0x79, 0x7b, 0x95, 0x8a, 0xb0, 0xe1,
+ 0x7f, 0x59, 0x80, 0xcd, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x19, 0x81, 0xe8, 0x98, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x61, 0x8a, 0x20, 0xad, 0x7e, 0x59, 0x86, 0xb1, 0xf9,
+ 0x7f, 0x29, 0x80, 0xf5, 0x7f, 0x51, 0x80, 0xf5, 0x7f, 0x79, 0x80, 0x80, 0xa1, 0x80, 0x41, 0x80,
+ 0x31, 0x81, 0xa9, 0x80, 0x99, 0x81, 0x00, 0x3d, 0x8d, 0xb0, 0x20, 0xe1, 0x89, 0x21, 0x76, 0xb0,
+ 0x8d, 0x80, 0x75, 0x7f, 0xe1, 0x80, 0xb5, 0x7e, 0xe1, 0x80, 0xe1, 0x7d, 0xe8, 0x7e, 0xb0, 0x80,
+ 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe1,
+}
+
+var ActionTimeline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x70, 0xb1, 0x80,
+ 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xa5, 0x7f, 0x80, 0x4d, 0x7f, 0xf5, 0x7f, 0xfd, 0x7e,
+ 0xdd, 0x7f, 0x20, 0xe1, 0x78, 0x19, 0x87, 0xb1, 0x19, 0x80, 0x51, 0x80, 0x25, 0x80, 0xb1, 0x80,
+ 0x25, 0x80, 0x0d, 0x81, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x90, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0xb0, 0x80, 0xa5, 0x7f, 0x0d, 0x80, 0x49, 0x7f, 0x25, 0x80, 0xf5, 0x7e, 0x20, 0xe9,
+ 0x7a, 0xe9, 0x7a, 0xb0, 0xb1, 0x7f, 0x19, 0x80, 0x51, 0x7f, 0x25, 0x80, 0xf5, 0x7e, 0x25, 0x80,
+ 0x90, 0x49, 0x7f, 0xf5, 0x7f, 0xf5, 0x7e, 0xdd, 0x7f, 0x20, 0xe9, 0x76, 0x21, 0x89, 0xb1, 0x19,
+ 0x80, 0x51, 0x80, 0x25, 0x80, 0xa9, 0x80, 0x25, 0x80, 0x05, 0x81, 0x80, 0x35, 0x82, 0x35, 0x7e,
+ 0x88, 0x78, 0x88, 0x91, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xb0, 0x5d,
+ 0x80, 0x80, 0xb5, 0x80, 0x0d, 0x80, 0x05, 0x81, 0x25, 0x80, 0x20, 0x21, 0x89, 0xe9, 0x76, 0xb1,
+ 0xe9, 0x7f, 0xb1, 0x7f, 0xdd, 0x7f, 0x51, 0x7f, 0xdd, 0x7f, 0xf5, 0x7e, 0x80, 0xcd, 0x7d, 0xcd,
+ 0x81, 0x78, 0x88, 0x78, 0x90, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xb0, 0x80, 0x5d, 0x80, 0xf5, 0x7f,
+ 0xb9, 0x80, 0xdd, 0x7f, 0x0d, 0x81, 0x20, 0x19, 0x85, 0x19, 0x85, 0xb0, 0x51, 0x80, 0xe9, 0x7f,
+ 0xb1, 0x80, 0xdd, 0x7f, 0x0d, 0x81, 0xdd, 0x7f, 0x90, 0xb9, 0x80, 0x0d, 0x80, 0x0d, 0x81, 0x25,
+ 0x80, 0x20, 0x19, 0x87, 0xe1, 0x78, 0xb1, 0xe9, 0x7f, 0xb1, 0x7f, 0xdd, 0x7f, 0x59, 0x7f, 0xdd,
+ 0x7f, 0xfd, 0x7e, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x90, 0x88, 0xcd, 0x81, 0x88,
+ 0x88, 0xe1,
+}
+
+var ActionTOC = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x74, 0xe7, 0xb8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0xc0, 0x80, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x58, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe3, 0x80, 0x98, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var ActionToday = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe7, 0x7c,
+ 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x70, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0,
+ 0xe6, 0x64, 0xe8, 0x70, 0xe7, 0xb8, 0xe9, 0xac, 0xe2, 0x6c, 0x78, 0xe7, 0x94, 0xe9, 0x94, 0xe6,
+ 0x6c, 0xe1,
+}
+
+var ActionToll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x80, 0xb0, 0x80,
+ 0xc9, 0x7a, 0x59, 0x83, 0x59, 0x76, 0x90, 0xb5, 0x74, 0xe8, 0x85, 0x70, 0xa0, 0x19, 0x6f, 0x4d,
+ 0x72, 0x54, 0x8d, 0x78, 0x54, 0x80, 0x90, 0x19, 0x85, 0xb5, 0x8d, 0x98, 0x7d, 0x8f, 0xe8, 0x4d,
+ 0x8b, 0xa0, 0x59, 0x71, 0xa9, 0x89, 0x5c, 0x39, 0x85, 0x5c, 0x80, 0xe2, 0x8c, 0x60, 0xb0, 0x29,
+ 0x77, 0x80, 0x60, 0x29, 0x87, 0x60, 0xa0, 0x91, 0x29, 0x87, 0xa0, 0xa0, 0xa0, 0xa0, 0xd9, 0x78,
+ 0xa0, 0x60, 0x80, 0xd9, 0x8e, 0x60, 0x8c, 0x60, 0xe3, 0x80, 0xb8, 0xb0, 0x61, 0x79, 0x80, 0x68,
+ 0xa1, 0x7a, 0x68, 0x68, 0x92, 0x61, 0x85, 0x68, 0x98, 0x68, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1,
+ 0x7a, 0x98, 0x68, 0x98, 0xe1,
+}
+
+var ActionTouchApp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x7d, 0x7e, 0xe8,
+ 0x6e, 0xb0, 0x80, 0x3d, 0x7d, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x90, 0x8a, 0x3d, 0x82, 0x8a, 0x8a,
+ 0xe9, 0x7d, 0x87, 0xb1, 0x69, 0x82, 0x65, 0x7e, 0x88, 0xa5, 0x7b, 0x88, 0x85, 0x78, 0x80, 0x09,
+ 0x7b, 0xf9, 0x7b, 0x6e, 0x6e, 0x6e, 0x90, 0x6e, 0x09, 0x84, 0x6e, 0x92, 0xb0, 0x80, 0x21, 0x83,
+ 0x99, 0x81, 0xe1, 0x85, 0x88, 0x7d, 0x87, 0xe3, 0xad, 0x93, 0x45, 0x89, 0x20, 0xed, 0x76, 0x7d,
+ 0x7b, 0xb0, 0xa9, 0x7f, 0xdd, 0x7f, 0x4d, 0x7f, 0xc9, 0x7f, 0xe9, 0x7e, 0xc9, 0x7f, 0xe6, 0x84,
+ 0xe8, 0x6e, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59, 0x81, 0x7a,
+ 0x86, 0xe9, 0x79, 0x95, 0x20, 0x29, 0x79, 0x91, 0x7e, 0xb1, 0xd9, 0x7f, 0xf9, 0x7f, 0xb1, 0x7f,
+ 0xf5, 0x7f, 0x89, 0x7f, 0xf5, 0x7f, 0x61, 0x7f, 0x80, 0xd1, 0x7e, 0x45, 0x80, 0x69, 0x7e, 0xa9,
+ 0x80, 0x21, 0x6d, 0x7e, 0x99, 0x81, 0xe1, 0x89, 0xe1, 0x89, 0xb0, 0x8d, 0x80, 0x8d, 0x80, 0x4d,
+ 0x81, 0xe1, 0x80, 0x21, 0x82, 0xe1, 0x80, 0xe6, 0x19, 0x8b, 0xb0, 0x85, 0x81, 0x80, 0xa9, 0x82,
+ 0xe5, 0x7e, 0xe1, 0x82, 0x71, 0x7d, 0x20, 0x85, 0x81, 0x75, 0x75, 0xb1, 0x05, 0x80, 0xdd, 0x7f,
+ 0x09, 0x80, 0xbd, 0x7f, 0x09, 0x80, 0x99, 0x7f, 0xfd, 0x7f, 0xc5, 0x7e, 0x3d, 0x7f, 0xb1, 0x7d,
+ 0x29, 0x7e, 0x3d, 0x7d, 0xe1,
+}
+
+var ActionTrackChanges = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x25, 0x8e, 0xdd, 0x71,
+ 0x20, 0x31, 0x7d, 0xd1, 0x82, 0xa0, 0x35, 0x8e, 0x95, 0x77, 0xa0, 0x95, 0x7b, 0xa0, 0x80, 0xb0,
+ 0x80, 0xd9, 0x88, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0x80, 0x60, 0xd9, 0x88, 0x60, 0x80, 0xb0, 0x80,
+ 0xd9, 0x77, 0x19, 0x86, 0x21, 0x71, 0x9c, 0x25, 0x70, 0xe9, 0x0d, 0x84, 0xb1, 0x51, 0x7a, 0xf5,
+ 0x80, 0x6c, 0xe1, 0x85, 0x6c, 0xd1, 0x8b, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0x90,
+ 0x98, 0xa1, 0x7a, 0x98, 0x68, 0xb0, 0x80, 0xb1, 0x7c, 0xa9, 0x7e, 0xb1, 0x79, 0x7d, 0x7c, 0x85,
+ 0x77, 0x20, 0x31, 0x7d, 0xd1, 0x82, 0xa0, 0x19, 0x87, 0xcd, 0x7b, 0x90, 0xcd, 0x7d, 0x90, 0x80,
+ 0xb0, 0x80, 0x6d, 0x84, 0x6d, 0x7c, 0x90, 0x70, 0x90, 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0xb0,
+ 0x80, 0x49, 0x7c, 0x91, 0x82, 0x31, 0x79, 0x8c, 0x49, 0x78, 0xe9, 0x49, 0x84, 0xb1, 0xcd, 0x7e,
+ 0xb5, 0x80, 0x7c, 0xf5, 0x81, 0x7c, 0x71, 0x83, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0x90, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xb0, 0x80, 0x85, 0x7e, 0x35, 0x7f, 0x3d, 0x7d, 0x7c, 0x91,
+ 0x7c, 0xe8, 0x58, 0xe7, 0x7c, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5,
+ 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0xb0, 0x80, 0x7d, 0x7a, 0xc5, 0x7d, 0x7d,
+ 0x75, 0x25, 0x7a, 0xdd, 0x71, 0xe1,
+}
+
+var ActionTranslate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xbd, 0x81, 0x29, 0x86,
+ 0x21, 0xed, 0x7a, 0xfd, 0x7a, 0x11, 0x80, 0xf1, 0x7f, 0xb0, 0x7d, 0x83, 0x21, 0x7c, 0xf5, 0x85,
+ 0xa9, 0x77, 0x6d, 0x87, 0xf1, 0x72, 0xe6, 0x94, 0xe8, 0x60, 0xe6, 0x78, 0xe8, 0x58, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe6, 0x54, 0xe9, 0xfd, 0x83, 0xe7, 0x59, 0x96, 0xa0, 0xfd, 0x7e, 0xd9, 0x77, 0xe1,
+ 0x7c, 0x81, 0x7b, 0x74, 0xb5, 0x7e, 0xb0, 0x25, 0x7e, 0xf1, 0x7d, 0x99, 0x7c, 0xb1, 0x7b, 0x61,
+ 0x7b, 0x4d, 0x79, 0xe7, 0x78, 0xb0, 0x75, 0x81, 0x45, 0x83, 0x75, 0x83, 0x59, 0x86, 0xf5, 0x85,
+ 0x21, 0x89, 0x01, 0x2d, 0x6d, 0x2d, 0x8b, 0x60, 0x9c, 0x22, 0x94, 0x6c, 0x39, 0x86, 0x39, 0x86,
+ 0x85, 0x81, 0xf1, 0x7b, 0xe2, 0x9a, 0x78, 0xe7, 0x78, 0x20, 0x6e, 0xb0, 0xe7, 0x88, 0x20, 0x41,
+ 0x82, 0x74, 0xe7, 0x81, 0x89, 0x00, 0xa4, 0xa8, 0xe7, 0x88, 0x20, 0x6e, 0x50, 0xe3, 0xc1, 0x7a,
+ 0x9c, 0x01, 0x96, 0x55, 0x81, 0x41, 0x8e, 0x94, 0xe7, 0x81, 0x79, 0xe1,
+}
+
+var ActionTrendingDown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x98, 0x22, 0x99,
+ 0x84, 0x69, 0x7b, 0x3d, 0x76, 0x41, 0x76, 0x70, 0x90, 0x01, 0x58, 0xd5, 0x76, 0xd5, 0x6e, 0x68,
+ 0x22, 0x98, 0x98, 0x90, 0x70, 0x95, 0x8c, 0x99, 0x8c, 0x00, 0xa8, 0x80, 0xe9, 0x98, 0xe1,
+}
+
+var ActionTrendingFlat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x80, 0x20, 0x70,
+ 0x70, 0xe9, 0x8c, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0xbc, 0xe9, 0x8c, 0xe1,
+}
+
+var ActionTrendingUp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x68, 0x22, 0x99,
+ 0x84, 0x99, 0x84, 0x3d, 0x76, 0xc1, 0x89, 0x70, 0x70, 0x01, 0x58, 0x2d, 0x89, 0xd5, 0x6e, 0x98,
+ 0x22, 0x98, 0x68, 0x90, 0x90, 0x95, 0x8c, 0x69, 0x73, 0x00, 0xa8, 0x80, 0xe8, 0x68, 0xe1,
+}
+
+var ActionTurnedIn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x64, 0xa4, 0x21, 0x9c,
+ 0x74, 0x9c, 0x8c, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ActionTurnedInNot = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x64, 0xa4, 0x21, 0x9c,
+ 0x74, 0x9c, 0x8c, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80,
+ 0xbc, 0x20, 0x6c, 0xa9, 0x7b, 0x00, 0x6c, 0x98, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb4, 0xe1,
+}
+
+var ActionUpdate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x41, 0x7c, 0xe6,
+ 0x71, 0x84, 0x20, 0x7d, 0x85, 0x5d, 0x7a, 0xb3, 0x8d, 0x7a, 0x99, 0x7a, 0xb5, 0x71, 0x65, 0x7a,
+ 0x3d, 0x6c, 0xcd, 0x7f, 0x8d, 0x7a, 0x69, 0x85, 0x8d, 0x7a, 0x2d, 0x8e, 0x80, 0x95, 0x93, 0x75,
+ 0x85, 0x69, 0x85, 0x4d, 0x8e, 0x69, 0x85, 0xc5, 0x93, 0x80, 0xb9, 0x82, 0x4d, 0x7d, 0x15, 0x84,
+ 0x2d, 0x7a, 0x11, 0x84, 0x35, 0x76, 0xe6, 0xa4, 0xb1, 0x80, 0xf5, 0x83, 0x3d, 0x7e, 0x19, 0x89,
+ 0xb9, 0x7a, 0x99, 0x8c, 0xfd, 0x78, 0xf5, 0x86, 0x95, 0x6d, 0xf5, 0x86, 0x91, 0x66, 0x80, 0x90,
+ 0xf1, 0x78, 0xc9, 0x6d, 0xf5, 0x7f, 0xd5, 0x66, 0xb0, 0x05, 0x87, 0x0d, 0x79, 0x4d, 0x92, 0x0d,
+ 0x79, 0x4d, 0x99, 0x80, 0x00, 0xa4, 0x5c, 0xe9, 0x41, 0x8e, 0xe2, 0x82, 0x70, 0xe9, 0x81, 0x88,
+ 0x21, 0x8e, 0x29, 0x84, 0x91, 0x7e, 0x6d, 0x82, 0x00, 0x7c, 0x84, 0xe8, 0x70, 0xe7, 0x86, 0xe1,
+}
+
+var ActionVerifiedUser = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x54, 0x00, 0x5c,
+ 0x64, 0xe9, 0x98, 0xb1, 0x80, 0x1d, 0x8b, 0xad, 0x87, 0x79, 0x95, 0xa4, 0xb0, 0x55, 0x8a, 0x79,
+ 0x7d, 0xa4, 0x1d, 0x73, 0xa4, 0x50, 0xe8, 0x64, 0x00, 0x80, 0x54, 0xe3, 0x78, 0xc0, 0x21, 0x70,
+ 0x70, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x78, 0x59, 0x84, 0x20, 0x2d, 0x8d, 0xd5, 0x72, 0x01, 0x98,
+ 0x74, 0x78, 0x94, 0xe1,
+}
+
+var ActionViewAgenda = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x84, 0xe6, 0x5c,
+ 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x98, 0xb0, 0x80, 0x19, 0x81, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0xc4, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8,
+ 0x88, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x58, 0xe6, 0x5c, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x98, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80,
+ 0x84, 0x84, 0x84, 0xe7, 0xc4, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x60,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var ActionViewArray = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x98, 0xe7, 0x8c,
+ 0xe8, 0x64, 0xe6, 0x60, 0xe9, 0xb4, 0xe3, 0xb8, 0x4c, 0xe9, 0xb4, 0xe7, 0x8c, 0xe8, 0x64, 0xe7,
+ 0x74, 0xe2, 0x70, 0x98, 0xe7, 0xa4, 0xe8, 0x64, 0xe6, 0x70, 0xe9, 0xb4, 0xe1,
+}
+
+var ActionViewCarousel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x9c, 0xe7, 0xa8,
+ 0xe8, 0x60, 0xe6, 0x6c, 0xe9, 0xbc, 0xe2, 0x58, 0x94, 0xe7, 0x90, 0xe8, 0x68, 0xe6, 0x58, 0xe9,
+ 0xac, 0xe3, 0xc0, 0x54, 0xe9, 0xac, 0xe7, 0x90, 0xe8, 0x68, 0xe7, 0x70, 0xe1,
+}
+
+var ActionViewColumn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x98, 0xe7, 0x94,
+ 0xe8, 0x64, 0xe6, 0x78, 0xe9, 0xb4, 0xe2, 0x60, 0x98, 0xe7, 0x94, 0xe8, 0x64, 0xe6, 0x60, 0xe9,
+ 0xb4, 0xe3, 0xb0, 0x4c, 0xe9, 0xb4, 0xe7, 0x94, 0xe8, 0x64, 0xe6, 0x90, 0xe1,
+}
+
+var ActionViewDay = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0xa4, 0xe7, 0xcc,
+ 0xe9, 0x74, 0xe6, 0x58, 0xe9, 0x8c, 0xe3, 0xc8, 0x4c, 0xe6, 0x5c, 0xb0, 0xe9, 0x7e, 0x80, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x98, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xc4, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x74, 0xb0, 0x80, 0xe9, 0x7e,
+ 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe2, 0x58, 0x5c, 0xe9, 0x8c, 0xe7, 0xcc, 0xe8, 0x5c, 0xe6, 0x58,
+ 0xe1,
+}
+
+var ActionViewHeadline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x8c, 0xe7, 0xc4,
+ 0xe9, 0x78, 0xe6, 0x60, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xc4, 0xe9, 0x78, 0xe6, 0x60, 0xe9,
+ 0x88, 0xe3, 0x80, 0x60, 0xe7, 0xc4, 0xe9, 0x78, 0xe6, 0x60, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe9,
+ 0x88, 0xe7, 0xc4, 0xe9, 0x78, 0xe6, 0x60, 0xe1,
+}
+
+var ActionViewList = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x88, 0xe7, 0x90,
+ 0xe9, 0x70, 0xe6, 0x60, 0xe9, 0x90, 0xe3, 0x80, 0x94, 0xe7, 0x90, 0xe9, 0x70, 0xe6, 0x60, 0xe9,
+ 0x90, 0xe3, 0x80, 0x58, 0xe7, 0x90, 0xe9, 0x70, 0xe6, 0x60, 0xe9, 0x90, 0xe3, 0x94, 0x94, 0xe7,
+ 0xb0, 0xe9, 0x70, 0xe6, 0x74, 0xe9, 0x90, 0xe3, 0x80, 0x94, 0xe7, 0xb0, 0xe9, 0x70, 0xe6, 0x74,
+ 0xe9, 0x90, 0xe3, 0x80, 0x48, 0xe9, 0x90, 0xe7, 0xb0, 0xe9, 0x70, 0xe6, 0x74, 0xe1,
+}
+
+var ActionViewModule = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x7c, 0xe7, 0x94,
+ 0xe8, 0x64, 0xe6, 0x60, 0xe9, 0x98, 0xe3, 0x80, 0x9c, 0xe7, 0x94, 0xe8, 0x80, 0xe6, 0x60, 0xe9,
+ 0x98, 0xe3, 0x98, 0x80, 0xe7, 0x94, 0xe8, 0x80, 0xe6, 0x78, 0xe9, 0x98, 0xe3, 0x98, 0x80, 0xe7,
+ 0x94, 0xe8, 0x80, 0xe6, 0x90, 0xe9, 0x98, 0xe2, 0x78, 0x7c, 0xe7, 0x94, 0xe8, 0x64, 0xe6, 0x78,
+ 0xe9, 0x98, 0xe3, 0x98, 0x68, 0xe9, 0x98, 0xe7, 0x94, 0xe8, 0x64, 0xe6, 0x90, 0xe1,
+}
+
+var ActionViewQuilt = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x98, 0xe7, 0x94,
+ 0xe8, 0x80, 0xe6, 0x78, 0xe9, 0x98, 0xe2, 0x60, 0x98, 0xe7, 0x94, 0xe8, 0x64, 0xe6, 0x60, 0xe9,
+ 0xb4, 0xe3, 0xb0, 0x80, 0xe7, 0x94, 0xe8, 0x80, 0xe6, 0x90, 0xe9, 0x98, 0xe2, 0x78, 0x64, 0xe9,
+ 0x98, 0xe7, 0xac, 0xe8, 0x64, 0xe6, 0x78, 0xe1,
+}
+
+var ActionViewStream = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x98, 0xe7, 0xc4,
+ 0xe8, 0x80, 0xe6, 0x60, 0xe9, 0x98, 0xe3, 0x80, 0x4c, 0xe9, 0x98, 0xe7, 0xc4, 0xe8, 0x64, 0xe6,
+ 0x60, 0xe1,
+}
+
+var ActionViewWeek = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x64, 0xe6, 0x5c,
+ 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb0, 0xb0, 0x80, 0x19, 0x81, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0x8c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8,
+ 0x68, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0xb8, 0x80, 0xe7, 0x74, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb0, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80,
+ 0x84, 0x84, 0x84, 0xe7, 0x8c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x68,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x64, 0x80, 0xe7, 0x74, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb0, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84,
+ 0x84, 0x84, 0xe7, 0x8c, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x68, 0xb0,
+ 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var ActionVisibility = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x62, 0xa0, 0x6c,
+ 0x62, 0x75, 0x6d, 0x39, 0x77, 0x54, 0x80, 0xb2, 0x75, 0x83, 0xc9, 0x88, 0x98, 0x9e, 0xac, 0x9e,
+ 0x05, 0x8a, 0x80, 0x8d, 0x92, 0xc9, 0x79, 0xac, 0x62, 0x8d, 0x7c, 0x39, 0x77, 0x05, 0x74, 0x62,
+ 0x54, 0x62, 0xe3, 0x80, 0xb2, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b, 0x6c, 0x6c, 0x92, 0x7d,
+ 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b, 0x94, 0x6c, 0x94, 0xe3, 0x80,
+ 0x60, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x92, 0xb1, 0x82, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe1,
+}
+
+var ActionVisibilityOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x6c, 0xb1, 0x85,
+ 0x85, 0x80, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x80, 0x4d, 0x81, 0xbd, 0x7f, 0x85, 0x82, 0x4d, 0x7f,
+ 0xa9, 0x83, 0x20, 0xd9, 0x85, 0xd9, 0x85, 0xb2, 0x05, 0x83, 0x7d, 0x7d, 0x69, 0x85, 0x39, 0x7a,
+ 0xe1, 0x86, 0x81, 0x76, 0x89, 0x7c, 0x39, 0x77, 0x68, 0x62, 0xfd, 0x69, 0x62, 0x35, 0x7d, 0x80,
+ 0x85, 0x7a, 0x81, 0x80, 0x09, 0x78, 0x69, 0x81, 0x20, 0x51, 0x84, 0x51, 0x84, 0xb0, 0x21, 0x81,
+ 0x91, 0x7f, 0x5d, 0x82, 0x4d, 0x7f, 0xa9, 0x83, 0x4d, 0x7f, 0xe2, 0x58, 0x8d, 0x70, 0x21, 0x91,
+ 0x84, 0x91, 0x84, 0xe9, 0x80, 0xe9, 0x80, 0xa0, 0x2d, 0x6e, 0x99, 0x78, 0x91, 0x6b, 0x09, 0x7c,
+ 0x54, 0x80, 0xb1, 0x75, 0x83, 0xc9, 0x88, 0x98, 0x9e, 0xac, 0x9e, 0x19, 0x83, 0x80, 0x11, 0x86,
+ 0x69, 0x7f, 0xc5, 0x88, 0x51, 0x7e, 0x20, 0xd9, 0x80, 0xd9, 0x80, 0x03, 0x75, 0x8f, 0xa8, 0xa4,
+ 0x75, 0x91, 0x8d, 0x6e, 0x5c, 0x58, 0x8d, 0x70, 0xe2, 0x11, 0x77, 0x99, 0x7b, 0x20, 0x19, 0x83,
+ 0x19, 0x83, 0xb2, 0xe9, 0x7f, 0x71, 0x80, 0xd9, 0x7f, 0xdd, 0x80, 0xd9, 0x7f, 0x51, 0x81, 0x80,
+ 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x75, 0x80, 0x80, 0xe1, 0x80, 0xf1, 0x7f, 0x4d, 0x81,
+ 0xd9, 0x7f, 0x20, 0x19, 0x83, 0x19, 0x83, 0xa0, 0x11, 0x83, 0x99, 0x89, 0x95, 0x81, 0x94, 0x80,
+ 0x94, 0xb1, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b, 0x6c, 0x6c, 0x80, 0x6d, 0x7e, 0x69, 0x80, 0xf1,
+ 0x7c, 0x11, 0x81, 0x99, 0x7b, 0xe3, 0x9d, 0x88, 0x71, 0x7e, 0x20, 0x4d, 0x86, 0x4d, 0x86, 0x00,
+ 0x8c, 0x80, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x20, 0xad, 0x7f, 0x09, 0x80,
+ 0xe1,
+}
+
+var ActionWatchLater = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe3,
+ 0x85, 0x88, 0x4d, 0x9c, 0x00, 0x7c, 0x84, 0xe8, 0x6c, 0xe7, 0x86, 0xe9, 0x81, 0x8a, 0x21, 0x92,
+ 0x59, 0x85, 0x81, 0x7e, 0x75, 0x82, 0xe1,
+}
+
+var ActionWork = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe7, 0x70,
+ 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x70, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c,
+ 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x68, 0x80, 0xe7, 0x70, 0xe8, 0x60, 0xe7, 0x90,
+ 0xe9, 0x88, 0xe1,
+}
+
+var ActionYoutubeSearchedFor = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x8a, 0x88, 0xe7,
+ 0x69, 0x7e, 0x20, 0x75, 0x7f, 0x75, 0x7f, 0xb1, 0xf5, 0x81, 0xbd, 0x7d, 0x25, 0x83, 0xc9, 0x7a,
+ 0x25, 0x83, 0x8d, 0x77, 0x80, 0xd1, 0x78, 0x31, 0x7a, 0x05, 0x73, 0x66, 0x05, 0x73, 0x80, 0x09,
+ 0x71, 0x68, 0x05, 0x71, 0x76, 0xe6, 0x58, 0x20, 0xb1, 0x87, 0x90, 0x00, 0x78, 0x76, 0xe7, 0x05,
+ 0x79, 0xb0, 0x05, 0x80, 0x76, 0x09, 0x84, 0x05, 0x77, 0x92, 0x05, 0x77, 0x91, 0x92, 0x09, 0x84,
+ 0x92, 0x92, 0xf9, 0x7b, 0x92, 0x6e, 0x92, 0xb0, 0xb5, 0x7e, 0x80, 0x7d, 0x7d, 0xb9, 0x7f, 0x61,
+ 0x7c, 0x3d, 0x7f, 0x20, 0x0d, 0x7d, 0xf5, 0x82, 0xb1, 0xf1, 0x81, 0x25, 0x81, 0x31, 0x84, 0xd1,
+ 0x81, 0x99, 0x86, 0xd1, 0x81, 0x3d, 0x83, 0x80, 0x2d, 0x86, 0xd1, 0x7e, 0x71, 0x88, 0xe1, 0x7c,
+ 0x20, 0x8d, 0x80, 0x8d, 0x80, 0xe8, 0x8e, 0x20, 0x05, 0x8a, 0xfd, 0x89, 0x00, 0xfd, 0x93, 0x9c,
+ 0x20, 0x09, 0x76, 0x6c, 0xe1,
+}
+
+var ActionZoomIn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x88, 0xe7, 0x6d,
+ 0x7e, 0x20, 0x71, 0x7f, 0x75, 0x7f, 0xa1, 0xd1, 0x86, 0x31, 0x81, 0x90, 0x39, 0x7e, 0x90, 0x76,
+ 0x90, 0xd1, 0x73, 0x31, 0x82, 0x5c, 0x76, 0x5c, 0x81, 0x5c, 0xd1, 0x73, 0x5c, 0x76, 0xd1, 0x73,
+ 0x90, 0x76, 0x90, 0xb0, 0x39, 0x83, 0x80, 0x31, 0x86, 0xd1, 0x7e, 0x75, 0x88, 0xdd, 0x7c, 0x20,
+ 0x8d, 0x80, 0x91, 0x80, 0xe9, 0x95, 0x81, 0x20, 0x94, 0xfd, 0x89, 0x00, 0xfd, 0x90, 0x9c, 0x20,
+ 0x05, 0x76, 0x6c, 0xe3, 0x68, 0x80, 0xa0, 0x05, 0x76, 0x88, 0x64, 0xfd, 0x7f, 0x64, 0x76, 0x82,
+ 0x05, 0x76, 0x64, 0x76, 0x64, 0x88, 0x05, 0x76, 0x88, 0x76, 0xfd, 0x7f, 0x88, 0x76, 0x88, 0xe3,
+ 0x8a, 0x70, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x74, 0xe9, 0x78, 0xe6, 0x6c, 0xe8, 0x74, 0xe7, 0x88,
+ 0xe8, 0x6c, 0xe7, 0x84, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x84, 0xe1,
+}
+
+var ActionZoomOut = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x88, 0xe7, 0x6d,
+ 0x7e, 0x20, 0x71, 0x7f, 0x75, 0x7f, 0xa1, 0xd1, 0x86, 0x31, 0x81, 0x90, 0x39, 0x7e, 0x90, 0x76,
+ 0x90, 0xd1, 0x73, 0x31, 0x82, 0x5c, 0x76, 0x5c, 0x81, 0x5c, 0xd1, 0x73, 0x5c, 0x76, 0xd1, 0x73,
+ 0x90, 0x76, 0x90, 0xb0, 0x39, 0x83, 0x80, 0x31, 0x86, 0xd1, 0x7e, 0x75, 0x88, 0xdd, 0x7c, 0x20,
+ 0x8d, 0x80, 0x91, 0x80, 0xe9, 0x95, 0x81, 0x20, 0x94, 0xfd, 0x89, 0x00, 0xfd, 0x90, 0x9c, 0x20,
+ 0x05, 0x76, 0x6c, 0xe3, 0x68, 0x80, 0xa0, 0x05, 0x76, 0x88, 0x64, 0xfd, 0x7f, 0x64, 0x76, 0x82,
+ 0x05, 0x76, 0x64, 0x76, 0x64, 0x88, 0x05, 0x76, 0x88, 0x76, 0xfd, 0x7f, 0x88, 0x76, 0x88, 0xe2,
+ 0x6c, 0x74, 0xe7, 0x94, 0xe9, 0x84, 0xe6, 0x6c, 0xe1,
+}
+
+var AlertAddAlert = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x7c, 0x05, 0x91,
+ 0xb0, 0x80, 0x35, 0x82, 0xc9, 0x81, 0xfd, 0x83, 0xfd, 0x83, 0xfd, 0x83, 0x90, 0xfd, 0x83, 0x39,
+ 0x7e, 0xfd, 0x83, 0x05, 0x7c, 0xe7, 0x0d, 0x78, 0xe3, 0xbd, 0x91, 0xa1, 0x77, 0xe8, 0x7a, 0xb0,
+ 0x80, 0x81, 0x79, 0x81, 0x7b, 0x11, 0x74, 0x6d, 0x75, 0xa1, 0x72, 0xe8, 0x31, 0x6e, 0xa0, 0x31,
+ 0x83, 0x6d, 0x6c, 0xc5, 0x81, 0x56, 0x80, 0x56, 0x90, 0xd1, 0x7c, 0x6d, 0x81, 0xd1, 0x7c, 0x31,
+ 0x83, 0xe9, 0x71, 0x81, 0xa0, 0xbd, 0x76, 0x11, 0x71, 0x3d, 0x72, 0x81, 0x76, 0x3d, 0x72, 0x7a,
+ 0xe9, 0xa5, 0x8b, 0x00, 0x5c, 0xe1, 0x8c, 0xe8, 0x9e, 0xe7, 0xc8, 0xe9, 0xe1, 0x7d, 0x20, 0xc5,
+ 0x7b, 0xc5, 0x7b, 0xe2, 0x90, 0x05, 0x81, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7,
+ 0x74, 0xe8, 0x7a, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x05, 0x84,
+ 0xe1,
+}
+
+var AlertError = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x84, 0xbc, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0x98, 0xe1,
+}
+
+var AlertErrorOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x8c, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe7, 0x78, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x98, 0xe7, 0x78, 0xe3, 0xfd, 0x81,
+ 0x6c, 0xa0, 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93,
+ 0xa8, 0x81, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0xa0,
+ 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91,
+ 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var AlertWarning = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0xa4, 0xe7, 0xd8,
+ 0x01, 0x80, 0x58, 0x54, 0xa4, 0xe3, 0xb0, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var AVAddToQueue = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xac, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe2, 0x90, 0x78, 0xe9, 0x88, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x74, 0xe9,
+ 0x78, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe1,
+}
+
+var AVAirplay = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0xa8, 0xe7, 0xb0, 0x00,
+ 0x80, 0x90, 0xe1,
+}
+
+var AVAlbum = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xba, 0xb0, 0x09, 0x7b, 0x80, 0x6e,
+ 0xf9, 0x7b, 0x6e, 0x6e, 0x92, 0x09, 0x84, 0x6e, 0x92, 0x6e, 0x92, 0x09, 0x84, 0x92, 0x92, 0xf9,
+ 0x7b, 0x92, 0x6e, 0x92, 0xe3, 0x80, 0x6a, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84,
+ 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c,
+ 0xe1,
+}
+
+var AVArtTrack = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x84, 0xe6, 0x88,
+ 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe6, 0x88, 0xe9, 0x88, 0xe7, 0xa0, 0xe9,
+ 0x78, 0xe2, 0x88, 0x94, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x88, 0xe9, 0x88, 0xe3, 0x78, 0x60, 0xe9,
+ 0x98, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe8, 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78,
+ 0xe7, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe3, 0x7a, 0x98, 0x22, 0x81,
+ 0x7b, 0x74, 0x81, 0x7c, 0x85, 0x84, 0x81, 0x7d, 0xfd, 0x7c, 0x00, 0x5e, 0x8c, 0xe7, 0x9c, 0xe1,
+}
+
+var AVAVTimer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x94, 0xb0, 0x80,
+ 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x92, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c,
+ 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe3, 0x80, 0x48, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x29,
+ 0x7c, 0xb1, 0xc9, 0x86, 0xf9, 0x80, 0x98, 0xcd, 0x86, 0x98, 0xd9, 0x8d, 0x80, 0xbd, 0x87, 0xbd,
+ 0x79, 0x9c, 0x64, 0x9c, 0x90, 0x64, 0xbd, 0x79, 0x64, 0x64, 0xb0, 0x80, 0xa5, 0x7c, 0x31, 0x81,
+ 0x91, 0x79, 0x29, 0x83, 0x29, 0x77, 0x00, 0x80, 0x84, 0x22, 0xd5, 0x82, 0x2d, 0x7d, 0x69, 0x72,
+ 0x69, 0x72, 0xfd, 0x7f, 0x0d, 0x80, 0xa0, 0xd9, 0x70, 0xe5, 0x74, 0x5c, 0x1d, 0x7a, 0x5c, 0x80,
+ 0xb0, 0x80, 0xf1, 0x89, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0x81, 0xa4, 0xf1, 0x89, 0xa4, 0x80,
+ 0xf1, 0x89, 0x5c, 0xfd, 0x7f, 0x5c, 0xe6, 0x7c, 0xe3, 0x9c, 0xa4, 0xb0, 0x80, 0xe9, 0x7e, 0x19,
+ 0x7f, 0x7c, 0x7c, 0x7c, 0x92, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84,
+ 0x19, 0x7f, 0x84, 0x7c, 0xe3, 0x50, 0x80, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84,
+ 0x92, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84,
+ 0xe1,
+}
+
+var AVBrandingWatermark = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x80, 0xe8,
+ 0x84, 0xe7, 0xa4, 0xe9, 0x98, 0xe1,
+}
+
+var AVCallToAction = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x5c, 0xe9,
+ 0x74, 0xe7, 0xc8, 0xe9, 0x8c, 0xe1,
+}
+
+var AVClosedCaption = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x60, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x7c, 0x7c, 0xe7, 0x7a, 0xe9,
+ 0x7e, 0xe7, 0x78, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x7e, 0xe7, 0x86, 0xe9, 0x84, 0xb0, 0x80, 0x19,
+ 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe7, 0x74, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c,
+ 0x7c, 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xe7, 0x8c, 0xb0, 0x1d,
+ 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0xe9, 0x84, 0xe3, 0x9c, 0x80, 0xe7, 0x7a, 0xe9, 0x7e,
+ 0xe7, 0x78, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x7e, 0xe7, 0x86, 0xe9, 0x84, 0xb0, 0x80, 0x19, 0x81,
+ 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe7, 0x74, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c,
+ 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xe7, 0x8c, 0xb0, 0x1d, 0x81,
+ 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0xe9, 0x84, 0xe1,
+}
+
+var AVEqualizer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0xa0, 0xe7, 0x90,
+ 0xe8, 0x60, 0xe7, 0x70, 0xe9, 0xc0, 0xe2, 0x60, 0xa0, 0xe7, 0x90, 0xe8, 0x80, 0xe6, 0x60, 0xe9,
+ 0xa0, 0xe3, 0xb0, 0x54, 0xe9, 0xac, 0xe7, 0x90, 0xe8, 0x74, 0xe7, 0x70, 0xe1,
+}
+
+var AVExplicit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0x98, 0xe7, 0x70, 0xe9,
+ 0x88, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x88, 0xe6, 0x74, 0xe8,
+ 0x6c, 0xe7, 0x98, 0xe9, 0x88, 0xe1,
+}
+
+var AVFastForward = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x98, 0x20, 0xa2,
+ 0x68, 0x00, 0x60, 0x68, 0xe9, 0xb0, 0xe3, 0xa4, 0x50, 0xe9, 0xb0, 0x21, 0xa2, 0x68, 0x5e, 0x68,
+ 0xe1,
+}
+
+var AVFastRewind = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x98, 0xe8, 0x68,
+ 0x00, 0x5a, 0x80, 0x20, 0xa2, 0x98, 0xe3, 0x82, 0x68, 0x20, 0xa2, 0x98, 0xe8, 0x68, 0x00, 0x7e,
+ 0x80, 0xe1,
+}
+
+var AVFeaturedPlayList = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80, 0x7c, 0xe6, 0x5c, 0xe9,
+ 0x78, 0xe7, 0xa4, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x5c, 0xe9, 0x78, 0xe7, 0xa4, 0xe9, 0x88,
+ 0xe1,
+}
+
+var AVFeaturedVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80, 0x80, 0xe6, 0x5c, 0xe8,
+ 0x64, 0xe7, 0xa4, 0xe9, 0x9c, 0xe1,
+}
+
+var AVFiberDVR = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x96, 0x7a, 0xe7, 0x88,
+ 0xe9, 0x84, 0xe7, 0x78, 0xe2, 0x62, 0x7a, 0xe7, 0x88, 0xe9, 0x8c, 0xe6, 0x62, 0xe2, 0xa4, 0x5c,
+ 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x70, 0x86, 0xb0,
+ 0x80, 0xb5, 0x81, 0xb5, 0x7e, 0x86, 0x7a, 0x86, 0xe6, 0x5c, 0xe8, 0x74, 0xe7, 0x8e, 0xb0, 0xb5,
+ 0x81, 0x80, 0x86, 0x4d, 0x81, 0x86, 0x86, 0xe9, 0x8c, 0xe3, 0x3d, 0x89, 0x86, 0xe7, 0x7a, 0x20,
+ 0x81, 0x7c, 0x68, 0xe7, 0x86, 0x21, 0x84, 0xdd, 0x86, 0x84, 0x25, 0x79, 0xe7, 0x86, 0x20, 0x81,
+ 0x7c, 0x98, 0xe2, 0xa4, 0x7e, 0xb0, 0x80, 0x35, 0x81, 0x35, 0x7f, 0x4d, 0x82, 0x35, 0x7e, 0xcd,
+ 0x82, 0x00, 0xa4, 0x8c, 0xe7, 0x7a, 0x20, 0x4d, 0x7e, 0x78, 0xe6, 0x96, 0xe9, 0x88, 0xe7, 0x7a,
+ 0xe8, 0x74, 0xe7, 0x8e, 0xb0, 0xb5, 0x81, 0x80, 0x86, 0x4d, 0x81, 0x86, 0x86, 0xe9, 0x84, 0xe1,
+}
+
+var AVFiberManualRecord = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x80, 0xd1, 0xa0,
+ 0xa0, 0x00, 0x04, 0xc0, 0x80, 0xa0, 0xa0, 0x00, 0x04, 0x40, 0x80, 0xe1,
+}
+
+var AVFiberNew = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x72, 0x8c,
+ 0xe7, 0x99, 0x7d, 0x20, 0xe9, 0x7a, 0x72, 0xe9, 0x8e, 0xe6, 0x5e, 0xe8, 0x74, 0xe7, 0x81, 0x82,
+ 0x20, 0x8a, 0x8e, 0xe9, 0x72, 0xe6, 0x72, 0xe9, 0x98, 0xe3, 0x94, 0x85, 0x76, 0xe7, 0x76, 0xe9,
+ 0x3d, 0x82, 0xe7, 0x8a, 0xe9, 0x85, 0x82, 0xe7, 0x76, 0xe9, 0x3d, 0x82, 0xe7, 0x8a, 0xe8, 0x8c,
+ 0xe7, 0x70, 0xe8, 0x74, 0xe7, 0x90, 0xe9, 0x85, 0x82, 0xe2, 0xa2, 0x88, 0xb0, 0x80, 0x19, 0x81,
+ 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe7, 0x70, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c,
+ 0xe8, 0x74, 0xe7, 0x81, 0x82, 0xe9, 0x05, 0x89, 0xe7, 0x41, 0x82, 0xe9, 0xfd, 0x78, 0xe7, 0x81,
+ 0x82, 0xe9, 0x05, 0x87, 0xe7, 0x41, 0x82, 0xe8, 0x74, 0xe6, 0xa2, 0xe9, 0x94, 0xe1,
+}
+
+var AVFiberPin = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x66, 0x7a, 0xe7, 0x88,
+ 0xe9, 0x84, 0xe7, 0x78, 0xe2, 0xa0, 0x60, 0xe6, 0x60, 0xb0, 0xc9, 0x7d, 0x80, 0x05, 0x7c, 0xc9,
+ 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x39, 0x82, 0xc9, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xc0, 0xb0, 0x39, 0x82, 0x80, 0x88, 0x39, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xc9,
+ 0x7d, 0x39, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x7e, 0xb0, 0x80, 0xb5, 0x81, 0xb5, 0x7e, 0x86,
+ 0x7a, 0x86, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x60, 0xe8, 0x74, 0xe7, 0x8e, 0xb0, 0xb5, 0x81, 0x80,
+ 0x86, 0x4d, 0x81, 0x86, 0x86, 0xe9, 0x84, 0xe3, 0x8e, 0x8e, 0xe7, 0x7a, 0xe8, 0x74, 0xe7, 0x86,
+ 0xe9, 0x98, 0xe3, 0x9e, 0x80, 0xe7, 0x99, 0x7d, 0x20, 0xe9, 0x7a, 0x72, 0xe9, 0x8e, 0xe6, 0x8c,
+ 0xe8, 0x74, 0xe7, 0x81, 0x82, 0x20, 0x8a, 0x8e, 0xe9, 0x72, 0xe6, 0xa0, 0xe9, 0x98, 0xe1,
+}
+
+var AVFiberSmartRecord = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x85, 0x70, 0xe8,
+ 0xb5, 0x74, 0xb0, 0xa9, 0x84, 0xa5, 0x81, 0x90, 0x15, 0x86, 0x90, 0x4d, 0x8b, 0x90, 0xa9, 0x7c,
+ 0xa9, 0x89, 0x70, 0x4d, 0x8b, 0xe9, 0x31, 0x84, 0xa0, 0xe9, 0x90, 0xb5, 0x8d, 0xac, 0x75, 0x87,
+ 0xac, 0x80, 0x80, 0xe9, 0x90, 0x4d, 0x72, 0x94, 0x85, 0x70, 0xe2, 0x54, 0x80, 0xd1, 0xa0, 0xa0,
+ 0x00, 0x04, 0xc0, 0x80, 0xa0, 0xa0, 0x00, 0x04, 0x40, 0x80, 0xe1,
+}
+
+var AVForward10 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x84, 0xb0, 0x80,
+ 0xcd, 0x88, 0x35, 0x87, 0xa0, 0xa0, 0xa0, 0x90, 0xa0, 0xcd, 0x78, 0xa0, 0x60, 0xe7, 0x78, 0xb0,
+ 0x80, 0x99, 0x86, 0x99, 0x7a, 0x98, 0x68, 0x98, 0x91, 0x68, 0x99, 0x7a, 0x68, 0x68, 0x69, 0x85,
+ 0x68, 0x98, 0x68, 0xe9, 0x90, 0x20, 0x94, 0x6c, 0x00, 0x80, 0x54, 0xe9, 0x90, 0xb0, 0x35, 0x77,
+ 0x80, 0x60, 0x35, 0x87, 0x60, 0xa0, 0xe3, 0xb5, 0x8d, 0x8c, 0xe6, 0x78, 0xe9, 0x81, 0x79, 0x20,
+ 0x7c, 0x99, 0x80, 0xe9, 0x99, 0x7e, 0x20, 0x81, 0x83, 0xb5, 0x7e, 0xe7, 0x35, 0x80, 0xe8, 0x90,
+ 0xe3, 0x81, 0x88, 0x81, 0x7c, 0xb0, 0x80, 0x99, 0x80, 0xe9, 0x7f, 0x35, 0x81, 0xcd, 0x7f, 0x99,
+ 0x81, 0x96, 0xb5, 0x7f, 0xcd, 0x80, 0x69, 0x7f, 0x19, 0x81, 0x69, 0x7f, 0x81, 0x80, 0x19, 0x7f,
+ 0xb5, 0x80, 0x4d, 0x7f, 0x35, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x35, 0x7f, 0xe9, 0x7f, 0xcd, 0x7e,
+ 0xcd, 0x7f, 0x4d, 0x7f, 0x99, 0x7f, 0x19, 0x7f, 0x4d, 0x7f, 0x81, 0x7f, 0x4d, 0x7f, 0x69, 0x7f,
+ 0xe9, 0x7e, 0xcd, 0x7f, 0x7e, 0xcd, 0x7f, 0x69, 0x7e, 0xe8, 0x86, 0xb0, 0x80, 0x69, 0x7f, 0x19,
+ 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x69, 0x7e, 0x96, 0x4d, 0x80, 0x35, 0x7f, 0x99, 0x80, 0xe9, 0x7e,
+ 0x99, 0x80, 0x81, 0x7f, 0xe9, 0x80, 0x4d, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x35, 0x81, 0xcd, 0x7f,
+ 0xcd, 0x80, 0x19, 0x80, 0x35, 0x81, 0x35, 0x80, 0xb5, 0x80, 0x69, 0x80, 0xe9, 0x80, 0xb5, 0x80,
+ 0x81, 0x80, 0xb5, 0x80, 0x99, 0x80, 0x19, 0x81, 0x35, 0x80, 0x82, 0x35, 0x80, 0x99, 0x81, 0xe9,
+ 0x81, 0x81, 0xe3, 0x4d, 0x7e, 0x4d, 0x7e, 0xb0, 0x80, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0xe9, 0x7f,
+ 0x7e, 0x96, 0xe9, 0x7f, 0x81, 0x7f, 0xcd, 0x7f, 0x69, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0x99, 0x7f,
+ 0xb5, 0x7f, 0xb5, 0x7f, 0xe9, 0x7f, 0x81, 0x7f, 0xe9, 0x7f, 0x99, 0x7f, 0x80, 0x81, 0x7f, 0x19,
+ 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x99, 0x7f, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xcd, 0x7f, 0x99,
+ 0x80, 0xe9, 0x7f, 0x99, 0x80, 0xe9, 0x7f, 0x82, 0xe9, 0xe9, 0x81, 0xb0, 0x80, 0x69, 0x80, 0x80,
+ 0xb5, 0x80, 0x19, 0x80, 0x82, 0x96, 0x19, 0x80, 0x81, 0x80, 0x35, 0x80, 0x99, 0x80, 0x35, 0x80,
+ 0x4d, 0x80, 0x69, 0x80, 0x4d, 0x80, 0x4d, 0x80, 0x19, 0x80, 0x81, 0x80, 0x19, 0x80, 0x69, 0x80,
+ 0x80, 0x81, 0x80, 0xe9, 0x7f, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x99,
+ 0x7f, 0x35, 0x80, 0x69, 0x7f, 0x19, 0x80, 0x69, 0x7f, 0x19, 0x80, 0x7e, 0xe9, 0x19, 0x7e, 0xe1,
+}
+
+var AVForward30 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x7b, 0x86, 0xe7,
+ 0xe9, 0x80, 0xb0, 0x69, 0x80, 0x80, 0xb5, 0x80, 0xe9, 0x7f, 0x82, 0xb5, 0x7f, 0x90, 0x4d, 0x80,
+ 0x81, 0x7f, 0x4d, 0x80, 0x19, 0x7f, 0xb0, 0x80, 0xcd, 0x7f, 0x80, 0xb5, 0x7f, 0xe9, 0x7f, 0x99,
+ 0x7f, 0x96, 0xe9, 0x7f, 0xb5, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xcd, 0x7f, 0xcd, 0x7f, 0x99, 0x7f,
+ 0xcd, 0x7f, 0xb5, 0x7f, 0xe9, 0x7f, 0x81, 0x7f, 0xe9, 0x7f, 0xb5, 0x7f, 0x80, 0x99, 0x7f, 0x19,
+ 0x80, 0xb5, 0x7f, 0x19, 0x80, 0x99, 0x7f, 0x35, 0x80, 0xcd, 0x7f, 0x35, 0x80, 0xcd, 0x7f, 0x4d,
+ 0x80, 0xe9, 0x7f, 0x35, 0x80, 0xe9, 0x7f, 0x69, 0x80, 0xe7, 0x4d, 0x7e, 0xb0, 0x80, 0x99, 0x7f,
+ 0x19, 0x80, 0x4d, 0x7f, 0x35, 0x80, 0x7e, 0x92, 0x4d, 0x80, 0x81, 0x7f, 0x99, 0x80, 0x4d, 0x7f,
+ 0x81, 0x80, 0x99, 0x7f, 0xe9, 0x80, 0x81, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x19, 0x81, 0xcd, 0x7f,
+ 0xb0, 0x69, 0x80, 0x80, 0xcd, 0x80, 0x19, 0x80, 0x35, 0x81, 0x35, 0x80, 0x92, 0xb5, 0x80, 0x4d,
+ 0x80, 0xe9, 0x80, 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x99, 0x80, 0xcd, 0x80, 0x35, 0x80, 0xb5,
+ 0x80, 0x35, 0x80, 0x19, 0x81, 0xb0, 0x80, 0x35, 0x80, 0x80, 0x69, 0x80, 0xe9, 0x7f, 0x81, 0x80,
+ 0x92, 0xe9, 0x7f, 0x4d, 0x80, 0xb5, 0x7f, 0x81, 0x80, 0xcd, 0x7f, 0x4d, 0x80, 0x99, 0x7f, 0x69,
+ 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x69, 0x7f, 0x4d, 0x80, 0xb0, 0x81, 0x80, 0x35, 0x80, 0xcd, 0x80,
+ 0x69, 0x80, 0x19, 0x81, 0xcd, 0x80, 0x90, 0x69, 0x80, 0xcd, 0x80, 0x69, 0x80, 0x35, 0x81, 0xb0,
+ 0x80, 0x69, 0x80, 0xe9, 0x7f, 0xcd, 0x80, 0xcd, 0x7f, 0x19, 0x81, 0x92, 0x99, 0x7f, 0x99, 0x80,
+ 0x69, 0x7f, 0xcd, 0x80, 0x69, 0x7f, 0x69, 0x80, 0x7e, 0x81, 0x80, 0x35, 0x7f, 0x35, 0x80, 0xcd,
+ 0x7e, 0x35, 0x80, 0xb0, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0x80, 0xe9, 0x7e, 0xe9, 0x7f, 0x92, 0x4d,
+ 0x7f, 0xcd, 0x7f, 0x19, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x4d, 0x7f, 0x35, 0x7f, 0xcd,
+ 0x7f, 0x4d, 0x7f, 0xcd, 0x7f, 0xe9, 0x7e, 0xe7, 0xb5, 0x81, 0xb0, 0x80, 0x35, 0x80, 0x80, 0x4d,
+ 0x80, 0x19, 0x80, 0x69, 0x80, 0x9a, 0x19, 0x80, 0x35, 0x80, 0x4d, 0x80, 0x4d, 0x80, 0x35, 0x80,
+ 0x35, 0x80, 0x69, 0x80, 0x35, 0x80, 0x4d, 0x80, 0x19, 0x80, 0x81, 0x80, 0x19, 0x80, 0x69, 0x80,
+ 0x80, 0x81, 0x80, 0xe9, 0x7f, 0x4d, 0x80, 0xe9, 0x7f, 0x69, 0x80, 0xcd, 0x7f, 0x35, 0x80, 0xcd,
+ 0x7f, 0x4d, 0x80, 0x99, 0x7f, 0x19, 0x80, 0xb5, 0x7f, 0x19, 0x80, 0x81, 0x7f, 0x80, 0x99, 0x7f,
+ 0xe9, 0x7f, 0x69, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0xb5, 0x7f, 0x99, 0x7f, 0xb5, 0x7f, 0xcd, 0x7f,
+ 0x99, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xe9, 0x7f, 0x69, 0x7f, 0xe9, 0x7f, 0xe7, 0x19, 0x7f, 0xe8,
+ 0x86, 0xe3, 0x81, 0x8b, 0x81, 0x81, 0xb0, 0x80, 0x99, 0x80, 0xe9, 0x7f, 0x35, 0x81, 0xcd, 0x7f,
+ 0x99, 0x81, 0x96, 0x99, 0x7f, 0xe9, 0x80, 0x69, 0x7f, 0x35, 0x81, 0x69, 0x7f, 0x81, 0x80, 0x19,
+ 0x7f, 0xb5, 0x80, 0x4d, 0x7f, 0x35, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x35, 0x7f, 0xe9, 0x7f, 0xcd,
+ 0x7e, 0xcd, 0x7f, 0x4d, 0x7f, 0x99, 0x7f, 0x19, 0x7f, 0x4d, 0x7f, 0x81, 0x7f, 0x4d, 0x7f, 0x69,
+ 0x7f, 0xe9, 0x7e, 0xcd, 0x7f, 0x7e, 0xcd, 0x7f, 0x69, 0x7e, 0xe8, 0x86, 0xb0, 0x80, 0x69, 0x7f,
+ 0x19, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x69, 0x7e, 0x96, 0x4d, 0x80, 0x35, 0x7f, 0x99, 0x80, 0xe9,
+ 0x7e, 0x99, 0x80, 0x81, 0x7f, 0xe9, 0x80, 0x4d, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x35, 0x81, 0xcd,
+ 0x7f, 0xcd, 0x80, 0x19, 0x80, 0x35, 0x81, 0x35, 0x80, 0xb5, 0x80, 0x69, 0x80, 0xe9, 0x80, 0xb5,
+ 0x80, 0x81, 0x80, 0xb5, 0x80, 0x99, 0x80, 0x19, 0x81, 0x35, 0x80, 0x82, 0x35, 0x80, 0x99, 0x81,
+ 0xe9, 0x81, 0x81, 0xe3, 0x4d, 0x7e, 0x4d, 0x7e, 0xb0, 0x80, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0xe9,
+ 0x7f, 0x7e, 0x96, 0xe9, 0x7f, 0x81, 0x7f, 0xcd, 0x7f, 0x69, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0x99,
+ 0x7f, 0xb5, 0x7f, 0xb5, 0x7f, 0xe9, 0x7f, 0x81, 0x7f, 0xe9, 0x7f, 0x99, 0x7f, 0x80, 0x81, 0x7f,
+ 0x19, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x99, 0x7f, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xcd, 0x7f,
+ 0x99, 0x80, 0xe9, 0x7f, 0x99, 0x80, 0xe9, 0x7f, 0x82, 0xe9, 0xe9, 0x81, 0xb0, 0x80, 0x69, 0x80,
+ 0x80, 0xb5, 0x80, 0x19, 0x80, 0x82, 0x96, 0x19, 0x80, 0x81, 0x80, 0x35, 0x80, 0x99, 0x80, 0x35,
+ 0x80, 0x4d, 0x80, 0x69, 0x80, 0x4d, 0x80, 0x4d, 0x80, 0x19, 0x80, 0x81, 0x80, 0x19, 0x80, 0x69,
+ 0x80, 0x80, 0x81, 0x80, 0xe9, 0x7f, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80,
+ 0x99, 0x7f, 0x35, 0x80, 0x69, 0x7f, 0x19, 0x80, 0x69, 0x7f, 0x19, 0x80, 0x7e, 0xe9, 0x19, 0x7e,
+ 0xe2, 0x60, 0x84, 0xb0, 0x80, 0xcd, 0x88, 0x35, 0x87, 0xa0, 0xa0, 0xa0, 0x90, 0xa0, 0xcd, 0x78,
+ 0xa0, 0x60, 0xe7, 0x78, 0xb0, 0x80, 0x99, 0x86, 0x99, 0x7a, 0x98, 0x68, 0x98, 0x91, 0x68, 0x99,
+ 0x7a, 0x68, 0x68, 0x69, 0x85, 0x68, 0x98, 0x68, 0xe9, 0x90, 0x20, 0x94, 0x6c, 0x00, 0x80, 0x54,
+ 0xe9, 0x90, 0xb0, 0x35, 0x77, 0x80, 0x60, 0x35, 0x87, 0x60, 0xa0, 0xe1,
+}
+
+var AVForward5 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x84, 0xb0, 0x80,
+ 0xcd, 0x88, 0x35, 0x87, 0xa0, 0xa0, 0xa0, 0x90, 0xa0, 0xcd, 0x78, 0xa0, 0x60, 0xe7, 0x78, 0xb0,
+ 0x80, 0x99, 0x86, 0x99, 0x7a, 0x98, 0x68, 0x98, 0x91, 0x68, 0x99, 0x7a, 0x68, 0x68, 0x69, 0x85,
+ 0x68, 0x98, 0x68, 0xe9, 0x90, 0x20, 0x94, 0x6c, 0x00, 0x80, 0x54, 0xe9, 0x90, 0xb0, 0x35, 0x77,
+ 0x80, 0x60, 0x35, 0x87, 0x60, 0xa0, 0xe3, 0x69, 0x8d, 0xcd, 0x81, 0x20, 0x81, 0x80, 0xb5, 0x7b,
+ 0xe7, 0xcd, 0x84, 0xe9, 0x69, 0x81, 0xe7, 0x99, 0x7c, 0x20, 0xcd, 0x7f, 0xcd, 0x81, 0xb0, 0x19,
+ 0x80, 0x80, 0x19, 0x80, 0xe9, 0x7f, 0x35, 0x80, 0xe9, 0x7f, 0x91, 0x35, 0x80, 0xe9, 0x7f, 0x4d,
+ 0x80, 0xe9, 0x7f, 0x35, 0x80, 0xe9, 0x7f, 0x69, 0x80, 0xe9, 0x7f, 0xe7, 0x69, 0x80, 0xb0, 0x69,
+ 0x80, 0x80, 0xcd, 0x80, 0x19, 0x80, 0x19, 0x81, 0x35, 0x80, 0x92, 0x99, 0x80, 0x4d, 0x80, 0xcd,
+ 0x80, 0x99, 0x80, 0x69, 0x80, 0x81, 0x80, 0x81, 0x80, 0xe9, 0x80, 0x35, 0x80, 0xcd, 0x80, 0x35,
+ 0x80, 0x35, 0x81, 0xb0, 0x80, 0x69, 0x80, 0xe9, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x19, 0x81, 0x92,
+ 0xb5, 0x7f, 0x99, 0x80, 0x81, 0x7f, 0xe9, 0x80, 0x81, 0x7f, 0x81, 0x80, 0x19, 0x7f, 0x99, 0x80,
+ 0x35, 0x7f, 0x35, 0x80, 0xb5, 0x7e, 0x35, 0x80, 0xb0, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0xe9, 0x7f,
+ 0xe9, 0x7e, 0xcd, 0x7f, 0x92, 0x69, 0x7f, 0xb5, 0x7f, 0x19, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x81,
+ 0x7f, 0x69, 0x7f, 0x35, 0x7f, 0xb5, 0x7f, 0x4d, 0x7f, 0xb5, 0x7f, 0xe9, 0x7e, 0xe7, 0xb5, 0x81,
+ 0xb0, 0x80, 0x69, 0x80, 0x35, 0x80, 0x99, 0x80, 0x69, 0x80, 0xcd, 0x80, 0x90, 0x81, 0x80, 0x4d,
+ 0x80, 0xcd, 0x80, 0x4d, 0x80, 0xb0, 0x35, 0x80, 0x80, 0x69, 0x80, 0x80, 0x81, 0x80, 0xe9, 0x7f,
+ 0x96, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x81,
+ 0x7f, 0x19, 0x80, 0x99, 0x7f, 0x19, 0x80, 0x69, 0x7f, 0x80, 0x99, 0x7f, 0xe9, 0x7f, 0x69, 0x7f,
+ 0xe9, 0x7f, 0xb5, 0x7f, 0xb5, 0x7f, 0x81, 0x7f, 0xb5, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xb5, 0x7f,
+ 0x99, 0x7f, 0xe9, 0x7f, 0x69, 0x7f, 0xe9, 0x7f, 0xe7, 0x99, 0x7f, 0x91, 0xcd, 0x7f, 0x19, 0x80,
+ 0xb5, 0x7f, 0x19, 0x80, 0xcd, 0x7f, 0x35, 0x80, 0xb5, 0x7f, 0x4d, 0x80, 0x21, 0xcd, 0x7f, 0x35,
+ 0x80, 0xb5, 0x7e, 0x99, 0x7f, 0xe1,
+}
+
+var AVGames = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x6e, 0xe8, 0x58,
+ 0xe6, 0x74, 0xe9, 0x96, 0x21, 0x8c, 0x8c, 0x8c, 0x74, 0xe3, 0x62, 0x86, 0xe6, 0x58, 0xe9, 0x98,
+ 0xe7, 0x96, 0x21, 0x8c, 0x74, 0x74, 0x74, 0xe3, 0x86, 0x9e, 0xe9, 0x96, 0xe7, 0x98, 0xe8, 0x92,
+ 0x21, 0x74, 0x74, 0x74, 0x8c, 0xe3, 0x9e, 0x62, 0x21, 0x74, 0x8c, 0x8c, 0x8c, 0xe7, 0x96, 0xe8,
+ 0x74, 0xe6, 0x92, 0xe1,
+}
+
+var AVHD = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xc9, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xc9,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x60, 0xb0, 0xe6, 0x76, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x68, 0xe8, 0x74, 0xe7, 0x86, 0xe9, 0x8a, 0xe7, 0x88, 0xe8,
+ 0x74, 0xe6, 0x7c, 0xe9, 0x98, 0xe3, 0x88, 0x68, 0xe7, 0x90, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9,
+ 0x80, 0x84, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe7, 0x70,
+ 0xe8, 0x74, 0xe3, 0x86, 0x92, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0x78, 0xe9, 0x8c, 0xe1,
+}
+
+var AVHearing = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0xa0, 0xb3, 0x71,
+ 0x7f, 0x80, 0xe1, 0x7e, 0xe1, 0x7f, 0x79, 0x7e, 0xb5, 0x7f, 0x99, 0x7e, 0x41, 0x7f, 0x91, 0x7d,
+ 0x3d, 0x7e, 0x95, 0x7c, 0x3d, 0x7b, 0xf9, 0x7e, 0xe5, 0x7c, 0x11, 0x7d, 0x6d, 0x7b, 0x35, 0x7b,
+ 0xfd, 0x79, 0x6d, 0x7e, 0xc9, 0x7e, 0xc9, 0x7c, 0x85, 0x7d, 0x61, 0x7b, 0xf5, 0x7a, 0xa0, 0x95,
+ 0x7a, 0xf5, 0x7d, 0x74, 0xdd, 0x7b, 0x74, 0x74, 0xb0, 0x80, 0x65, 0x7a, 0x65, 0x84, 0x6c, 0x94,
+ 0x6c, 0x90, 0x94, 0x65, 0x84, 0x94, 0x94, 0xe7, 0x88, 0xb0, 0x80, 0x29, 0x78, 0xd9, 0x79, 0x64,
+ 0x64, 0x64, 0x90, 0x64, 0x29, 0x86, 0x64, 0x9c, 0xb5, 0x80, 0x89, 0x82, 0xc5, 0x80, 0x4d, 0x85,
+ 0x21, 0x82, 0xcd, 0x87, 0xd1, 0x81, 0x51, 0x83, 0xf9, 0x83, 0xf5, 0x84, 0xb5, 0x85, 0x4d, 0x86,
+ 0xa1, 0x81, 0x41, 0x81, 0xcd, 0x82, 0x29, 0x82, 0x71, 0x83, 0x19, 0x84, 0x35, 0x81, 0xa1, 0x83,
+ 0xc1, 0x82, 0xb1, 0x85, 0x75, 0x85, 0x19, 0x87, 0x0d, 0x81, 0x79, 0x80, 0x25, 0x82, 0xb5, 0x80,
+ 0x4d, 0x83, 0xb5, 0x80, 0x69, 0x84, 0x80, 0x90, 0x69, 0x7c, 0x90, 0x70, 0xe7, 0x78, 0xb0, 0x80,
+ 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe2, 0x45, 0x77, 0x45, 0x6d, 0x20, 0x2d, 0x7d, 0x2d,
+ 0x7d, 0xa0, 0x75, 0x70, 0x6d, 0x6e, 0x5c, 0xed, 0x73, 0x5c, 0x74, 0x90, 0x75, 0x82, 0x95, 0x8b,
+ 0x71, 0x86, 0x91, 0x8f, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0xa0, 0x05, 0x74, 0x79, 0x83, 0x64, 0xf9,
+ 0x7e, 0x64, 0x74, 0x90, 0x05, 0x82, 0x89, 0x76, 0x45, 0x85, 0x45, 0x73, 0xe2, 0x7e, 0x74, 0xb0,
+ 0x80, 0xc5, 0x82, 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0x92, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xc5, 0x7d,
+ 0x76, 0x76, 0x76, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0xe1,
+}
+
+var AVHighQuality = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x60, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x7c, 0x8c, 0xe7, 0x7a, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7a, 0xe8, 0x74, 0xe7, 0x86, 0xe9, 0x8a, 0xe7, 0x88, 0xe9,
+ 0x76, 0xe7, 0x86, 0xe9, 0x98, 0xe3, 0x9c, 0x7c, 0xb0, 0x80, 0x19, 0x81, 0x1d, 0x7f, 0x84, 0x7c,
+ 0x84, 0xe7, 0x81, 0x7e, 0xe9, 0x86, 0xe7, 0x7a, 0xe9, 0x7a, 0xe6, 0x88, 0xb0, 0xe5, 0x7e, 0x80,
+ 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0xe5, 0x80, 0x7c, 0x84, 0x7c,
+ 0xe7, 0x8c, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0xe9, 0x90, 0xe3, 0x72, 0x7e,
+ 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0x78, 0xe9, 0x8c, 0xe1,
+}
+
+var AVLibraryAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe6, 0x58,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe3, 0xc0, 0x70, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x7c, 0xa4, 0xe7, 0x70, 0xe9, 0x90, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x70, 0xe9,
+ 0x78, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var AVLibraryBooks = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe6, 0x58,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe3, 0xc0, 0x70, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x7c, 0xa4, 0xe6, 0x74, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe3, 0x70, 0x90,
+ 0xe6, 0x74, 0xe9, 0x78, 0xe7, 0x98, 0xe9, 0x88, 0xe3, 0x90, 0x60, 0xe6, 0x74, 0xe9, 0x78, 0xe7,
+ 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var AVLibraryMusic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x70,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0x94, 0xe7, 0x74, 0xe9,
+ 0x96, 0xb0, 0x80, 0xc5, 0x82, 0xc5, 0x7d, 0x8a, 0x76, 0x8a, 0x91, 0x76, 0xc5, 0x7d, 0x76, 0x76,
+ 0x3d, 0x82, 0x76, 0x8a, 0x76, 0xb0, 0x21, 0x81, 0x80, 0x29, 0x82, 0x65, 0x80, 0x86, 0x05, 0x81,
+ 0xe8, 0x64, 0xe7, 0x90, 0xe9, 0x88, 0xe2, 0x60, 0x68, 0xe6, 0x58, 0xe9, 0xb8, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x60, 0xe8, 0x68, 0xe1,
+}
+
+var AVLoop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x60, 0xe8, 0x54,
+ 0x21, 0x70, 0x90, 0x90, 0x90, 0xe9, 0x74, 0xb1, 0xa1, 0x86, 0x80, 0x98, 0x61, 0x85, 0x98, 0x98,
+ 0x80, 0x09, 0x82, 0x7d, 0x7f, 0xf1, 0x83, 0x9d, 0x7e, 0x9d, 0x85, 0x20, 0xed, 0x82, 0xed, 0x82,
+ 0xa0, 0x15, 0x8f, 0x0d, 0x86, 0xa0, 0x25, 0x83, 0xa0, 0x80, 0xb0, 0x80, 0x29, 0x77, 0xd9, 0x78,
+ 0x60, 0x60, 0x60, 0xe3, 0x80, 0xb8, 0xb1, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x80,
+ 0xf9, 0x7d, 0x85, 0x80, 0x11, 0x7c, 0x65, 0x81, 0x65, 0x7a, 0x20, 0x15, 0x7d, 0x15, 0x7d, 0xa0,
+ 0xed, 0x70, 0xf5, 0x79, 0x60, 0xdd, 0x7c, 0x60, 0x80, 0xb0, 0x80, 0xd9, 0x88, 0x29, 0x87, 0xa0,
+ 0xa0, 0xa0, 0xe9, 0x8c, 0x21, 0x90, 0x70, 0x70, 0x70, 0xe9, 0x8c, 0xe1,
+}
+
+var AVMic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x88, 0xb0, 0x51,
+ 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x00, 0x8c, 0x64, 0xb1, 0x80, 0xb1, 0x7c,
+ 0x51, 0x7d, 0x74, 0x74, 0x74, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x98, 0xb0,
+ 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3, 0x99, 0x8a, 0x74, 0xb1, 0x80, 0x8c, 0xf1,
+ 0x7a, 0x35, 0x8a, 0x69, 0x75, 0x35, 0x8a, 0x7d, 0x7a, 0x80, 0x69, 0x75, 0xcd, 0x7b, 0x69, 0x75,
+ 0xcd, 0x75, 0xe6, 0x64, 0xb0, 0x80, 0xd5, 0x86, 0x71, 0x85, 0x79, 0x8c, 0x98, 0x71, 0x8d, 0xe8,
+ 0xa4, 0xe7, 0x88, 0xe9, 0x71, 0x79, 0xb0, 0x91, 0x86, 0x09, 0x7f, 0x98, 0x65, 0x79, 0x98, 0x91,
+ 0x72, 0xe7, 0x99, 0x7c, 0xe1,
+}
+
+var AVMicNone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x88, 0xb0, 0x51,
+ 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x00, 0x8c, 0x64, 0xb1, 0x80, 0xb1, 0x7c,
+ 0x51, 0x7d, 0x74, 0x74, 0x74, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x98, 0xb0,
+ 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe2, 0x99, 0x7d, 0xcd, 0x71, 0xb1, 0x80, 0xb1,
+ 0x7e, 0x15, 0x81, 0x99, 0x7d, 0x69, 0x82, 0x99, 0x7d, 0x51, 0x81, 0x80, 0x69, 0x82, 0x15, 0x81,
+ 0x69, 0x82, 0x69, 0x82, 0x20, 0xfd, 0x7f, 0x69, 0x8c, 0xb1, 0x80, 0x51, 0x81, 0xf1, 0x7e, 0x69,
+ 0x82, 0xa1, 0x7d, 0x69, 0x82, 0xb1, 0x7e, 0x80, 0x99, 0x7d, 0xed, 0x7e, 0x99, 0x7d, 0x99, 0x7d,
+ 0xe8, 0xcd, 0x71, 0xe3, 0x9a, 0x35, 0x8c, 0xb1, 0x80, 0x8c, 0xf1, 0x7a, 0x35, 0x8a, 0x69, 0x75,
+ 0x35, 0x8a, 0x7d, 0x7a, 0x80, 0x69, 0x75, 0xcd, 0x7b, 0x69, 0x75, 0xcd, 0x75, 0xe6, 0x64, 0xb0,
+ 0x80, 0xd5, 0x86, 0x71, 0x85, 0x79, 0x8c, 0x98, 0x71, 0x8d, 0xe8, 0xa4, 0xe7, 0x88, 0xe9, 0x71,
+ 0x79, 0xb0, 0x91, 0x86, 0x09, 0x7f, 0x98, 0x65, 0x79, 0x98, 0x91, 0x72, 0xe7, 0x99, 0x7c, 0xe1,
+}
+
+var AVMicOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x7c, 0xe7, 0x99,
+ 0x7c, 0xb0, 0x80, 0x7d, 0x81, 0xb1, 0x7f, 0xe1, 0x82, 0x21, 0x7f, 0x19, 0x84, 0x20, 0x75, 0x82,
+ 0x75, 0x82, 0xa0, 0x55, 0x8d, 0x9d, 0x82, 0x9c, 0x61, 0x80, 0x9c, 0x7c, 0xe3, 0xf9, 0x77, 0x55,
+ 0x80, 0xb0, 0x80, 0xe5, 0x7f, 0x09, 0x80, 0xc9, 0x7f, 0x09, 0x80, 0xad, 0x7f, 0xe8, 0x64, 0xb0,
+ 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x90, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x61,
+ 0x80, 0x20, 0xf9, 0x8b, 0xf5, 0x8b, 0xe2, 0x8d, 0x70, 0x5c, 0x00, 0x5c, 0x8d, 0x70, 0x20, 0x05,
+ 0x8c, 0x05, 0x8c, 0xe9, 0x71, 0x81, 0xb1, 0x80, 0x51, 0x83, 0xad, 0x82, 0x8c, 0xfd, 0x85, 0x8c,
+ 0x75, 0x80, 0x80, 0xe1, 0x80, 0xf1, 0x7f, 0x4d, 0x81, 0xd9, 0x7f, 0x20, 0x51, 0x83, 0x51, 0x83,
+ 0xb1, 0x91, 0x7e, 0xa9, 0x80, 0x7a, 0x09, 0x81, 0x61, 0x7b, 0x09, 0x81, 0x7d, 0x7a, 0x80, 0x69,
+ 0x75, 0xcd, 0x7b, 0x69, 0x75, 0xcd, 0x75, 0xe6, 0x64, 0xb0, 0x80, 0xd5, 0x86, 0x71, 0x85, 0x79,
+ 0x8c, 0x98, 0x71, 0x8d, 0xe8, 0xa4, 0xe7, 0x88, 0xe9, 0x71, 0x79, 0xb0, 0xd1, 0x81, 0xbd, 0x7f,
+ 0x89, 0x83, 0x19, 0x7f, 0x15, 0x85, 0x31, 0x7e, 0x02, 0x75, 0x8f, 0xa4, 0xa4, 0x75, 0x8f, 0x8d,
+ 0x70, 0x5c, 0xe1,
+}
+
+var AVMovie = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0x20, 0x88,
+ 0x90, 0xe7, 0x74, 0x20, 0x78, 0x70, 0xe7, 0x78, 0x20, 0x88, 0x90, 0xe7, 0x74, 0x20, 0x78, 0x70,
+ 0xe7, 0x78, 0x20, 0x88, 0x90, 0xe7, 0x74, 0x20, 0x78, 0x70, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60,
+ 0xe7, 0x70, 0xe1,
+}
+
+var AVMusicVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0,
+ 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xe3, 0x78, 0xb8, 0xe6, 0x5c, 0xe8,
+ 0x64, 0xe7, 0xc8, 0xe9, 0xb8, 0xe3, 0x4c, 0x70, 0xb1, 0x80, 0xb1, 0x7c, 0xb1, 0x82, 0x74, 0x8c,
+ 0x74, 0xb5, 0x80, 0x80, 0x82, 0x25, 0x80, 0x84, 0x61, 0x80, 0xe8, 0x68, 0xe7, 0x94, 0xe9, 0x88,
+ 0xe7, 0x74, 0x20, 0xfd, 0x7f, 0x0d, 0x8e, 0xa0, 0xf5, 0x83, 0x59, 0x89, 0x4d, 0x81, 0x98, 0x7c,
+ 0x98, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xe1,
+}
+
+var AVNewReleases = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x80, 0x23, 0x21,
+ 0x7b, 0x71, 0x7a, 0xb1, 0x80, 0xa1, 0x78, 0xc9, 0x78, 0x61, 0x7e, 0x39, 0x7c, 0xa5, 0x79, 0x00,
+ 0x80, 0x5c, 0x23, 0x35, 0x79, 0x15, 0x7d, 0x39, 0x7c, 0x5d, 0x86, 0xc9, 0x78, 0xa1, 0x81, 0xb1,
+ 0x80, 0x61, 0x87, 0x00, 0x54, 0x80, 0x23, 0xe1, 0x84, 0x91, 0x85, 0x51, 0x7f, 0x61, 0x87, 0x39,
+ 0x87, 0xa1, 0x81, 0xc9, 0x83, 0x5d, 0x86, 0x00, 0x80, 0xa4, 0x23, 0xcd, 0x86, 0xed, 0x82, 0xc9,
+ 0x83, 0xa5, 0x79, 0x39, 0x87, 0x61, 0x7e, 0x51, 0x7f, 0xa1, 0x78, 0x00, 0xac, 0x80, 0xe2, 0x84,
+ 0x94, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe8, 0x6c,
+ 0xe7, 0x88, 0xe9, 0x98, 0xe1,
+}
+
+var AVNotInterested = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb1, 0x29, 0x77, 0x80, 0x60,
+ 0xd9, 0x78, 0x60, 0x60, 0x80, 0x4d, 0x7c, 0x45, 0x81, 0xe9, 0x78, 0x61, 0x83, 0x35, 0x76, 0x00,
+ 0xcd, 0x89, 0xa1, 0x8c, 0xa0, 0x19, 0x87, 0xbd, 0x8e, 0xb5, 0x83, 0xa0, 0x80, 0xa0, 0xe3, 0xa1,
+ 0x8c, 0xcd, 0x79, 0x00, 0x35, 0x76, 0x61, 0x73, 0xa0, 0xe9, 0x78, 0x45, 0x71, 0x4d, 0x7c, 0x60,
+ 0x80, 0x60, 0xb1, 0xd9, 0x88, 0x80, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0x80, 0xb5, 0x83, 0xbd, 0x7e,
+ 0x19, 0x87, 0xa1, 0x7c, 0xcd, 0x89, 0xe1,
+}
+
+var AVNote = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x78, 0x00, 0x90,
+ 0x60, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x05, 0x98, 0xa0,
+ 0x58, 0x39, 0x8e, 0xcd, 0x6d, 0xa0, 0x60, 0xa0, 0x20, 0xc0, 0xfd, 0x7f, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x39, 0x7e, 0x88, 0x05, 0x7c, 0xe8, 0x78, 0xe3, 0x64, 0x6e, 0x20, 0x96, 0x96, 0xe6, 0x8c,
+ 0xe8, 0x66, 0xe1,
+}
+
+var AVPause = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x9c, 0xe7, 0x90,
+ 0xe8, 0x64, 0xe7, 0x70, 0xe9, 0xb8, 0xe3, 0xa0, 0x48, 0xe9, 0xb8, 0xe7, 0x90, 0xe8, 0x64, 0xe7,
+ 0x70, 0xe1,
+}
+
+var AVPauseCircleFilled = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x7c, 0xb8, 0xe7, 0x78, 0xe8, 0x70, 0xe7,
+ 0x88, 0xe9, 0xa0, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe8, 0x70, 0xe7, 0x88, 0xe9, 0xa0, 0xe1,
+}
+
+var AVPauseCircleOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x90, 0xe7, 0x88,
+ 0xe8, 0x70, 0xe7, 0x78, 0xe9, 0xa0, 0xe3, 0x8c, 0x48, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74,
+ 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b,
+ 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80,
+ 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0,
+ 0xe3, 0x84, 0x70, 0xe7, 0x88, 0xe8, 0x70, 0xe7, 0x78, 0xe9, 0xa0, 0xe1,
+}
+
+var AVPlayArrow = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x64, 0xe9, 0xb8,
+ 0x20, 0xac, 0x64, 0xe1,
+}
+
+var AVPlayCircleFilled = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x78, 0xba, 0xe8, 0x6e, 0x21, 0x98, 0x92,
+ 0x68, 0x92, 0xe1,
+}
+
+var AVPlayCircleOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x92, 0x21, 0x98,
+ 0x6e, 0x68, 0x6e, 0xe9, 0xa4, 0xe3, 0x88, 0x46, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58,
+ 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58,
+ 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80, 0x31,
+ 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var AVPlaylistAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x78, 0xe6, 0x58,
+ 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xe3, 0x80, 0x70, 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe3, 0x90, 0xa0, 0xe9, 0x70, 0xe7, 0x78, 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90,
+ 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe2, 0x58, 0x90, 0xe7,
+ 0xa0, 0xe9, 0x78, 0xe6, 0x58, 0xe9, 0x88, 0xe1,
+}
+
+var AVPlaylistAddCheck = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x78, 0xe6, 0x58,
+ 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xe3, 0x80, 0x70, 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe2, 0x58, 0x90, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x58, 0xe9, 0x88, 0xe3, 0xce, 0x6e, 0x21,
+ 0x86, 0x86, 0x05, 0x72, 0x9c, 0x00, 0x7e, 0x8e, 0x21, 0x86, 0x7a, 0x05, 0x86, 0x8c, 0x00, 0xa6,
+ 0x7e, 0xe1,
+}
+
+var AVPlaylistPlay = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x74, 0xe6, 0x58,
+ 0xe9, 0x88, 0xe7, 0xc4, 0xe8, 0x74, 0xe3, 0x80, 0x70, 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0xc4, 0xe9,
+ 0x78, 0xe2, 0x58, 0x8c, 0xe7, 0xb4, 0xe9, 0x78, 0xe6, 0x58, 0xe9, 0x88, 0xe3, 0xbc, 0x78, 0xe9,
+ 0x98, 0x21, 0x94, 0x74, 0x6c, 0x74, 0xe1,
+}
+
+var AVQueue = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe6, 0x58,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe3, 0xc0, 0x70, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x7c, 0xa4, 0xe7, 0x70, 0xe9, 0x90, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x70, 0xe9,
+ 0x78, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var AVQueueMusic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x68, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe2, 0x5c, 0x90, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0xb8, 0x58, 0xe9,
+ 0x61, 0x90, 0xb1, 0x61, 0x7f, 0xc5, 0x7f, 0xb5, 0x7e, 0xa1, 0x7f, 0x7c, 0xa1, 0x7f, 0xb1, 0x7c,
+ 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x91, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x8c, 0x51, 0x7d, 0x8c,
+ 0x74, 0xe8, 0x70, 0xe7, 0x8c, 0xe9, 0x78, 0xe6, 0x94, 0xe1,
+}
+
+var AVQueuePlayNext = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xc9, 0x7d, 0x80, 0x78, 0xc9, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xc9,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xa0, 0xe7, 0x88, 0xe8, 0x64, 0xb0, 0x80, 0xc9, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x84, 0x78, 0xe9, 0x74, 0xe7, 0x78, 0xe9, 0x8c, 0xe7, 0x74,
+ 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0x8c, 0xe9, 0x78, 0xe7, 0x74,
+ 0xe3, 0xac, 0xa0, 0x25, 0x6e, 0x92, 0x7a, 0x7a, 0x8c, 0x74, 0x74, 0x74, 0x86, 0x7a, 0x92, 0x92,
+ 0xe1,
+}
+
+var AVRadio = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x79, 0x6e, 0x4d, 0x74,
+ 0xa0, 0x05, 0x6d, 0xe1, 0x74, 0x58, 0x55, 0x76, 0x58, 0x70, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x9d, 0x78, 0x20, 0x89,
+ 0x90, 0x55, 0x79, 0x01, 0xc5, 0x87, 0x54, 0x79, 0x6e, 0x4d, 0x74, 0xe2, 0x6c, 0xa0, 0xb0, 0xb1,
+ 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x92, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x8c, 0xb1, 0x82,
+ 0x8c, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0xe3, 0xb4, 0x60, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe6, 0x60, 0xe9, 0x70, 0xe7, 0xc0, 0xe9, 0x90, 0xe1,
+}
+
+var AVRecentActors = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x64, 0xe9, 0xb8,
+ 0xe7, 0x88, 0xe8, 0x64, 0xe7, 0x78, 0xe3, 0x70, 0xb8, 0xe7, 0x88, 0xe8, 0x64, 0xe7, 0x78, 0xe9,
+ 0xb8, 0xe3, 0x74, 0x48, 0xe6, 0x58, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9,
+ 0xb0, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0xb0, 0xb0, 0x19, 0x81, 0x80,
+ 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x68, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c,
+ 0xe3, 0x68, 0x81, 0x85, 0xb1, 0x7d, 0x82, 0x80, 0x81, 0x84, 0x05, 0x82, 0x81, 0x84, 0x81, 0x84,
+ 0x80, 0x7d, 0x82, 0xfd, 0x7d, 0x81, 0x84, 0x81, 0x7b, 0x81, 0x84, 0x90, 0x81, 0x7b, 0xfd, 0x7d,
+ 0x81, 0x7b, 0x81, 0x7b, 0xb0, 0x80, 0x85, 0x7d, 0x05, 0x82, 0x81, 0x7b, 0x81, 0x84, 0x81, 0x7b,
+ 0xe2, 0x82, 0x94, 0xe6, 0x5e, 0xe9, 0x81, 0x7e, 0xb0, 0x80, 0x7a, 0x8c, 0x81, 0x7b, 0x92, 0x81,
+ 0x7b, 0x90, 0x92, 0x81, 0x81, 0x92, 0x81, 0x84, 0xe8, 0x94, 0xe1,
+}
+
+var AVRemoveFromQueue = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xac, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe2, 0x90, 0x78, 0xe9, 0x88, 0xe6, 0x70, 0xe9, 0x78, 0xe7, 0xa0, 0xe1,
+}
+
+var AVRepeat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x6c, 0xe7, 0xa8,
+ 0xe9, 0x8c, 0x21, 0x90, 0x70, 0x70, 0x70, 0xe9, 0x8c, 0xe6, 0x64, 0xe9, 0x98, 0xe7, 0x88, 0xe9,
+ 0x70, 0xe3, 0xa8, 0xa8, 0xe6, 0x6c, 0xe9, 0x74, 0x21, 0x70, 0x90, 0x90, 0x90, 0xe9, 0x74, 0xe7,
+ 0xb0, 0xe8, 0x84, 0xe7, 0x78, 0xe9, 0x90, 0xe1,
+}
+
+var AVRepeatOne = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x6c, 0xe7, 0xa8,
+ 0xe9, 0x8c, 0x21, 0x90, 0x70, 0x70, 0x70, 0xe9, 0x8c, 0xe6, 0x64, 0xe9, 0x98, 0xe7, 0x88, 0xe9,
+ 0x70, 0xe3, 0xa8, 0xa8, 0xe6, 0x6c, 0xe9, 0x74, 0x21, 0x70, 0x90, 0x90, 0x90, 0xe9, 0x74, 0xe7,
+ 0xb0, 0xe8, 0x84, 0xe7, 0x78, 0xe9, 0x90, 0xe3, 0x70, 0x78, 0xe8, 0x74, 0xe7, 0x7c, 0x20, 0x78,
+ 0x84, 0xe9, 0x84, 0xe7, 0x86, 0xe9, 0x90, 0xe7, 0x86, 0xe1,
+}
+
+var AVReplay = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x64, 0xe8, 0x54,
+ 0x00, 0x6c, 0x68, 0x20, 0x94, 0x94, 0xe9, 0x70, 0xb0, 0xa1, 0x86, 0x80, 0x98, 0x61, 0x85, 0x98,
+ 0x98, 0x91, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0xe6, 0x60, 0xb0, 0x80,
+ 0xd9, 0x88, 0x29, 0x87, 0xa0, 0xa0, 0xa0, 0x91, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xd9, 0x78, 0x60,
+ 0x60, 0x60, 0xe1,
+}
+
+var AVReplay10 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x64, 0xe8, 0x54,
+ 0x00, 0x6c, 0x68, 0x20, 0x94, 0x94, 0xe9, 0x70, 0xb0, 0x99, 0x86, 0x80, 0x98, 0x69, 0x85, 0x98,
+ 0x98, 0x91, 0x99, 0x7a, 0x98, 0x68, 0x98, 0x68, 0x99, 0x7a, 0x68, 0x68, 0xe6, 0x60, 0xb0, 0x80,
+ 0xcd, 0x88, 0x35, 0x87, 0xa0, 0xa0, 0xa0, 0x91, 0xa0, 0xcd, 0x78, 0xa0, 0x60, 0xcd, 0x78, 0x60,
+ 0x60, 0x60, 0xe3, 0xcd, 0x7d, 0xac, 0xe7, 0x4d, 0x7e, 0xe9, 0x81, 0x79, 0x20, 0x7c, 0x99, 0x80,
+ 0xe9, 0x99, 0x7e, 0x20, 0x81, 0x83, 0xb5, 0x7e, 0xe7, 0x35, 0x80, 0xe8, 0x90, 0xe3, 0x81, 0x88,
+ 0x81, 0x7c, 0xb0, 0x80, 0x99, 0x80, 0xe9, 0x7f, 0x35, 0x81, 0xcd, 0x7f, 0x99, 0x81, 0x96, 0xb5,
+ 0x7f, 0xcd, 0x80, 0x69, 0x7f, 0x19, 0x81, 0x69, 0x7f, 0x81, 0x80, 0x19, 0x7f, 0xb5, 0x80, 0x4d,
+ 0x7f, 0x35, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x35, 0x7f, 0xe9, 0x7f, 0xcd, 0x7e, 0xcd, 0x7f, 0x4d,
+ 0x7f, 0x99, 0x7f, 0x19, 0x7f, 0x4d, 0x7f, 0x81, 0x7f, 0x4d, 0x7f, 0x69, 0x7f, 0xe9, 0x7e, 0xcd,
+ 0x7f, 0x7e, 0xcd, 0x7f, 0x69, 0x7e, 0xe8, 0x86, 0xb0, 0x80, 0x69, 0x7f, 0x19, 0x80, 0xcd, 0x7e,
+ 0x35, 0x80, 0x69, 0x7e, 0x96, 0x4d, 0x80, 0x35, 0x7f, 0x99, 0x80, 0xe9, 0x7e, 0x99, 0x80, 0x81,
+ 0x7f, 0xe9, 0x80, 0x4d, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x35, 0x81, 0xcd, 0x7f, 0xcd, 0x80, 0x19,
+ 0x80, 0x35, 0x81, 0x35, 0x80, 0xb5, 0x80, 0x69, 0x80, 0xe9, 0x80, 0xb5, 0x80, 0x81, 0x80, 0xb5,
+ 0x80, 0x99, 0x80, 0x19, 0x81, 0x35, 0x80, 0x82, 0x35, 0x80, 0x99, 0x81, 0xe9, 0x81, 0x81, 0xe3,
+ 0x69, 0x7e, 0x4d, 0x7e, 0xb0, 0x80, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0xe9, 0x7f, 0x7e, 0x96, 0xe9,
+ 0x7f, 0x81, 0x7f, 0xcd, 0x7f, 0x69, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0x99, 0x7f, 0xb5, 0x7f, 0xb5,
+ 0x7f, 0xe9, 0x7f, 0x81, 0x7f, 0xe9, 0x7f, 0x99, 0x7f, 0x80, 0x81, 0x7f, 0x19, 0x80, 0xb5, 0x7f,
+ 0x35, 0x80, 0x99, 0x7f, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xcd, 0x7f, 0x99, 0x80, 0xe9, 0x7f,
+ 0x99, 0x80, 0xe9, 0x7f, 0x82, 0xe9, 0xe9, 0x81, 0xb0, 0x80, 0x69, 0x80, 0x80, 0xb5, 0x80, 0x19,
+ 0x80, 0x82, 0x96, 0x19, 0x80, 0x81, 0x80, 0x35, 0x80, 0x99, 0x80, 0x35, 0x80, 0x4d, 0x80, 0x69,
+ 0x80, 0x4d, 0x80, 0x4d, 0x80, 0x19, 0x80, 0x81, 0x80, 0x19, 0x80, 0x69, 0x80, 0x80, 0x81, 0x80,
+ 0xe9, 0x7f, 0x4d, 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x99, 0x7f, 0x35, 0x80,
+ 0x69, 0x7f, 0x19, 0x80, 0x69, 0x7f, 0x19, 0x80, 0x7e, 0xe9, 0x19, 0x7e, 0xe1,
+}
+
+var AVReplay30 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x64, 0xe8, 0x54,
+ 0x00, 0x6c, 0x68, 0x20, 0x94, 0x94, 0xe9, 0x70, 0xb0, 0x99, 0x86, 0x80, 0x98, 0x69, 0x85, 0x98,
+ 0x98, 0x91, 0x99, 0x7a, 0x98, 0x68, 0x98, 0x68, 0x99, 0x7a, 0x68, 0x68, 0xe6, 0x60, 0xb0, 0x80,
+ 0xcd, 0x88, 0x35, 0x87, 0xa0, 0xa0, 0xa0, 0x91, 0xa0, 0xcd, 0x78, 0xa0, 0x60, 0xcd, 0x78, 0x60,
+ 0x60, 0x60, 0xe3, 0x19, 0x7b, 0xa2, 0xe7, 0xe9, 0x80, 0xb0, 0x69, 0x80, 0x80, 0xb5, 0x80, 0xe9,
+ 0x7f, 0x82, 0xb5, 0x7f, 0x90, 0x4d, 0x80, 0x81, 0x7f, 0x4d, 0x80, 0x19, 0x7f, 0xb0, 0x80, 0xcd,
+ 0x7f, 0x80, 0xb5, 0x7f, 0xe9, 0x7f, 0x99, 0x7f, 0x96, 0xe9, 0x7f, 0xb5, 0x7f, 0xcd, 0x7f, 0x99,
+ 0x7f, 0xcd, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0xe9, 0x7f, 0x81, 0x7f, 0xe9,
+ 0x7f, 0xb5, 0x7f, 0x80, 0x99, 0x7f, 0x19, 0x80, 0xb5, 0x7f, 0x19, 0x80, 0x99, 0x7f, 0x35, 0x80,
+ 0xcd, 0x7f, 0x35, 0x80, 0xcd, 0x7f, 0x4d, 0x80, 0xe9, 0x7f, 0x35, 0x80, 0xe9, 0x7f, 0x69, 0x80,
+ 0xe7, 0x4d, 0x7e, 0xb0, 0x80, 0x99, 0x7f, 0x19, 0x80, 0x4d, 0x7f, 0x35, 0x80, 0x7e, 0x92, 0x4d,
+ 0x80, 0x81, 0x7f, 0x99, 0x80, 0x4d, 0x7f, 0x81, 0x80, 0x99, 0x7f, 0xe9, 0x80, 0x81, 0x7f, 0xb5,
+ 0x80, 0xcd, 0x7f, 0x19, 0x81, 0xcd, 0x7f, 0xb0, 0x69, 0x80, 0x80, 0xcd, 0x80, 0x19, 0x80, 0x35,
+ 0x81, 0x35, 0x80, 0x92, 0xb5, 0x80, 0x4d, 0x80, 0xe9, 0x80, 0x81, 0x80, 0x81, 0x80, 0x81, 0x80,
+ 0x99, 0x80, 0xcd, 0x80, 0x35, 0x80, 0xb5, 0x80, 0x35, 0x80, 0x19, 0x81, 0xb0, 0x80, 0x35, 0x80,
+ 0x80, 0x69, 0x80, 0xe9, 0x7f, 0x81, 0x80, 0x92, 0xe9, 0x7f, 0x4d, 0x80, 0xb5, 0x7f, 0x81, 0x80,
+ 0xcd, 0x7f, 0x4d, 0x80, 0x99, 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x69, 0x7f, 0x4d, 0x80,
+ 0xb0, 0x81, 0x80, 0x35, 0x80, 0xcd, 0x80, 0x69, 0x80, 0x19, 0x81, 0xcd, 0x80, 0x90, 0x69, 0x80,
+ 0xcd, 0x80, 0x69, 0x80, 0x35, 0x81, 0xb0, 0x80, 0x69, 0x80, 0xe9, 0x7f, 0xcd, 0x80, 0xcd, 0x7f,
+ 0x19, 0x81, 0x92, 0x99, 0x7f, 0x99, 0x80, 0x69, 0x7f, 0xcd, 0x80, 0x69, 0x7f, 0x69, 0x80, 0x7e,
+ 0x81, 0x80, 0x35, 0x7f, 0x35, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0xb0, 0x99, 0x7f, 0x80, 0x4d, 0x7f,
+ 0x80, 0xe9, 0x7e, 0xe9, 0x7f, 0x92, 0x4d, 0x7f, 0xcd, 0x7f, 0x19, 0x7f, 0x81, 0x7f, 0x81, 0x7f,
+ 0x81, 0x7f, 0x4d, 0x7f, 0x35, 0x7f, 0xcd, 0x7f, 0x4d, 0x7f, 0xcd, 0x7f, 0xe9, 0x7e, 0xe7, 0xb5,
+ 0x81, 0xb0, 0x80, 0x35, 0x80, 0x80, 0x4d, 0x80, 0x19, 0x80, 0x69, 0x80, 0x9a, 0x19, 0x80, 0x35,
+ 0x80, 0x4d, 0x80, 0x4d, 0x80, 0x35, 0x80, 0x35, 0x80, 0x69, 0x80, 0x35, 0x80, 0x4d, 0x80, 0x19,
+ 0x80, 0x81, 0x80, 0x19, 0x80, 0x69, 0x80, 0x80, 0x81, 0x80, 0xe9, 0x7f, 0x4d, 0x80, 0xe9, 0x7f,
+ 0x69, 0x80, 0xcd, 0x7f, 0x35, 0x80, 0xcd, 0x7f, 0x4d, 0x80, 0x99, 0x7f, 0x19, 0x80, 0xb5, 0x7f,
+ 0x19, 0x80, 0x81, 0x7f, 0x80, 0x99, 0x7f, 0xe9, 0x7f, 0x69, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0xb5,
+ 0x7f, 0x99, 0x7f, 0xb5, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xe9, 0x7f, 0x69,
+ 0x7f, 0xe9, 0x7f, 0xe7, 0x19, 0x7f, 0xe8, 0x86, 0xe3, 0x81, 0x8b, 0x81, 0x81, 0xb0, 0x80, 0x99,
+ 0x80, 0xe9, 0x7f, 0x35, 0x81, 0xcd, 0x7f, 0x99, 0x81, 0x96, 0xb5, 0x7f, 0xcd, 0x80, 0x69, 0x7f,
+ 0x19, 0x81, 0x69, 0x7f, 0x81, 0x80, 0x19, 0x7f, 0xb5, 0x80, 0x4d, 0x7f, 0x35, 0x80, 0xcd, 0x7e,
+ 0x35, 0x80, 0x35, 0x7f, 0xe9, 0x7f, 0xcd, 0x7e, 0xcd, 0x7f, 0x4d, 0x7f, 0x99, 0x7f, 0x19, 0x7f,
+ 0x4d, 0x7f, 0x81, 0x7f, 0x4d, 0x7f, 0x69, 0x7f, 0xe9, 0x7e, 0xcd, 0x7f, 0x7e, 0xcd, 0x7f, 0x69,
+ 0x7e, 0xe8, 0x86, 0xb0, 0x80, 0x69, 0x7f, 0x19, 0x80, 0xcd, 0x7e, 0x35, 0x80, 0x69, 0x7e, 0x96,
+ 0x4d, 0x80, 0x35, 0x7f, 0x99, 0x80, 0xe9, 0x7e, 0x99, 0x80, 0x81, 0x7f, 0xe9, 0x80, 0x4d, 0x7f,
+ 0xb5, 0x80, 0xcd, 0x7f, 0x35, 0x81, 0xcd, 0x7f, 0xcd, 0x80, 0x19, 0x80, 0x35, 0x81, 0x35, 0x80,
+ 0xb5, 0x80, 0x69, 0x80, 0xe9, 0x80, 0xb5, 0x80, 0x81, 0x80, 0xb5, 0x80, 0x99, 0x80, 0x19, 0x81,
+ 0x35, 0x80, 0x82, 0x35, 0x80, 0x99, 0x81, 0xe9, 0x81, 0x81, 0xe3, 0x4d, 0x7e, 0x4d, 0x7e, 0xb0,
+ 0x80, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0xe9, 0x7f, 0x7e, 0x96, 0xe9, 0x7f, 0x81, 0x7f, 0xcd, 0x7f,
+ 0x69, 0x7f, 0xcd, 0x7f, 0xb5, 0x7f, 0x99, 0x7f, 0xb5, 0x7f, 0xb5, 0x7f, 0xe9, 0x7f, 0x81, 0x7f,
+ 0xe9, 0x7f, 0x99, 0x7f, 0x80, 0x81, 0x7f, 0x19, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x99, 0x7f, 0x4d,
+ 0x80, 0xcd, 0x7f, 0x69, 0x80, 0xcd, 0x7f, 0x99, 0x80, 0xe9, 0x7f, 0x99, 0x80, 0xe9, 0x7f, 0x82,
+ 0xe9, 0xe9, 0x81, 0xb0, 0x80, 0x69, 0x80, 0x80, 0xb5, 0x80, 0x19, 0x80, 0x82, 0x96, 0x19, 0x80,
+ 0x81, 0x80, 0x35, 0x80, 0x99, 0x80, 0x35, 0x80, 0x4d, 0x80, 0x69, 0x80, 0x4d, 0x80, 0x4d, 0x80,
+ 0x19, 0x80, 0x81, 0x80, 0x19, 0x80, 0x69, 0x80, 0x80, 0x81, 0x80, 0xe9, 0x7f, 0x4d, 0x80, 0xcd,
+ 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x99, 0x7f, 0x35, 0x80, 0x69, 0x7f, 0x19, 0x80, 0x69,
+ 0x7f, 0x19, 0x80, 0x7e, 0xe9, 0x19, 0x7e, 0xe1,
+}
+
+var AVReplay5 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x64, 0xe8, 0x54,
+ 0x00, 0x6c, 0x68, 0x20, 0x94, 0x94, 0xe9, 0x70, 0xb0, 0x99, 0x86, 0x80, 0x98, 0x69, 0x85, 0x98,
+ 0x98, 0x91, 0x99, 0x7a, 0x98, 0x68, 0x98, 0x68, 0x99, 0x7a, 0x68, 0x68, 0xe6, 0x60, 0xb0, 0x80,
+ 0xcd, 0x88, 0x35, 0x87, 0xa0, 0xa0, 0xa0, 0x91, 0xa0, 0xcd, 0x78, 0xa0, 0x60, 0xcd, 0x78, 0x60,
+ 0x60, 0x60, 0xe3, 0x69, 0x7d, 0xcd, 0x91, 0x20, 0x81, 0x80, 0xb5, 0x7b, 0xe7, 0xcd, 0x84, 0xe9,
+ 0x69, 0x81, 0xe7, 0x99, 0x7c, 0x20, 0xcd, 0x7f, 0xcd, 0x81, 0xb0, 0x19, 0x80, 0x80, 0x19, 0x80,
+ 0xe9, 0x7f, 0x35, 0x80, 0xe9, 0x7f, 0x91, 0x35, 0x80, 0xe9, 0x7f, 0x4d, 0x80, 0xe9, 0x7f, 0x35,
+ 0x80, 0xe9, 0x7f, 0x69, 0x80, 0xe9, 0x7f, 0xe7, 0x69, 0x80, 0xb0, 0x69, 0x80, 0x80, 0xcd, 0x80,
+ 0x19, 0x80, 0x19, 0x81, 0x35, 0x80, 0x92, 0x99, 0x80, 0x4d, 0x80, 0xcd, 0x80, 0x99, 0x80, 0x69,
+ 0x80, 0x81, 0x80, 0x81, 0x80, 0xe9, 0x80, 0x35, 0x80, 0xcd, 0x80, 0x35, 0x80, 0x35, 0x81, 0xb0,
+ 0x80, 0x69, 0x80, 0xe9, 0x7f, 0xb5, 0x80, 0xcd, 0x7f, 0x19, 0x81, 0x92, 0xb5, 0x7f, 0x99, 0x80,
+ 0x81, 0x7f, 0xe9, 0x80, 0x81, 0x7f, 0x81, 0x80, 0x19, 0x7f, 0x99, 0x80, 0x35, 0x7f, 0x35, 0x80,
+ 0xb5, 0x7e, 0x35, 0x80, 0xb0, 0x99, 0x7f, 0x80, 0x4d, 0x7f, 0xe9, 0x7f, 0xe9, 0x7e, 0xcd, 0x7f,
+ 0x92, 0x69, 0x7f, 0xb5, 0x7f, 0x19, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x81, 0x7f, 0x69, 0x7f, 0x35,
+ 0x7f, 0xb5, 0x7f, 0x4d, 0x7f, 0xb5, 0x7f, 0xe9, 0x7e, 0xe7, 0xb5, 0x81, 0xb0, 0x80, 0x69, 0x80,
+ 0x35, 0x80, 0x99, 0x80, 0x69, 0x80, 0xcd, 0x80, 0x90, 0x81, 0x80, 0x4d, 0x80, 0xcd, 0x80, 0x4d,
+ 0x80, 0xb0, 0x35, 0x80, 0x80, 0x69, 0x80, 0x80, 0x81, 0x80, 0xe9, 0x7f, 0x96, 0x4d, 0x80, 0xcd,
+ 0x7f, 0x69, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0xb5, 0x7f, 0x35, 0x80, 0x81, 0x7f, 0x19, 0x80, 0x99,
+ 0x7f, 0x19, 0x80, 0x69, 0x7f, 0x80, 0x99, 0x7f, 0xe9, 0x7f, 0x69, 0x7f, 0xe9, 0x7f, 0xb5, 0x7f,
+ 0xb5, 0x7f, 0x81, 0x7f, 0xb5, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0xb5, 0x7f, 0x99, 0x7f, 0xe9, 0x7f,
+ 0x69, 0x7f, 0xe9, 0x7f, 0xe7, 0x99, 0x7f, 0x91, 0xcd, 0x7f, 0x19, 0x80, 0xb5, 0x7f, 0x19, 0x80,
+ 0xcd, 0x7f, 0x35, 0x80, 0xb5, 0x7f, 0x4d, 0x80, 0x21, 0xcd, 0x7f, 0x35, 0x80, 0xb5, 0x7e, 0x99,
+ 0x7f, 0xe1,
+}
+
+var AVShuffle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x7d, 0x59, 0x7a,
+ 0x01, 0xd5, 0x72, 0x60, 0x60, 0xd5, 0x72, 0x21, 0x59, 0x8a, 0x59, 0x8a, 0xd5, 0x82, 0x2d, 0x7d,
+ 0xe2, 0x8a, 0x60, 0x20, 0x19, 0x84, 0x19, 0x84, 0x01, 0x60, 0x2d, 0x8d, 0xd5, 0x72, 0xa0, 0x20,
+ 0x19, 0x99, 0xe9, 0x66, 0x00, 0xa0, 0x76, 0xe8, 0x60, 0xe6, 0x8a, 0xe3, 0xa9, 0x80, 0xd5, 0x92,
+ 0x21, 0x2d, 0x7d, 0xd5, 0x82, 0x45, 0x86, 0x45, 0x86, 0x00, 0x8a, 0xa0, 0xe7, 0x96, 0xe8, 0x8a,
+ 0x21, 0xe9, 0x7b, 0x19, 0x84, 0xc1, 0x79, 0xbd, 0x79, 0xe1,
+}
+
+var AVSkipNext = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x98, 0x21, 0xa2,
+ 0x68, 0x5e, 0x68, 0xe9, 0xb0, 0xe3, 0xa8, 0x50, 0xe9, 0xb0, 0xe7, 0x88, 0xe8, 0x68, 0xe7, 0x78,
+ 0xe1,
+}
+
+var AVSkipPrevious = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x68, 0xe7, 0x88,
+ 0xe9, 0xb0, 0xe7, 0x78, 0xe3, 0x8e, 0x98, 0x20, 0xa2, 0x98, 0xe8, 0x68, 0xe1,
+}
+
+var AVSlowMotionVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x82, 0x95, 0x7b,
+ 0x00, 0x78, 0x6e, 0xe9, 0xa4, 0x20, 0x19, 0x86, 0x6d, 0x7b, 0x00, 0x90, 0x80, 0xe3, 0x80, 0x80,
+ 0x00, 0x78, 0x6e, 0xe9, 0xa4, 0x20, 0x19, 0x86, 0x6d, 0x7b, 0x00, 0x90, 0x80, 0xe3, 0x80, 0x80,
+ 0x00, 0x78, 0x6e, 0xe9, 0xa4, 0x20, 0x19, 0x86, 0x6d, 0x7b, 0x00, 0x90, 0x80, 0xe2, 0x7c, 0x25,
+ 0x70, 0xe8, 0x19, 0x6c, 0xb0, 0xfd, 0x7b, 0x69, 0x80, 0x51, 0x78, 0x84, 0x5d, 0x75, 0x6d, 0x84,
+ 0x20, 0xd9, 0x82, 0xdd, 0x82, 0xb0, 0x39, 0x82, 0x49, 0x7e, 0xe1, 0x84, 0x21, 0x7d, 0xcd, 0x87,
+ 0xc5, 0x7c, 0xe2, 0x61, 0x73, 0x35, 0x76, 0x20, 0x25, 0x7d, 0x29, 0x7d, 0xa0, 0x19, 0x6e, 0x51,
+ 0x76, 0x81, 0x6c, 0xfd, 0x79, 0x19, 0x6c, 0x7c, 0xe7, 0x0d, 0x84, 0xb0, 0x5d, 0x80, 0x15, 0x7d,
+ 0x85, 0x81, 0x6d, 0x7a, 0x3d, 0x83, 0x35, 0x78, 0xe2, 0x25, 0x70, 0x84, 0xe6, 0x19, 0x6c, 0xb0,
+ 0x69, 0x80, 0x05, 0x84, 0x84, 0xb1, 0x87, 0x6d, 0x84, 0xa5, 0x8a, 0x20, 0xdd, 0x82, 0x25, 0x7d,
+ 0xa0, 0xa9, 0x71, 0x95, 0x87, 0x81, 0x70, 0xed, 0x84, 0x25, 0x70, 0x84, 0xe3, 0x39, 0x83, 0x7d,
+ 0x8d, 0xa0, 0x51, 0x76, 0xe9, 0x91, 0x74, 0x81, 0x93, 0x7c, 0xe9, 0x93, 0xe9, 0xf5, 0x7b, 0xb0,
+ 0x15, 0x7d, 0xa5, 0x7f, 0x6d, 0x7a, 0x7d, 0x7e, 0x35, 0x78, 0xc5, 0x7c, 0x20, 0x29, 0x7d, 0xdd,
+ 0x82, 0xe2, 0xa8, 0x80, 0xb0, 0x80, 0x51, 0x8a, 0x29, 0x78, 0xd9, 0x92, 0x19, 0x6e, 0xe9, 0x93,
+ 0xe9, 0xf5, 0x7b, 0xa0, 0xf1, 0x89, 0xd1, 0x8e, 0xa0, 0x19, 0x88, 0xa0, 0x80, 0x80, 0xf1, 0x89,
+ 0x31, 0x71, 0x19, 0x82, 0x25, 0x70, 0xe8, 0x19, 0x6c, 0xa0, 0x29, 0x8c, 0x29, 0x6d, 0xa8, 0xb1,
+ 0x75, 0xa8, 0x80, 0xe1,
+}
+
+var AVSnooze = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xc5, 0x77, 0xc9, 0x6e,
+ 0x20, 0x71, 0x7d, 0xf1, 0x7c, 0x00, 0x58, 0x71, 0x73, 0x21, 0x91, 0x82, 0x11, 0x83, 0x31, 0x89,
+ 0x4d, 0x78, 0xe2, 0xa8, 0x71, 0x73, 0x22, 0xd1, 0x76, 0x4d, 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31,
+ 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73, 0xe2, 0xfd, 0x7f, 0x60, 0xa0, 0x0d, 0x76, 0x60, 0x5c,
+ 0x11, 0x78, 0x5c, 0x84, 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0x81, 0xa4, 0xf1, 0x8b, 0xa4,
+ 0x84, 0xf1, 0x89, 0x60, 0xfd, 0x7f, 0x60, 0xe2, 0x80, 0xa0, 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd,
+ 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79,
+ 0x9c, 0x64, 0x9c, 0xe3, 0x74, 0x5c, 0xe7, 0x41, 0x87, 0x00, 0x74, 0x69, 0x86, 0xe8, 0x94, 0xe7,
+ 0x98, 0xe9, 0x78, 0xe7, 0xc1, 0x78, 0x00, 0x8c, 0x99, 0x7d, 0xe8, 0x74, 0xe6, 0x74, 0xe9, 0x88,
+ 0xe1,
+}
+
+var AVSortByAlpha = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x85, 0x51, 0x71,
+ 0xe7, 0x91, 0x76, 0x20, 0xb9, 0x84, 0x49, 0x7b, 0xe3, 0xa1, 0x76, 0x6d, 0x9d, 0xe7, 0x51, 0x89,
+ 0x20, 0x59, 0x7b, 0xa9, 0x84, 0xe2, 0x35, 0x74, 0x8d, 0x74, 0x00, 0x35, 0x6b, 0x75, 0x8b, 0xe7,
+ 0xb1, 0x83, 0x20, 0xd9, 0x81, 0x19, 0x7b, 0xe7, 0x39, 0x8a, 0x20, 0xd9, 0x81, 0xe9, 0x84, 0xe7,
+ 0xb1, 0x83, 0x00, 0x7d, 0x77, 0x8d, 0x74, 0xe6, 0x35, 0x74, 0xe3, 0xbd, 0x7d, 0xbd, 0x8e, 0x21,
+ 0xe1, 0x83, 0xa5, 0x75, 0xe1, 0x83, 0x5d, 0x8a, 0xe6, 0xf1, 0x71, 0xe3, 0x85, 0x95, 0x8a, 0xe7,
+ 0x3d, 0x8c, 0xe9, 0x31, 0x83, 0xe7, 0xf1, 0x6e, 0xe9, 0x6d, 0x7d, 0x20, 0xd9, 0x8b, 0xe1, 0x6e,
+ 0xe7, 0x3d, 0x74, 0xe9, 0xcd, 0x7c, 0xe7, 0x99, 0x90, 0xe9, 0x85, 0x82, 0x20, 0x25, 0x74, 0x35,
+ 0x91, 0xe1,
+}
+
+var AVStop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x68, 0xe7, 0xb0,
+ 0xe9, 0xb0, 0xe6, 0x68, 0xe1,
+}
+
+var AVSubscriptions = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x70, 0xe6, 0x60,
+ 0xe9, 0x78, 0xe7, 0xc0, 0xe9, 0x88, 0xe2, 0x98, 0x58, 0xe6, 0x68, 0xe9, 0x88, 0xe7, 0xb0, 0xe8,
+ 0x58, 0xe3, 0x90, 0xa8, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe6,
+ 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe8, 0x80, 0xb0, 0x80, 0xcd, 0x7d,
+ 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88,
+ 0xe3, 0x68, 0x90, 0x20, 0x68, 0x79, 0x79, 0xe9, 0x11, 0x8d, 0x00, 0x90, 0x90, 0xe1,
+}
+
+var AVSubtitles = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x60, 0x80, 0xe7, 0x90, 0xe9,
+ 0x88, 0xe6, 0x60, 0xe9, 0x78, 0xe3, 0xa8, 0x98, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88,
+ 0xe3, 0x98, 0x80, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x78,
+ 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var AVSurroundSound = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x85, 0x77, 0x7d, 0x88, 0x20,
+ 0x2d, 0x7d, 0xd5, 0x82, 0xa0, 0x91, 0x71, 0x31, 0x88, 0x60, 0x19, 0x84, 0x60, 0x80, 0xb0, 0x80,
+ 0xe9, 0x7b, 0x91, 0x81, 0xd1, 0x77, 0xb1, 0x84, 0xb1, 0x74, 0x20, 0xd5, 0x82, 0xd5, 0x82, 0xa0,
+ 0x31, 0x75, 0xd9, 0x79, 0x68, 0xf1, 0x7c, 0x68, 0x80, 0xb0, 0x80, 0x11, 0x83, 0x2d, 0x81, 0x29,
+ 0x86, 0x85, 0x83, 0x7d, 0x88, 0xe2, 0x80, 0x90, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x6d, 0x7c, 0x70,
+ 0x70, 0x92, 0x95, 0x83, 0x70, 0x90, 0x70, 0x90, 0x95, 0x83, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70,
+ 0x90, 0xe3, 0x51, 0x8b, 0x51, 0x83, 0x20, 0x2d, 0x7d, 0x2d, 0x7d, 0xa0, 0xd5, 0x8a, 0x29, 0x86,
+ 0x98, 0x11, 0x83, 0x98, 0x80, 0xb0, 0x80, 0xf1, 0x7c, 0xd1, 0x7e, 0xd9, 0x79, 0x7d, 0x7c, 0x85,
+ 0x77, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0xa0, 0x71, 0x8e, 0xd1, 0x77, 0xa0, 0xe9, 0x7b, 0xa0, 0x80,
+ 0xb0, 0x80, 0x19, 0x84, 0x71, 0x7e, 0x31, 0x88, 0x51, 0x7b, 0x51, 0x8b, 0xe2, 0x80, 0x78, 0xb0,
+ 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var AVVideoCall = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x7a, 0xe9, 0x72,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb0, 0xe9, 0x7e, 0x80, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xa8, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xb0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0x20, 0x90, 0x90, 0xe8,
+ 0x6a, 0x20, 0x70, 0x90, 0xe3, 0x74, 0x8a, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7,
+ 0x74, 0xe9, 0x78, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe1,
+}
+
+var AVVideoLabel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb4, 0xe6, 0x5c, 0xe8,
+ 0x64, 0xe7, 0xc8, 0xe9, 0xac, 0xe1,
+}
+
+var AVVideoLibrary = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe6, 0x58,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe3, 0xc0, 0x70, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe2, 0x80, 0x8a, 0xe8, 0x66, 0x21, 0x98, 0x92, 0x68, 0x92, 0xe1,
+}
+
+var AVVideocam = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x7a, 0xe9, 0x72,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb0, 0xe9, 0x7e, 0x80, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xa8, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xb0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0x20, 0x90, 0x90, 0xe8,
+ 0x6a, 0x20, 0x70, 0x90, 0xe1,
+}
+
+var AVVideocamOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x6a, 0x20, 0x70,
+ 0x90, 0xe9, 0x72, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0xa5, 0x7b, 0x00,
+ 0xa4, 0x5d, 0x8a, 0xe8, 0x6a, 0xe2, 0x8d, 0x6e, 0x58, 0x01, 0x58, 0x8d, 0x6e, 0x75, 0x71, 0x68,
+ 0xe6, 0x60, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xa8, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0xb0, 0xb0, 0x69, 0x80, 0x80, 0xc5, 0x80, 0xd9, 0x7f,
+ 0x19, 0x81, 0xa1, 0x7f, 0x02, 0x75, 0x8f, 0xa4, 0xa4, 0x75, 0x8f, 0x8d, 0x6e, 0x58, 0xe1,
+}
+
+var AVVolumeDown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9a, 0x80, 0xb0, 0x80,
+ 0x79, 0x7c, 0xf5, 0x7d, 0x6d, 0x79, 0x76, 0xf5, 0x77, 0xe9, 0x1d, 0x90, 0xb0, 0xf5, 0x82, 0x85,
+ 0x7e, 0x8a, 0x79, 0x7b, 0x8a, 0xf1, 0x77, 0xe3, 0x4a, 0x74, 0xe9, 0x98, 0xe7, 0x90, 0x20, 0x94,
+ 0x94, 0xe8, 0x60, 0x00, 0x74, 0x74, 0xe7, 0x70, 0xe1,
+}
+
+var AVVolumeMute = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x74, 0xe9, 0x98,
+ 0xe7, 0x90, 0x20, 0x94, 0x94, 0xe8, 0x60, 0x00, 0x7c, 0x74, 0xe7, 0x70, 0xe1,
+}
+
+var AVVolumeOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x92, 0x80, 0xb0, 0x80,
+ 0x79, 0x7c, 0xf5, 0x7d, 0x6d, 0x79, 0x76, 0xf5, 0x77, 0xe9, 0x6d, 0x84, 0x20, 0xe9, 0x84, 0xe9,
+ 0x84, 0xb0, 0x11, 0x80, 0x95, 0x7f, 0x19, 0x80, 0x29, 0x7f, 0x19, 0x80, 0xb9, 0x7e, 0xe3, 0x8a,
+ 0x80, 0xb0, 0x80, 0xe1, 0x81, 0x99, 0x7f, 0xa9, 0x83, 0xed, 0x7e, 0x49, 0x85, 0x20, 0x09, 0x83,
+ 0x09, 0x83, 0xa0, 0x41, 0x91, 0xd1, 0x85, 0xa4, 0x86, 0xa4, 0x80, 0xb0, 0x80, 0x71, 0x77, 0x05,
+ 0x7a, 0x49, 0x70, 0x64, 0x75, 0x6e, 0xe9, 0x21, 0x84, 0xb0, 0xc9, 0x85, 0xb9, 0x81, 0x94, 0x11,
+ 0x87, 0x94, 0x69, 0x8d, 0xe2, 0x8d, 0x70, 0x5c, 0x01, 0x5c, 0x8d, 0x70, 0x75, 0x77, 0x74, 0xe6,
+ 0x5c, 0xe9, 0x98, 0xe7, 0x90, 0x20, 0x94, 0x94, 0xe8, 0x8d, 0x82, 0x20, 0x85, 0x88, 0x85, 0x88,
+ 0xb0, 0xa9, 0x7e, 0x09, 0x81, 0x29, 0x7d, 0xdd, 0x81, 0x7d, 0x7b, 0x5d, 0x82, 0xe9, 0x21, 0x84,
+ 0xb0, 0xc1, 0x82, 0x61, 0x7f, 0x45, 0x85, 0x1d, 0x7e, 0x61, 0x87, 0x61, 0x7c, 0x01, 0x75, 0x8f,
+ 0xa4, 0xa4, 0x75, 0x8f, 0x20, 0x5c, 0x5c, 0x00, 0x8d, 0x70, 0x5c, 0xe2, 0x80, 0x60, 0x20, 0xd1,
+ 0x7b, 0x31, 0x84, 0x00, 0x80, 0x5d, 0x78, 0xe8, 0x60, 0xe1,
+}
+
+var AVVolumeUp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x74, 0xe9, 0x98,
+ 0xe7, 0x90, 0x20, 0x94, 0x94, 0xe8, 0x60, 0x00, 0x6c, 0x74, 0xe6, 0x5c, 0xe3, 0xb6, 0x8c, 0xb0,
+ 0x80, 0x79, 0x7c, 0xf5, 0x7d, 0x6d, 0x79, 0x76, 0xf5, 0x77, 0xe9, 0x1d, 0x90, 0xb0, 0xf5, 0x82,
+ 0x85, 0x7e, 0x8a, 0x79, 0x7b, 0x8a, 0xf1, 0x77, 0xe2, 0x88, 0x75, 0x6e, 0xe9, 0x21, 0x84, 0xb0,
+ 0xc9, 0x85, 0xb9, 0x81, 0x94, 0x11, 0x87, 0x94, 0x69, 0x8d, 0x90, 0xc9, 0x7b, 0xb1, 0x8b, 0x6c,
+ 0x69, 0x8d, 0xe9, 0x21, 0x84, 0xb0, 0x05, 0x88, 0x31, 0x7e, 0x9c, 0x09, 0x77, 0x9c, 0x75, 0x6e,
+ 0x80, 0x05, 0x8c, 0x49, 0x70, 0x88, 0x75, 0x6e, 0xe1,
+}
+
+var AVWeb = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x8c, 0x98,
+ 0xe6, 0x60, 0xe9, 0x70, 0xe7, 0xac, 0xe9, 0x90, 0xe3, 0x80, 0x6c, 0xe6, 0x60, 0xe9, 0x70, 0xe7,
+ 0xac, 0xe9, 0x90, 0xe3, 0x94, 0x94, 0xe7, 0x70, 0xe8, 0x74, 0xe7, 0x90, 0xe9, 0xa4, 0xe1,
+}
+
+var AVWebAsset = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x60, 0xe6, 0x64,
+ 0xb0, 0xc9, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xc9,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x39, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x64, 0xe8,
+ 0x70, 0xe7, 0xb8, 0xe9, 0xa8, 0xe1,
+}
+
+var CommunicationBusiness = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x6c, 0xe8, 0x5c,
+ 0xe6, 0x58, 0xe9, 0xc8, 0xe7, 0xd0, 0xe8, 0x6c, 0xe6, 0x80, 0xe2, 0x68, 0x9c, 0xe6, 0x60, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x80, 0x70, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x60,
+ 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90, 0xb0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0xa8, 0xb0, 0xe6, 0x80, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0xa8, 0xe3, 0x78, 0x60,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe1,
+}
+
+var CommunicationCall = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x41, 0x75, 0x99, 0x7d,
+ 0xb0, 0xe1, 0x82, 0xa9, 0x85, 0x85, 0x87, 0x4d, 0x8a, 0x31, 0x8d, 0x2d, 0x8d, 0x20, 0x69, 0x84,
+ 0x99, 0x7b, 0xb0, 0x8d, 0x80, 0x75, 0x7f, 0x59, 0x81, 0x4d, 0x7f, 0x09, 0x82, 0x85, 0x7f, 0xa0,
+ 0x19, 0x8b, 0x99, 0x86, 0x85, 0x8d, 0x8e, 0xa0, 0x8e, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0xe5, 0x80,
+ 0x84, 0x84, 0xe9, 0x8e, 0xb0, 0x80, 0x1d, 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xa0, 0x39, 0x7d,
+ 0xa4, 0x5c, 0xc9, 0x82, 0x5c, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe7,
+ 0x8e, 0xb2, 0x1d, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x80, 0x7d, 0x82, 0x69, 0x80, 0xe9,
+ 0x84, 0x25, 0x81, 0x25, 0x87, 0x39, 0x80, 0xb1, 0x80, 0x11, 0x80, 0x7d, 0x81, 0x85, 0x7f, 0x09,
+ 0x82, 0x20, 0x99, 0x7b, 0x6d, 0x84, 0xe1,
+}
+
+var CommunicationCallEnd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x74, 0xb0, 0xcd,
+ 0x7c, 0x80, 0xb5, 0x79, 0x81, 0x80, 0xcd, 0x76, 0x71, 0x81, 0xe9, 0x35, 0x86, 0xb3, 0x80, 0xcd,
+ 0x80, 0x8d, 0x7f, 0x79, 0x81, 0xe1, 0x7e, 0xcd, 0x81, 0x0d, 0x7e, 0xfd, 0x80, 0x45, 0x7c, 0x3d,
+ 0x82, 0xad, 0x7a, 0xb5, 0x83, 0xa5, 0x7f, 0x59, 0x80, 0x29, 0x7f, 0x91, 0x80, 0x99, 0x7e, 0x91,
+ 0x80, 0x75, 0x7f, 0x80, 0xf5, 0x7e, 0xc9, 0x7f, 0x99, 0x7e, 0x69, 0x7f, 0x00, 0x99, 0x68, 0x31,
+ 0x82, 0xb1, 0xa1, 0x7f, 0xa1, 0x7f, 0x69, 0x7f, 0x21, 0x7f, 0x69, 0x7f, 0x95, 0x7e, 0x80, 0x75,
+ 0x7f, 0x39, 0x80, 0xf5, 0x7e, 0x99, 0x80, 0x95, 0x7e, 0xa0, 0xb1, 0x6e, 0x8d, 0x79, 0xf1, 0x76,
+ 0x6c, 0x80, 0x6c, 0x90, 0x51, 0x91, 0x8d, 0x83, 0x69, 0x97, 0x59, 0x89, 0xb1, 0x61, 0x80, 0x5d,
+ 0x80, 0x99, 0x80, 0xe1, 0x80, 0x99, 0x80, 0x6d, 0x81, 0x80, 0x8d, 0x80, 0xc9, 0x7f, 0x0d, 0x81,
+ 0x69, 0x7f, 0x69, 0x81, 0x20, 0x0d, 0x7b, 0xf5, 0x84, 0xb3, 0xa5, 0x7f, 0x5d, 0x80, 0x25, 0x7f,
+ 0x99, 0x80, 0x99, 0x7e, 0x99, 0x80, 0x75, 0x7f, 0x80, 0xf5, 0x7e, 0xc9, 0x7f, 0x99, 0x7e, 0x71,
+ 0x7f, 0x69, 0x7e, 0x89, 0x7e, 0xa1, 0x7c, 0x49, 0x7d, 0xad, 0x7a, 0x4d, 0x7c, 0x59, 0x7f, 0xad,
+ 0x7f, 0xe1, 0x7e, 0xfd, 0x7e, 0xe1, 0x7e, 0x35, 0x7e, 0xe9, 0xcd, 0x79, 0xa0, 0x4d, 0x86, 0x81,
+ 0x7a, 0x35, 0x83, 0x74, 0x80, 0x74, 0xe1,
+}
+
+var CommunicationCallMade = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x64, 0xe9, 0x88,
+ 0xe7, 0x2d, 0x8d, 0x02, 0x60, 0x2d, 0x8d, 0xd5, 0x72, 0xa0, 0x94, 0xd5, 0x78, 0xe8, 0x8c, 0xe7,
+ 0x88, 0xe8, 0x64, 0xe1,
+}
+
+var CommunicationCallMerge = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0xd5, 0x90, 0x03,
+ 0xd5, 0x8c, 0x9c, 0x8c, 0x2d, 0x87, 0x2d, 0x83, 0x94, 0x94, 0xd5, 0x90, 0xe2, 0x6e, 0x70, 0xe7,
+ 0x8e, 0xe9, 0x2d, 0x8b, 0x01, 0x2d, 0x73, 0x9c, 0x6c, 0xd5, 0x90, 0x20, 0x98, 0x68, 0xe8, 0x70,
+ 0xe7, 0x8e, 0x21, 0x6e, 0x6e, 0x6e, 0x92, 0xe1,
+}
+
+var CommunicationCallMissed = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x8f, 0x6c, 0x01,
+ 0x80, 0x2d, 0x85, 0xd5, 0x74, 0x74, 0xe6, 0x7c, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0xa0, 0xe7, 0x88,
+ 0xe9, 0xd5, 0x76, 0x21, 0x9c, 0x9c, 0xa4, 0x5c, 0xe1,
+}
+
+var CommunicationCallMissedOutgoing = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0xd1, 0x78, 0x21,
+ 0xa4, 0xa4, 0x9c, 0x64, 0xe8, 0x8c, 0xe7, 0x88, 0xe8, 0x6c, 0xe6, 0x84, 0xe9, 0x88, 0xe7, 0x31,
+ 0x89, 0x02, 0x80, 0x31, 0x85, 0xd1, 0x70, 0x6c, 0x5c, 0xd1, 0x78, 0xe1,
+}
+
+var CommunicationCallReceived = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xd5, 0x72, 0x01,
+ 0x2d, 0x8d, 0x60, 0x6c, 0x2d, 0x87, 0xe8, 0x74, 0xe7, 0x78, 0xe9, 0xa8, 0xe7, 0xa8, 0xe9, 0x78,
+ 0xe6, 0xd5, 0x78, 0xe1,
+}
+
+var CommunicationCallSplit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x60, 0x23, 0x99,
+ 0x84, 0x99, 0x84, 0x3d, 0x7a, 0xc1, 0x85, 0xd5, 0x82, 0xd5, 0x82, 0xc1, 0x85, 0x3d, 0x7a, 0x00,
+ 0xa0, 0x78, 0xe8, 0x60, 0xe3, 0x70, 0x80, 0xe6, 0x60, 0xe9, 0x98, 0x20, 0x99, 0x84, 0x69, 0x7b,
+ 0x00, 0x7c, 0xd5, 0x80, 0xe8, 0xa0, 0xe7, 0x88, 0xe8, 0x2d, 0x7f, 0x00, 0x69, 0x77, 0x99, 0x74,
+ 0xe1,
+}
+
+var CommunicationChat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0x74, 0xe7, 0xb0, 0xe9, 0x88, 0xe6, 0x68,
+ 0xe9, 0x78, 0xe3, 0xa0, 0x94, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe3, 0x90, 0x68,
+ 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe1,
+}
+
+var CommunicationChatBubble = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xc8, 0x20, 0x90, 0x70, 0xe7, 0xb8,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var CommunicationChatBubbleOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xc8, 0x20, 0x90, 0x70, 0xe7, 0xb8,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x68, 0x20, 0x78, 0x88, 0xe8, 0x60, 0xe7, 0xc0,
+ 0xe9, 0xb0, 0xe1,
+}
+
+var CommunicationClearAll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x84, 0xe7, 0xb8,
+ 0xe9, 0x78, 0xe6, 0x64, 0xe9, 0x88, 0xe3, 0x78, 0x90, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x90, 0x58, 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c, 0xe1,
+}
+
+var CommunicationComment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x93, 0x60, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x3d, 0x7e, 0x78, 0x05, 0x7c, 0x78, 0xe6, 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58,
+ 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0x21, 0x90, 0x90, 0xfd, 0x7f, 0x38, 0xe2, 0x98, 0x88, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0,
+ 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe3, 0x80, 0x74,
+ 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe1,
+}
+
+var CommunicationContactMail = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x70, 0xe9, 0x7c,
+ 0x21, 0x74, 0x88, 0x74, 0x78, 0xe9, 0x84, 0x21, 0x8c, 0x88, 0x8c, 0x78, 0xe3, 0x84, 0x6c, 0xe6,
+ 0x58, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xd0, 0xb0, 0x35, 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd,
+ 0x83, 0x78, 0x00, 0xb0, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x48,
+ 0x8c, 0xb1, 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c,
+ 0x74, 0x8c, 0x90, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xb0, 0x80, 0xb1, 0x7c, 0xb1, 0x82, 0x74, 0x8c,
+ 0x74, 0xe3, 0x98, 0xb0, 0xe6, 0x58, 0xe9, 0x7c, 0xb0, 0x80, 0x78, 0x90, 0xcd, 0x79, 0x98, 0xcd,
+ 0x79, 0x80, 0x88, 0x8c, 0x88, 0x94, 0xe9, 0x84, 0xe3, 0xa0, 0x68, 0xe6, 0x88, 0xe8, 0x68, 0xe7,
+ 0xa0, 0xe9, 0x98, 0xe1,
+}
+
+var CommunicationContactPhone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x5c, 0xe6, 0x58,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xd0, 0xb0, 0x35, 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83,
+ 0x78, 0x00, 0xb0, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x48, 0x8c,
+ 0xb1, 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74,
+ 0x8c, 0x90, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xb0, 0x80, 0xb1, 0x7c, 0xb1, 0x82, 0x74, 0x8c, 0x74,
+ 0xe3, 0x98, 0xb0, 0xe6, 0x58, 0xe9, 0x7c, 0xb0, 0x80, 0x78, 0x90, 0xcd, 0x79, 0x98, 0xcd, 0x79,
+ 0x80, 0x88, 0x8c, 0x88, 0x94, 0xe9, 0x84, 0xe3, 0xb5, 0x87, 0x70, 0xe7, 0x49, 0x83, 0x00, 0xa4,
+ 0x90, 0x20, 0x05, 0x7c, 0xfd, 0x83, 0xb1, 0x65, 0x7d, 0x0d, 0x7e, 0x71, 0x7b, 0x41, 0x7b, 0x8d,
+ 0x7a, 0x05, 0x78, 0xa9, 0x7f, 0xb9, 0x7e, 0x75, 0x7f, 0x65, 0x7d, 0x75, 0x7f, 0x78, 0x90, 0x35,
+ 0x80, 0x49, 0x7d, 0x91, 0x80, 0x78, 0xb0, 0xe5, 0x80, 0xc5, 0x7c, 0xd9, 0x82, 0xf9, 0x79, 0x75,
+ 0x85, 0x05, 0x78, 0x00, 0xa4, 0x70, 0x20, 0xfd, 0x7c, 0x88, 0xe6, 0xb5, 0x8b, 0xb0, 0x91, 0x7f,
+ 0x41, 0x81, 0x4d, 0x7f, 0x99, 0x82, 0x4d, 0x7f, 0x88, 0x90, 0x41, 0x80, 0xc1, 0x82, 0xb5, 0x80,
+ 0x88, 0xe1,
+}
+
+var CommunicationContacts = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x50, 0xe6, 0x60,
+ 0xe9, 0x88, 0xe7, 0xc0, 0xe8, 0x50, 0xe2, 0x60, 0xb0, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x60, 0xe9,
+ 0x88, 0xe2, 0xa0, 0x60, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe3, 0x60, 0x81, 0x85, 0xb1, 0x7d, 0x82, 0x80, 0x81, 0x84, 0x05, 0x82, 0x81, 0x84, 0x81, 0x84,
+ 0x80, 0x7d, 0x82, 0xfd, 0x7d, 0x81, 0x84, 0x81, 0x7b, 0x81, 0x84, 0x90, 0x81, 0x7b, 0xfd, 0x7d,
+ 0x81, 0x7b, 0x81, 0x7b, 0xb0, 0x80, 0x85, 0x7d, 0x05, 0x82, 0x81, 0x7b, 0x81, 0x84, 0x81, 0x7b,
+ 0xe2, 0x94, 0x94, 0xe6, 0x6c, 0xe9, 0x7a, 0xb0, 0x80, 0xad, 0x7c, 0xad, 0x86, 0x76, 0x94, 0x76,
+ 0x90, 0x94, 0xad, 0x81, 0x94, 0x8a, 0xe9, 0x86, 0xe1,
+}
+
+var CommunicationDialerSIP = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe7, 0x7c,
+ 0xe9, 0x94, 0xe7, 0x84, 0xe8, 0x5c, 0xe3, 0x78, 0x88, 0xe7, 0x78, 0xe8, 0x60, 0xe7, 0x88, 0xe8,
+ 0x5c, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x84, 0xe7, 0x78, 0xe9, 0x84, 0xe7, 0x8c, 0xe9,
+ 0x74, 0xe3, 0x8c, 0x78, 0xe9, 0x94, 0xe7, 0x84, 0xe9, 0x78, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x74,
+ 0xe3, 0x88, 0x88, 0xe7, 0x7c, 0xe8, 0x60, 0xe7, 0x84, 0xe9, 0x84, 0xe3, 0x80, 0xaa, 0xb1, 0x85,
+ 0x7d, 0x80, 0x1d, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9, 0x7f, 0x85, 0x7e,
+ 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x59, 0x7a, 0x21, 0x7d,
+ 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x99, 0x7b, 0xb0, 0x8d, 0x80,
+ 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79, 0xe9, 0x74, 0x72,
+ 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb2,
+ 0xe5, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f, 0xc4, 0xc4, 0xc4,
+ 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var CommunicationDialpad = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x9c, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0x54, 0xa0, 0xcd, 0x71, 0x54, 0x60, 0xcd,
+ 0x6b, 0x60, 0x5c, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x80, 0x98, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0x80, 0x98, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0xb0, 0x60, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe2, 0x80, 0x84, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x98, 0x80, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0x80, 0x68, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x68, 0x80, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x68, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe1,
+}
+
+var CommunicationEmail = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x90,
+ 0x01, 0x80, 0x84, 0x60, 0x70, 0xe9, 0x78, 0x21, 0xa0, 0x94, 0xa0, 0x6c, 0xe9, 0x88, 0xe1,
+}
+
+var CommunicationForum = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x68, 0xe7, 0x78,
+ 0xe9, 0xa4, 0xe6, 0x68, 0xe9, 0x88, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xac, 0x20, 0x90, 0x90, 0xe8, 0x6c, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3,
+ 0x70, 0x98, 0xe8, 0x5c, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x5c, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb8, 0x20, 0x90, 0x70, 0xe7, 0xa8, 0xb0,
+ 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe1,
+}
+
+var CommunicationImportContacts = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x64, 0xb2, 0xcd,
+ 0x7d, 0x4d, 0x7f, 0x59, 0x7b, 0x7e, 0x72, 0x7e, 0x19, 0x7c, 0x80, 0xe9, 0x77, 0xcd, 0x80, 0x6a,
+ 0x86, 0x19, 0x7d, 0xcd, 0x7d, 0xe9, 0x78, 0x7a, 0x6a, 0x7a, 0x90, 0xe9, 0x77, 0xcd, 0x80, 0x6a,
+ 0x86, 0xe9, 0x4d, 0x9d, 0xb1, 0x80, 0x81, 0x80, 0x81, 0x80, 0x82, 0x82, 0x82, 0x35, 0x80, 0x80,
+ 0x4d, 0x80, 0xe9, 0x7f, 0x81, 0x80, 0xe9, 0x7f, 0xa0, 0x35, 0x6e, 0xe9, 0x90, 0x19, 0x72, 0xa0,
+ 0x6a, 0xa0, 0xb4, 0xe9, 0x83, 0x80, 0x19, 0x88, 0xcd, 0x80, 0x96, 0x86, 0xb5, 0x82, 0x4d, 0x7e,
+ 0x99, 0x87, 0x7a, 0x96, 0x7a, 0x4d, 0x83, 0x80, 0xb5, 0x86, 0x99, 0x80, 0x81, 0x89, 0x19, 0x82,
+ 0x35, 0x80, 0x19, 0x80, 0x4d, 0x80, 0x19, 0x80, 0x81, 0x80, 0x19, 0x80, 0x81, 0x80, 0x80, 0x82,
+ 0x81, 0x7f, 0x82, 0x7e, 0xe8, 0x68, 0xb0, 0xcd, 0x7e, 0x19, 0x7f, 0x81, 0x7d, 0x81, 0x7e, 0x78,
+ 0x7c, 0xe3, 0x80, 0xb6, 0xb1, 0xcd, 0x7d, 0x4d, 0x7f, 0x69, 0x7b, 0x7e, 0x72, 0x7e, 0x99, 0x7c,
+ 0x80, 0xb5, 0x77, 0x4d, 0x81, 0x6a, 0x86, 0xe8, 0x70, 0xb1, 0xb5, 0x82, 0x4d, 0x7e, 0x99, 0x87,
+ 0x7a, 0x96, 0x7a, 0x69, 0x82, 0x80, 0xcd, 0x84, 0x4d, 0x80, 0x8e, 0x82, 0xe9, 0xae, 0xe1,
+}
+
+var CommunicationImportExport = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x5c, 0x20, 0x70,
+ 0xfd, 0x87, 0xe7, 0x8c, 0xe8, 0x88, 0xe7, 0x88, 0xe8, 0xfd, 0x75, 0xe7, 0x8c, 0x00, 0x74, 0x5c,
+ 0xe3, 0x9c, 0x05, 0x9c, 0xe8, 0x78, 0xe7, 0x78, 0xe9, 0x05, 0x8e, 0xe7, 0x74, 0x00, 0x8c, 0xa4,
+ 0x20, 0x90, 0x05, 0x78, 0xe7, 0x74, 0xe1,
+}
+
+var CommunicationInvertColorsOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x4d, 0x91, 0xc1, 0x91,
+ 0x20, 0x4d, 0x7b, 0x4d, 0x7b, 0x00, 0x80, 0x75, 0x80, 0x21, 0xe1, 0x78, 0xe1, 0x78, 0x2d, 0x7d,
+ 0x2d, 0x7d, 0x01, 0x8d, 0x70, 0x62, 0x5c, 0x8d, 0x73, 0x20, 0x91, 0x85, 0x91, 0x85, 0xb2, 0xe9,
+ 0x7a, 0x49, 0x86, 0x49, 0x7b, 0x89, 0x8f, 0x21, 0x81, 0x61, 0x95, 0x21, 0x83, 0x21, 0x83, 0x39,
+ 0x87, 0xb1, 0x84, 0x51, 0x8b, 0xb1, 0x84, 0x91, 0x83, 0x80, 0x25, 0x87, 0xd1, 0x7e, 0x11, 0x8a,
+ 0x71, 0x7c, 0x20, 0x69, 0x85, 0x61, 0x85, 0x00, 0xa4, 0x75, 0x92, 0x20, 0x4d, 0x7f, 0x4d, 0x7f,
+ 0xe2, 0x80, 0x2d, 0x8f, 0xb2, 0xcd, 0x7c, 0x80, 0xc9, 0x79, 0xc1, 0x7e, 0x85, 0x77, 0x7d, 0x7c,
+ 0xbd, 0x7d, 0xbd, 0x7d, 0x7d, 0x7c, 0xb9, 0x7a, 0x7d, 0x7c, 0x85, 0x77, 0x80, 0x5d, 0x7d, 0xdd,
+ 0x80, 0xdd, 0x7a, 0x6d, 0x82, 0xcd, 0x78, 0x00, 0x80, 0x8d, 0x85, 0xe9, 0xa1, 0x89, 0xe3, 0x80,
+ 0x09, 0x63, 0xe9, 0x29, 0x89, 0x20, 0x85, 0x8e, 0x85, 0x8e, 0xb0, 0xbd, 0x82, 0x19, 0x7a, 0xb1,
+ 0x81, 0xdd, 0x72, 0xcd, 0x7c, 0xfd, 0x6d, 0x00, 0x80, 0x8d, 0x6c, 0x21, 0x99, 0x78, 0x69, 0x87,
+ 0xd5, 0x82, 0xd5, 0x82, 0x00, 0x80, 0x35, 0x72, 0xe1,
+}
+
+var CommunicationLiveHelp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x58, 0xe6, 0x64,
+ 0xa0, 0xcd, 0x6f, 0x58, 0x5c, 0xcd, 0x6d, 0x5c, 0x60, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0x21, 0x8c, 0x8c, 0x8c, 0x74, 0xe7, 0x90, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe2, 0x84, 0x98, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x21, 0x84, 0x85,
+ 0x70, 0x20, 0x35, 0x7e, 0xd9, 0x81, 0xa0, 0xe9, 0x82, 0xcd, 0x7f, 0x84, 0x82, 0x84, 0x88, 0xe7,
+ 0x78, 0xe9, 0x7e, 0xb0, 0x80, 0xcd, 0x7d, 0xe9, 0x80, 0xcd, 0x7b, 0x59, 0x82, 0x59, 0x7a, 0x20,
+ 0x7d, 0x82, 0x7d, 0x7d, 0xa0, 0x8d, 0x83, 0x19, 0x7a, 0x88, 0x19, 0x79, 0x88, 0x70, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x90, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe7, 0x78, 0xb0,
+ 0x80, 0x95, 0x7b, 0x95, 0x83, 0x70, 0x90, 0x70, 0x90, 0x90, 0x95, 0x83, 0x90, 0x90, 0xb0, 0x80,
+ 0xc5, 0x81, 0x4d, 0x7f, 0x59, 0x83, 0x21, 0x7e, 0x85, 0x84, 0xe1,
+}
+
+var CommunicationLocationOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x6a, 0xb1, 0xc5,
+ 0x82, 0x80, 0x8a, 0x3d, 0x82, 0x8a, 0x8a, 0x80, 0x79, 0x81, 0x59, 0x7f, 0xc9, 0x82, 0x59, 0x7e,
+ 0xb5, 0x83, 0x20, 0x45, 0x87, 0x45, 0x87, 0xa0, 0x8d, 0x8c, 0x3d, 0x81, 0x9c, 0x61, 0x7d, 0x9c,
+ 0x74, 0xb1, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0x0d, 0x7c, 0x80, 0x79, 0x78, 0xa9,
+ 0x81, 0xf1, 0x75, 0x4d, 0x84, 0x20, 0x61, 0x86, 0x61, 0x86, 0xa0, 0x35, 0x7d, 0xa9, 0x75, 0x89,
+ 0x7e, 0x6a, 0x80, 0x6a, 0xe3, 0xc1, 0x88, 0x35, 0x93, 0x21, 0xc1, 0x76, 0xc1, 0x76, 0xc9, 0x7f,
+ 0xc9, 0x7f, 0x01, 0x8d, 0x6e, 0x5c, 0x58, 0x8d, 0x70, 0x20, 0x5d, 0x86, 0x5d, 0x86, 0xb1, 0xc5,
+ 0x7f, 0xfd, 0x80, 0xa5, 0x7f, 0x05, 0x82, 0xa5, 0x7f, 0x19, 0x83, 0x80, 0x81, 0x8a, 0x9c, 0xb4,
+ 0x9c, 0xb4, 0x90, 0x59, 0x83, 0x4d, 0x7c, 0xc1, 0x86, 0x4d, 0x77, 0x20, 0xb5, 0x86, 0xb5, 0x86,
+ 0x00, 0xa0, 0x75, 0x8f, 0x20, 0xc1, 0x78, 0xc1, 0x78, 0xe1,
+}
+
+var CommunicationLocationOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb1, 0x45,
+ 0x78, 0x80, 0x64, 0x45, 0x86, 0x64, 0x9c, 0x80, 0x81, 0x8a, 0x9c, 0xb4, 0x9c, 0xb4, 0x90, 0x9c,
+ 0x81, 0x70, 0x9c, 0x4c, 0xb0, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0xe3, 0x80, 0xa6,
+ 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x92, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x8a,
+ 0x3d, 0x82, 0x8a, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x8a, 0xe1,
+}
+
+var CommunicationMailOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8,
+ 0xe6, 0x60, 0xe8, 0x70, 0x21, 0xa0, 0x94, 0xa0, 0x6c, 0xe9, 0xa8, 0xe2, 0x80, 0x7c, 0x00, 0x60,
+ 0x68, 0xe7, 0xc0, 0x00, 0x80, 0x7c, 0xe1,
+}
+
+var CommunicationMessage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xb0, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0,
+ 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe3, 0x80, 0x74,
+ 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe1,
+}
+
+var CommunicationNoSIM = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x8d, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x3d, 0x7e, 0x78, 0x05, 0x7c, 0x78, 0xe6, 0x78, 0x20, 0x51, 0x7b, 0xb1, 0x84,
+ 0x01, 0x9c, 0x5d, 0x89, 0xfd, 0x8d, 0x64, 0xe2, 0x4d, 0x6f, 0xc5, 0x6f, 0x01, 0xc5, 0x6c, 0x4d,
+ 0x72, 0x64, 0x8d, 0x77, 0xe8, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0x05, 0x94, 0xb0, 0xb5, 0x80, 0x80, 0x59, 0x81, 0xcd, 0x7f, 0xed, 0x81, 0x7d, 0x7f, 0x21, 0xc5,
+ 0x83, 0xc5, 0x83, 0x8d, 0x82, 0x75, 0x7d, 0x00, 0x4d, 0x6f, 0xc5, 0x6f, 0xe1,
+}
+
+var CommunicationPhone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x41, 0x75, 0x99, 0x7d,
+ 0xb0, 0xe1, 0x82, 0xa9, 0x85, 0x85, 0x87, 0x4d, 0x8a, 0x31, 0x8d, 0x2d, 0x8d, 0x20, 0x69, 0x84,
+ 0x99, 0x7b, 0xb0, 0x8d, 0x80, 0x75, 0x7f, 0x59, 0x81, 0x4d, 0x7f, 0x09, 0x82, 0x85, 0x7f, 0xa0,
+ 0x19, 0x8b, 0x99, 0x86, 0x85, 0x8d, 0x8e, 0xa0, 0x8e, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0xe5, 0x80,
+ 0x84, 0x84, 0xe9, 0x8e, 0xb0, 0x80, 0x1d, 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xa0, 0x39, 0x7d,
+ 0xa4, 0x5c, 0xc9, 0x82, 0x5c, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe7,
+ 0x8e, 0xb2, 0x1d, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x80, 0x7d, 0x82, 0x69, 0x80, 0xe9,
+ 0x84, 0x25, 0x81, 0x25, 0x87, 0x39, 0x80, 0xb1, 0x80, 0x11, 0x80, 0x7d, 0x81, 0x85, 0x7f, 0x09,
+ 0x82, 0x20, 0x99, 0x7b, 0x6d, 0x84, 0xe1,
+}
+
+var CommunicationPhoneLinkErase = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x69, 0x78, 0x2b,
+ 0x7c, 0x7c, 0x70, 0x90, 0x70, 0x70, 0x7c, 0x84, 0x90, 0x90, 0x70, 0x90, 0x84, 0x84, 0x90, 0x70,
+ 0x90, 0x90, 0x84, 0x7c, 0x70, 0x70, 0x90, 0x70, 0xe2, 0x9c, 0x54, 0xe6, 0x74, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c, 0xe7, 0x88, 0xe8, 0x60, 0xe7, 0xa8, 0xe9, 0xc0,
+ 0xe6, 0x74, 0xe9, 0x78, 0xe6, 0x6c, 0xe9, 0x8c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var CommunicationPhoneLinkLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x54, 0xe6, 0x74,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c, 0xe7, 0x88, 0xe8, 0x60, 0xe7,
+ 0xa8, 0xe9, 0xc0, 0xe6, 0x74, 0xe9, 0x78, 0xe6, 0x6c, 0xe9, 0x8c, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x99, 0x6f, 0xa8, 0xe8, 0x76,
+ 0xa0, 0x99, 0x7d, 0x35, 0x78, 0xcd, 0x7a, 0x6c, 0x70, 0x6c, 0x80, 0x69, 0x72, 0x35, 0x78, 0x69,
+ 0x72, 0x76, 0xe8, 0x7c, 0xb0, 0xcd, 0x7e, 0x80, 0x99, 0x7d, 0x35, 0x81, 0x99, 0x7d, 0x69, 0x82,
+ 0xe9, 0x8e, 0xb0, 0x80, 0x69, 0x81, 0x35, 0x81, 0x99, 0x82, 0x69, 0x82, 0x99, 0x82, 0xe7, 0x96,
+ 0xb0, 0x69, 0x81, 0x80, 0x99, 0x82, 0xcd, 0x7e, 0x99, 0x82, 0x99, 0x7d, 0xe9, 0x72, 0xb0, 0x80,
+ 0x99, 0x7e, 0xcd, 0x7e, 0x69, 0x7d, 0x99, 0x7d, 0x69, 0x7d, 0xe3, 0x69, 0x7d, 0x80, 0xe7, 0x74,
+ 0xe8, 0x76, 0xb0, 0x80, 0x69, 0x7e, 0x69, 0x81, 0x69, 0x7d, 0x86, 0x69, 0x7d, 0x90, 0x86, 0x82,
+ 0x86, 0x99, 0x82, 0xe8, 0x7c, 0xe1,
+}
+
+var CommunicationPhoneLinkRing = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x35, 0x90, 0x69, 0x77,
+ 0x20, 0x7c, 0x84, 0xb0, 0x99, 0x83, 0x99, 0x83, 0x99, 0x83, 0x35, 0x89, 0x80, 0x9a, 0x20, 0x84,
+ 0x84, 0xb0, 0x8a, 0x69, 0x7b, 0x8a, 0xcd, 0x73, 0x80, 0x5e, 0xe2, 0x98, 0x99, 0x7b, 0x20, 0x7c,
+ 0x84, 0xb0, 0x82, 0x69, 0x81, 0x82, 0x35, 0x83, 0x80, 0x99, 0x84, 0x20, 0x84, 0x84, 0xb0, 0x69,
+ 0x82, 0x99, 0x7d, 0x69, 0x82, 0x74, 0x80, 0x69, 0x77, 0xe2, 0x88, 0x54, 0xe6, 0x60, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xcc, 0xe6, 0x60, 0xe8, 0x60, 0xe7,
+ 0xa8, 0xe9, 0xc0, 0xe1,
+}
+
+var CommunicationPhoneLinkSetup = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x99, 0x7f, 0x82, 0xe9,
+ 0x7c, 0x20, 0x35, 0x82, 0x69, 0x7e, 0xb0, 0x35, 0x80, 0xcd, 0x7f, 0x35, 0x80, 0x99, 0x7f, 0x35,
+ 0x80, 0x69, 0x7f, 0x20, 0x7c, 0x99, 0x7c, 0xb0, 0xcd, 0x7f, 0xcd, 0x7f, 0x99, 0x7f, 0x99, 0x7f,
+ 0x69, 0x7f, 0xcd, 0x7f, 0x20, 0x69, 0x7d, 0xcd, 0x80, 0xb0, 0x69, 0x7f, 0x99, 0x7f, 0xcd, 0x7e,
+ 0x35, 0x7f, 0x35, 0x7e, 0x7e, 0x20, 0x99, 0x7f, 0x69, 0x7d, 0xb0, 0x80, 0xcd, 0x7f, 0xcd, 0x7f,
+ 0x99, 0x7f, 0x69, 0x7f, 0x99, 0x7f, 0xe6, 0x6c, 0xb0, 0xcd, 0x7f, 0x80, 0x99, 0x7f, 0x35, 0x80,
+ 0x69, 0x7f, 0x69, 0x80, 0x20, 0x99, 0x7f, 0x99, 0x82, 0xb0, 0x69, 0x7f, 0x35, 0x80, 0xcd, 0x7e,
+ 0x99, 0x80, 0x35, 0x7e, 0x82, 0x20, 0x69, 0x7d, 0x7e, 0xb0, 0xcd, 0x7f, 0x80, 0x99, 0x7f, 0x80,
+ 0x69, 0x7f, 0x35, 0x80, 0x20, 0x7c, 0x69, 0x83, 0xb0, 0xcd, 0x7f, 0x35, 0x80, 0x80, 0x69, 0x80,
+ 0x35, 0x80, 0x99, 0x80, 0x20, 0x35, 0x82, 0x99, 0x81, 0xe9, 0x84, 0x20, 0xcd, 0x7d, 0x99, 0x81,
+ 0xb0, 0xcd, 0x7f, 0x69, 0x80, 0xcd, 0x7f, 0x99, 0x80, 0xcd, 0x7f, 0xcd, 0x80, 0x20, 0x84, 0x69,
+ 0x83, 0xb0, 0x35, 0x80, 0x35, 0x80, 0x69, 0x80, 0x69, 0x80, 0x99, 0x80, 0x35, 0x80, 0x20, 0xcd,
+ 0x82, 0x35, 0x7f, 0xb0, 0x99, 0x80, 0x69, 0x80, 0x35, 0x81, 0xcd, 0x80, 0xcd, 0x81, 0x82, 0x20,
+ 0x69, 0x80, 0x99, 0x82, 0xb0, 0xcd, 0x7f, 0x35, 0x80, 0x35, 0x80, 0x69, 0x80, 0x69, 0x80, 0x69,
+ 0x80, 0xe7, 0x88, 0xb0, 0x35, 0x80, 0x80, 0x69, 0x80, 0xcd, 0x7f, 0x99, 0x80, 0x99, 0x7f, 0x20,
+ 0x69, 0x80, 0x69, 0x7d, 0xb0, 0x99, 0x80, 0xcd, 0x7f, 0x35, 0x81, 0x69, 0x7f, 0xcd, 0x81, 0x7e,
+ 0x20, 0x99, 0x82, 0x82, 0xb0, 0x35, 0x80, 0x80, 0x69, 0x80, 0x80, 0x99, 0x80, 0xcd, 0x7f, 0x20,
+ 0x84, 0x99, 0x7c, 0xb0, 0x35, 0x80, 0xcd, 0x7f, 0x80, 0x99, 0x7f, 0xcd, 0x7f, 0x69, 0x7f, 0x20,
+ 0xcd, 0x7d, 0x35, 0x7e, 0xe2, 0x70, 0x88, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78,
+ 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88,
+ 0xe2, 0x9c, 0x54, 0xe6, 0x74, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c,
+ 0xe7, 0x88, 0xe8, 0x60, 0xe7, 0xa8, 0xe9, 0xc0, 0xe6, 0x74, 0xe9, 0x78, 0xe6, 0x6c, 0xe9, 0x8c,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var CommunicationPortableWiFiOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x21, 0x8b, 0x7d, 0x84,
+ 0xb2, 0x91, 0x80, 0xa1, 0x7e, 0xe1, 0x80, 0x1d, 0x7d, 0xe1, 0x80, 0x85, 0x7b, 0x80, 0x61, 0x79,
+ 0xa1, 0x7a, 0x68, 0x68, 0x68, 0x69, 0x7e, 0x80, 0xe9, 0x7c, 0x51, 0x80, 0x85, 0x7b, 0xe1, 0x80,
+ 0x20, 0x41, 0x83, 0x41, 0x83, 0xb2, 0x69, 0x80, 0xf1, 0x7f, 0xd1, 0x80, 0xe1, 0x7f, 0x3d, 0x81,
+ 0xe1, 0x7f, 0x6d, 0x84, 0x80, 0x90, 0x95, 0x83, 0x90, 0x90, 0x80, 0x71, 0x80, 0xf5, 0x7f, 0xd9,
+ 0x80, 0xe5, 0x7f, 0x41, 0x81, 0x20, 0x3d, 0x83, 0x3d, 0x83, 0xe2, 0x80, 0x60, 0xb1, 0xd9, 0x88,
+ 0x80, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0x80, 0xb5, 0x82, 0x4d, 0x7f, 0x3d, 0x85, 0x1d, 0x7e, 0x79,
+ 0x87, 0x20, 0xf1, 0x82, 0xf1, 0x82, 0xa0, 0xe9, 0x92, 0x61, 0x87, 0xa8, 0xd1, 0x83, 0xa8, 0x80,
+ 0xb1, 0x80, 0xf5, 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58, 0x31, 0x7c, 0x80, 0xa1, 0x78, 0x19, 0x81,
+ 0x99, 0x75, 0xf5, 0x82, 0x20, 0xed, 0x82, 0xed, 0x82, 0xa0, 0xbd, 0x7a, 0xb1, 0x70, 0x4d, 0x7d,
+ 0x60, 0x80, 0x60, 0xe2, 0x8d, 0x6e, 0x5a, 0x00, 0x58, 0x8d, 0x6f, 0x20, 0x35, 0x84, 0x35, 0x84,
+ 0xa0, 0x95, 0x6d, 0x25, 0x77, 0x58, 0x61, 0x7b, 0x58, 0x80, 0xb0, 0x80, 0x65, 0x87, 0x05, 0x84,
+ 0xd5, 0x8d, 0xfd, 0x89, 0x4d, 0x91, 0x20, 0x84, 0x8d, 0x7c, 0xa0, 0x39, 0x73, 0x11, 0x8b, 0x60,
+ 0xe9, 0x85, 0x60, 0x80, 0xb0, 0x80, 0x7d, 0x7c, 0x25, 0x81, 0x41, 0x79, 0x11, 0x83, 0x9d, 0x76,
+ 0x20, 0xe1, 0x82, 0xe1, 0x82, 0xa0, 0xb5, 0x74, 0x5d, 0x7b, 0x68, 0x99, 0x7d, 0x68, 0x80, 0xb0,
+ 0x80, 0x71, 0x84, 0x69, 0x82, 0x4d, 0x88, 0xfd, 0x85, 0x61, 0x8a, 0x20, 0x05, 0x82, 0x85, 0x7c,
+ 0xa0, 0xa1, 0x79, 0x85, 0x85, 0x70, 0xf5, 0x82, 0x70, 0x80, 0xb0, 0x80, 0xb5, 0x7e, 0x59, 0x80,
+ 0x85, 0x7d, 0xe1, 0x80, 0x71, 0x7c, 0x20, 0x29, 0x83, 0x29, 0x83, 0x00, 0x78, 0x80, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x21, 0x69, 0x80, 0xf5, 0x7f, 0x05, 0x80, 0x05, 0x80,
+ 0x02, 0x75, 0x8f, 0xa6, 0xa4, 0x75, 0x90, 0x8d, 0x70, 0x5e, 0x20, 0x7c, 0x7c, 0xe1,
+}
+
+var CommunicationPresentToAll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x09, 0xa0, 0xe6, 0x5c,
+ 0xe8, 0xf9, 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe2, 0x78, 0x80, 0xe7, 0x78, 0x21, 0x90, 0x70,
+ 0x90, 0x90, 0xe7, 0x78, 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x70, 0xe1,
+}
+
+var CommunicationRingVolume = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x97, 0x59, 0x89,
+ 0xa0, 0x51, 0x91, 0x8d, 0x83, 0x11, 0x89, 0x80, 0x80, 0x80, 0x80, 0xb1, 0x6e, 0x8d, 0x83, 0x99,
+ 0x68, 0x59, 0x89, 0xb1, 0xa1, 0x7f, 0x5d, 0x80, 0x69, 0x7f, 0xe1, 0x80, 0x69, 0x7f, 0x6d, 0x81,
+ 0x80, 0x8d, 0x80, 0x39, 0x80, 0x0d, 0x81, 0x99, 0x80, 0x69, 0x81, 0x20, 0xf5, 0x84, 0xf5, 0x84,
+ 0xb3, 0x5d, 0x80, 0x5d, 0x80, 0xdd, 0x80, 0x99, 0x80, 0x69, 0x81, 0x99, 0x80, 0x8d, 0x80, 0x80,
+ 0x0d, 0x81, 0xc9, 0x7f, 0x69, 0x81, 0x71, 0x7f, 0x99, 0x81, 0x89, 0x7e, 0x61, 0x83, 0x49, 0x7d,
+ 0x55, 0x85, 0x4d, 0x7c, 0xa9, 0x80, 0xad, 0x7f, 0x21, 0x81, 0xfd, 0x7e, 0x21, 0x81, 0x35, 0x7e,
+ 0xe9, 0xcd, 0x79, 0xa0, 0xb5, 0x79, 0x81, 0x84, 0xcd, 0x7c, 0x88, 0x80, 0x88, 0x90, 0x4d, 0x86,
+ 0x81, 0x80, 0x35, 0x89, 0x71, 0x81, 0xe9, 0x35, 0x86, 0xb3, 0x80, 0xcd, 0x80, 0x75, 0x80, 0x79,
+ 0x81, 0x21, 0x81, 0xcd, 0x81, 0xf5, 0x81, 0xfd, 0x80, 0xc1, 0x83, 0x3d, 0x82, 0x55, 0x85, 0xb5,
+ 0x83, 0x5d, 0x80, 0x59, 0x80, 0xd9, 0x80, 0x91, 0x80, 0x69, 0x81, 0x91, 0x80, 0x8d, 0x80, 0x80,
+ 0x0d, 0x81, 0xc9, 0x7f, 0x6d, 0x81, 0x69, 0x7f, 0x20, 0xf5, 0x84, 0x0d, 0x7b, 0xb1, 0x5d, 0x80,
+ 0xa5, 0x7f, 0x99, 0x80, 0x25, 0x7f, 0x99, 0x80, 0x99, 0x7e, 0xfd, 0x7f, 0x71, 0x7f, 0xc5, 0x7f,
+ 0xf1, 0x7e, 0x69, 0x7f, 0x91, 0x7e, 0xe3, 0xe9, 0x7a, 0x2d, 0x6b, 0x22, 0x2d, 0x7d, 0x2d, 0x7d,
+ 0xe1, 0x78, 0x21, 0x87, 0xd5, 0x82, 0xd5, 0x82, 0x90, 0xe9, 0x86, 0xf5, 0x78, 0x21, 0x87, 0xe1,
+ 0x78, 0xe2, 0x84, 0x58, 0xe7, 0x78, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x58, 0xe2, 0xcd, 0x74, 0xa1,
+ 0x7b, 0x22, 0xd5, 0x82, 0x2d, 0x7d, 0xe1, 0x78, 0xe1, 0x78, 0x2d, 0x7d, 0xd5, 0x82, 0xb0, 0x39,
+ 0x80, 0x15, 0x80, 0x21, 0x87, 0x21, 0x87, 0x21, 0x87, 0x21, 0x87, 0xe1,
+}
+
+var CommunicationRSSFeed = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0xe5, 0x70, 0xe9,
+ 0xa9, 0x85, 0xb0, 0x11, 0x8e, 0x80, 0x75, 0x99, 0x69, 0x8b, 0x75, 0x99, 0x75, 0x99, 0xe7, 0xa9,
+ 0x85, 0xa0, 0x1d, 0x8f, 0xd1, 0x7e, 0x31, 0x81, 0xe5, 0x70, 0x60, 0xe5, 0x70, 0xe2, 0x60, 0x35,
+ 0x7c, 0xe9, 0xa9, 0x85, 0xb0, 0xd1, 0x87, 0x80, 0x25, 0x8e, 0x59, 0x86, 0x25, 0x8e, 0x25, 0x8e,
+ 0xe7, 0xa9, 0x85, 0xb0, 0x80, 0x11, 0x75, 0x21, 0x77, 0x35, 0x6c, 0x35, 0x6c, 0x35, 0x6c, 0xe2,
+ 0x60, 0xa5, 0x8b, 0xd1, 0x5d, 0x84, 0x5d, 0x84, 0x00, 0x04, 0xb9, 0x88, 0x80, 0x5d, 0x84, 0x5d,
+ 0x84, 0x00, 0x04, 0x49, 0x77, 0x80, 0xe1,
+}
+
+var CommunicationScreenShare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x98, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xa8, 0x68, 0xb0, 0x80, 0xc9, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xc9, 0x7d, 0x80, 0x78, 0xc9, 0x81, 0x78, 0x88,
+ 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xc9, 0x81, 0x88, 0x88, 0x88, 0xe6, 0x50, 0xe9, 0x88, 0xe7,
+ 0xe0, 0xe9, 0x78, 0xe7, 0x70, 0xe3, 0x64, 0xf1, 0x78, 0xe9, 0xa1, 0x7b, 0xb1, 0x71, 0x7a, 0x80,
+ 0xc9, 0x76, 0xb5, 0x81, 0x68, 0x71, 0x85, 0x21, 0x81, 0xa9, 0x7a, 0x39, 0x84, 0x59, 0x75, 0x98,
+ 0x45, 0x74, 0xe8, 0x6c, 0x21, 0x90, 0x75, 0x87, 0x70, 0x7d, 0x87, 0xe1,
+}
+
+var CommunicationSpeakerPhone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x25, 0x77, 0x00,
+ 0xdd, 0x78, 0x74, 0xb0, 0xd5, 0x81, 0x2d, 0x7e, 0x59, 0x84, 0x0d, 0x7d, 0x25, 0x87, 0x0d, 0x7d,
+ 0x90, 0x51, 0x85, 0x21, 0x81, 0x25, 0x87, 0xf5, 0x82, 0x00, 0x94, 0x25, 0x77, 0xa0, 0x71, 0x87,
+ 0x99, 0x74, 0xe9, 0x83, 0x66, 0x80, 0x66, 0x90, 0x91, 0x78, 0x99, 0x81, 0x6c, 0x25, 0x84, 0xe2,
+ 0x80, 0x56, 0xa0, 0xf5, 0x79, 0x56, 0x79, 0x74, 0x75, 0x6d, 0x81, 0x70, 0x6d, 0x71, 0x20, 0xd1,
+ 0x82, 0xd1, 0x82, 0xa0, 0x91, 0x76, 0xfd, 0x70, 0x0d, 0x7b, 0xfd, 0x6e, 0x80, 0xfd, 0x6e, 0x90,
+ 0x71, 0x89, 0x84, 0xb1, 0x8c, 0x41, 0x85, 0x20, 0xd1, 0x82, 0x31, 0x7d, 0xa0, 0x89, 0x8b, 0x75,
+ 0x6d, 0x0d, 0x86, 0x56, 0x80, 0x56, 0xe3, 0xb5, 0x85, 0x05, 0x91, 0x00, 0x4d, 0x7a, 0x78, 0xa0,
+ 0x09, 0x79, 0x78, 0x70, 0x09, 0x7d, 0x70, 0x4d, 0x7e, 0xe9, 0x71, 0x93, 0xb0, 0x80, 0x45, 0x81,
+ 0x09, 0x81, 0x4d, 0x82, 0x4d, 0x82, 0x4d, 0x82, 0xe7, 0x71, 0x8b, 0xb0, 0x45, 0x81, 0x80, 0x4d,
+ 0x82, 0xf9, 0x7e, 0x4d, 0x82, 0xb5, 0x7d, 0xe8, 0x4d, 0x7e, 0xb0, 0xfd, 0x7f, 0xbd, 0x7e, 0xf5,
+ 0x7e, 0xb9, 0x7d, 0xb5, 0x7d, 0xb9, 0x7d, 0xe2, 0x8c, 0xa0, 0xe6, 0x74, 0xe8, 0x80, 0xe7, 0x98,
+ 0xe9, 0xa0, 0xe1,
+}
+
+var CommunicationStayCurrentLandscape = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x6a, 0x6c, 0x00,
+ 0x54, 0x94, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0xe2, 0x9c,
+ 0x6c, 0xe9, 0xa8, 0xe6, 0x64, 0xe8, 0x6c, 0xe7, 0xb8, 0xe1,
+}
+
+var CommunicationStayCurrentPortrait = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x05, 0x6a, 0x00,
+ 0x6c, 0x54, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0xe9, 0xc8, 0xb0,
+ 0x80, 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05,
+ 0x7c, 0xe2, 0x94, 0x9c, 0xe6, 0x6c, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe1,
+}
+
+var CommunicationStayPrimaryLandscape = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x6a, 0x6c, 0x00,
+ 0x54, 0x94, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0xe2, 0x9c,
+ 0x6c, 0xe9, 0xa8, 0xe6, 0x64, 0xe8, 0x6c, 0xe7, 0xb8, 0xe1,
+}
+
+var CommunicationStayPrimaryPortrait = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x05, 0x6a, 0x00,
+ 0x6c, 0x54, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0xe9, 0xc8, 0xb0,
+ 0x80, 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05,
+ 0x7c, 0xe2, 0x94, 0x9c, 0xe6, 0x6c, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe1,
+}
+
+var CommunicationStopScreenShare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x71, 0x92, 0x0d, 0x8c,
+ 0x20, 0x88, 0x88, 0xe6, 0xb0, 0xe9, 0x78, 0xe7, 0x71, 0x7a, 0xe3, 0x8d, 0x81, 0x78, 0x20, 0x05,
+ 0x80, 0x58, 0xb0, 0x80, 0xc9, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x71, 0x76, 0x00, 0xe9,
+ 0x80, 0x81, 0x7a, 0xb0, 0x5d, 0x80, 0xed, 0x7f, 0xb9, 0x80, 0xdd, 0x7f, 0x19, 0x81, 0xcd, 0x7f,
+ 0xe9, 0xbd, 0x7b, 0x22, 0x90, 0x75, 0x87, 0xd9, 0x7c, 0xf1, 0x82, 0x15, 0x8b, 0x15, 0x8b, 0xb0,
+ 0x39, 0x81, 0x59, 0x7f, 0x11, 0x82, 0x05, 0x7e, 0x11, 0x82, 0x85, 0x7c, 0xe2, 0xc9, 0x6c, 0x75,
+ 0x6b, 0x01, 0x39, 0x6a, 0x5c, 0x4d, 0x6d, 0x15, 0x71, 0xa0, 0x81, 0x6c, 0xcd, 0x71, 0x58, 0xdd,
+ 0x72, 0x58, 0x0d, 0x74, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xc9, 0x81, 0x88, 0x88, 0x88, 0xe6,
+ 0x50, 0xe9, 0x88, 0xe7, 0x45, 0xa4, 0x21, 0x6d, 0x85, 0x6d, 0x85, 0x8d, 0x82, 0x75, 0x7d, 0x00,
+ 0xc9, 0x6c, 0x75, 0x6b, 0xe2, 0x6c, 0x0d, 0x86, 0xb0, 0xa1, 0x80, 0x0d, 0x7d, 0xd9, 0x81, 0x19,
+ 0x7a, 0x25, 0x84, 0xe1, 0x77, 0x20, 0x31, 0x83, 0x31, 0x83, 0xb0, 0xed, 0x7c, 0xc5, 0x80, 0x99,
+ 0x7a, 0x5d, 0x82, 0xb1, 0x78, 0xf1, 0x84, 0xe1,
+}
+
+var CommunicationSwapCalls = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0x20, 0x70,
+ 0x90, 0xe7, 0x8c, 0xe9, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x90, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0x99, 0x7b, 0x69, 0x7c, 0x70, 0x70, 0x70, 0x90,
+ 0x70, 0x99, 0x83, 0x70, 0x90, 0xe9, 0x9c, 0xe6, 0x58, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe7, 0x74,
+ 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x90, 0x88, 0xcd, 0x81, 0x88,
+ 0x88, 0xe9, 0x9c, 0xb0, 0x80, 0x69, 0x84, 0x99, 0x83, 0x90, 0x90, 0x90, 0x90, 0x90, 0x69, 0x7c,
+ 0x90, 0x70, 0xe8, 0x70, 0xe7, 0x8c, 0x20, 0x70, 0x70, 0xe1,
+}
+
+var CommunicationTextSMS = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x7c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var CommunicationVoicemail = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9a, 0x68, 0xb1, 0xed,
+ 0x79, 0x80, 0x6a, 0xed, 0x84, 0x6a, 0x96, 0x80, 0xa9, 0x82, 0xf1, 0x80, 0x19, 0x85, 0x85, 0x82,
+ 0x8e, 0xe7, 0xf9, 0x76, 0xb1, 0x91, 0x81, 0x19, 0x7e, 0x85, 0x82, 0xa9, 0x7b, 0x85, 0x82, 0x72,
+ 0x80, 0xed, 0x79, 0x15, 0x7b, 0x6a, 0x6a, 0x6a, 0x80, 0x50, 0xed, 0x78, 0x50, 0x7e, 0x90, 0xed,
+ 0x84, 0x96, 0x96, 0x96, 0xe7, 0xb4, 0xb0, 0x15, 0x86, 0x80, 0x96, 0x15, 0x7b, 0x96, 0x6a, 0x90,
+ 0x15, 0x7b, 0x6a, 0x6a, 0x6a, 0xe2, 0x66, 0x8c, 0xb0, 0x21, 0x7c, 0x80, 0x72, 0xe1, 0x7c, 0x72,
+ 0x72, 0x92, 0x21, 0x83, 0x72, 0x8e, 0x72, 0x8e, 0x21, 0x83, 0x8e, 0x8e, 0xe1, 0x7c, 0x8e, 0x72,
+ 0x8e, 0xe3, 0xb4, 0x80, 0xb0, 0x21, 0x7c, 0x80, 0x72, 0xe1, 0x7c, 0x72, 0x72, 0x92, 0x21, 0x83,
+ 0x72, 0x8e, 0x72, 0x8e, 0x21, 0x83, 0x8e, 0x8e, 0xe1, 0x7c, 0x8e, 0x72, 0x8e, 0xe1,
+}
+
+var CommunicationVPNKey = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x4d, 0x81, 0x78, 0xb1,
+ 0x59, 0x7e, 0x59, 0x7b, 0xed, 0x79, 0x70, 0xb5, 0x74, 0x70, 0x61, 0x79, 0x80, 0x68, 0x61, 0x85,
+ 0x68, 0x98, 0x90, 0x61, 0x85, 0x98, 0x98, 0x98, 0xb0, 0x39, 0x85, 0x80, 0xa9, 0x89, 0xa9, 0x7c,
+ 0x4d, 0x8b, 0x70, 0xe6, 0x94, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x70, 0xe6,
+ 0x4d, 0x81, 0xe2, 0x6c, 0x88, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd,
+ 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe1,
+}
+
+var ContentAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x84, 0xe6, 0x84,
+ 0xe9, 0x98, 0xe7, 0x78, 0xe8, 0x84, 0xe6, 0x64, 0xe9, 0x78, 0xe7, 0x98, 0xe8, 0x64, 0xe7, 0x88,
+ 0xe9, 0x98, 0xe7, 0x98, 0xe9, 0x88, 0xe1,
+}
+
+var ContentAddBox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xa8, 0xe7, 0x70, 0xe9,
+ 0x90, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x88, 0xe9,
+ 0x90, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var ContentAddCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x94, 0xac, 0xe7, 0x70, 0xe9, 0x90, 0xe7,
+ 0x78, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7,
+ 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var ContentAddCircleOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe2, 0x80, 0x58, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74,
+ 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b,
+ 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80,
+ 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0,
+ 0xe1,
+}
+
+var ContentArchive = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x91, 0x75, 0x72,
+ 0x20, 0x3d, 0x7d, 0xa5, 0x7c, 0xa0, 0xc5, 0x8d, 0x71, 0x6e, 0xf1, 0x8c, 0x5c, 0x98, 0x5c, 0xe6,
+ 0x68, 0xb0, 0x11, 0x7f, 0x80, 0x3d, 0x7e, 0x71, 0x80, 0xb1, 0x7d, 0x19, 0x81, 0x20, 0x3d, 0x7d,
+ 0x5d, 0x83, 0xa0, 0x59, 0x6e, 0x29, 0x73, 0x5c, 0x09, 0x74, 0x5c, 0x6a, 0xe9, 0xb2, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x6a, 0xb0, 0x80, 0x09, 0x7f, 0xa9, 0x7f, 0x29, 0x7e, 0x19, 0x7f, 0x75, 0x7d,
+ 0xe2, 0x80, 0x96, 0x00, 0x6a, 0x80, 0xe7, 0x8e, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x8e,
+ 0x00, 0x80, 0x96, 0xe2, 0x41, 0x72, 0x64, 0x20, 0xa1, 0x81, 0x7c, 0xe7, 0xb0, 0x20, 0xe1, 0x81,
+ 0x84, 0xe7, 0x81, 0x64, 0xe1,
+}
+
+var ContentBackspace = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xa1, 0x7e, 0x80, 0x89, 0x7d, 0xb5, 0x80, 0xd1, 0x7c, 0xc5, 0x81, 0x00, 0x50, 0xfd, 0x7f,
+ 0x20, 0xd1, 0x8a, 0x3d, 0x90, 0xa0, 0x89, 0x73, 0x49, 0x91, 0xa1, 0x74, 0xa4, 0x6c, 0xa4, 0xe7,
+ 0xbc, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x74, 0x2d, 0x99, 0x0b, 0x2d, 0x8b, 0x94, 0x88, 0xd5, 0x82,
+ 0xd5, 0x7c, 0x94, 0x74, 0x2d, 0x87, 0x2d, 0x81, 0x80, 0x74, 0xd5, 0x78, 0xd5, 0x7c, 0x6c, 0x88,
+ 0x2d, 0x7d, 0x2d, 0x8b, 0x6c, 0x9c, 0xd5, 0x78, 0xd5, 0x86, 0x80, 0x9c, 0x2d, 0x87, 0xe1,
+}
+
+var ContentBlock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe2, 0x60, 0x80, 0xb1, 0x80, 0x29, 0x77, 0x29,
+ 0x87, 0x60, 0xa0, 0x60, 0xb5, 0x83, 0x80, 0x19, 0x87, 0x45, 0x81, 0xcd, 0x89, 0x61, 0x83, 0x00,
+ 0x61, 0x73, 0xcd, 0x89, 0xa0, 0x45, 0x71, 0x19, 0x87, 0x60, 0xb5, 0x83, 0x60, 0x80, 0xe3, 0xa0,
+ 0xa0, 0xb0, 0x4d, 0x7c, 0x80, 0xe9, 0x78, 0xbd, 0x7e, 0x35, 0x76, 0xa1, 0x7c, 0x00, 0xa1, 0x8c,
+ 0x35, 0x76, 0xa0, 0xbd, 0x8e, 0xe9, 0x78, 0xa0, 0x4d, 0x7c, 0xa0, 0x80, 0xb0, 0x80, 0xd9, 0x88,
+ 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ContentClear = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0xd5, 0x74, 0x0a,
+ 0x2d, 0x8b, 0x64, 0x80, 0x2d, 0x7d, 0xd5, 0x74, 0x64, 0x64, 0xd5, 0x74, 0x2d, 0x7d, 0x80, 0x64,
+ 0x2d, 0x8b, 0xd5, 0x74, 0x9c, 0x80, 0xd5, 0x82, 0x2d, 0x8b, 0x9c, 0x9c, 0x2d, 0x8b, 0xd5, 0x82,
+ 0x80, 0xe1,
+}
+
+var ContentContentCopy = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x54, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x54, 0x58, 0xcd, 0x6b, 0x58, 0x5c, 0xe9, 0xb8, 0xe7, 0x88, 0xe8, 0x5c, 0xe7,
+ 0xb0, 0xe8, 0x54, 0xe3, 0x8c, 0x90, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xac, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x70, 0xe8, 0x6c, 0xe7, 0xac, 0xe9, 0xb8, 0xe1,
+}
+
+var ContentContentCut = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x49, 0x7b, 0x49, 0x77,
+ 0xb1, 0x75, 0x80, 0x7e, 0xb9, 0x80, 0xe5, 0x7d, 0xb9, 0x80, 0xb9, 0x7c, 0x80, 0x95, 0x7b, 0x6d,
+ 0x7c, 0x70, 0x70, 0x70, 0x91, 0x70, 0x95, 0x83, 0x70, 0x90, 0x95, 0x83, 0x90, 0x90, 0x90, 0xb0,
+ 0x2d, 0x81, 0x80, 0x49, 0x82, 0xbd, 0x7f, 0x49, 0x83, 0x49, 0x7f, 0x00, 0x78, 0x80, 0x20, 0x49,
+ 0x7b, 0xb9, 0x84, 0xb1, 0x7e, 0x8d, 0x7f, 0xe5, 0x7d, 0x49, 0x7f, 0xb9, 0x7c, 0x49, 0x7f, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x91, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0xb0, 0x80, 0xd5, 0x7e, 0xbd, 0x7f, 0xb9, 0x7d, 0x49, 0x7f, 0xb9, 0x7c, 0x00, 0x80,
+ 0x88, 0x20, 0x9c, 0x9c, 0xe7, 0x8c, 0xe9, 0x7c, 0x00, 0x49, 0x7b, 0x49, 0x77, 0xe2, 0x68, 0x70,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88,
+ 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x80, 0xb0, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x98, 0x62, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x8d, 0x7f, 0x7e,
+ 0x7e, 0x92, 0x75, 0x80, 0x7e, 0x82, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e,
+ 0x82, 0xe2, 0x9c, 0x5c, 0x00, 0x84, 0x74, 0x20, 0x88, 0x88, 0x00, 0xa8, 0x60, 0xe8, 0x5c, 0xe1,
+}
+
+var ContentContentPaste = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x58, 0xe7, 0xa1,
+ 0x77, 0xb0, 0x31, 0x7f, 0xb1, 0x7d, 0xfd, 0x7c, 0x78, 0x61, 0x7a, 0x78, 0x90, 0x31, 0x7b, 0xb1,
+ 0x81, 0x61, 0x7a, 0x88, 0xe6, 0x64, 0xa0, 0xcd, 0x6f, 0x58, 0x5c, 0xcd, 0x6d, 0x5c, 0x60, 0xe9,
+ 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe2, 0x80, 0x58, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84,
+ 0x7c, 0x84, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x9c, 0xc8, 0xe6,
+ 0x64, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0xa8, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0xc0, 0xe1,
+}
+
+var ContentCreate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x81, 0x8a, 0xe8,
+ 0xa4, 0xe7, 0x81, 0x87, 0x21, 0x21, 0x96, 0xe1, 0x69, 0x81, 0x78, 0x81, 0x78, 0x00, 0x5c, 0x81,
+ 0x8a, 0xe3, 0x69, 0xa3, 0x99, 0x6b, 0xb0, 0xc9, 0x80, 0x39, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0x80,
+ 0x2d, 0x7d, 0x20, 0x55, 0x7b, 0x55, 0x7b, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f,
+ 0x2d, 0x7d, 0x80, 0x22, 0x59, 0x7c, 0xa9, 0x83, 0x81, 0x87, 0x81, 0x87, 0xa9, 0x83, 0x59, 0x7c,
+ 0xe1,
+}
+
+var ContentDeleteSweep = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x90, 0xe7, 0x90,
+ 0xe9, 0x88, 0xe7, 0x70, 0xe3, 0x80, 0x60, 0xe7, 0x9c, 0xe9, 0x88, 0xe6, 0x8c, 0xe3, 0x80, 0x90,
+ 0xe7, 0x98, 0xe9, 0x88, 0xe6, 0x8c, 0xe2, 0x5c, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xe6,
+ 0x5c, 0xe9, 0xa8, 0xe3, 0xac, 0x4c, 0xe7, 0x74, 0x20, 0x7c, 0x7c, 0xe7, 0x70, 0x20, 0x7c, 0x84,
+ 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0xb0, 0xe1,
+}
+
+var ContentDrafts = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x93, 0x70, 0xb0,
+ 0x80, 0x91, 0x7e, 0x41, 0x7f, 0x51, 0x7d, 0x21, 0x7e, 0x99, 0x7c, 0x01, 0x80, 0x54, 0xe9, 0x6d,
+ 0x99, 0x74, 0xa0, 0xc5, 0x6c, 0x51, 0x75, 0x58, 0x91, 0x76, 0x58, 0x70, 0xe9, 0xa8, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x20, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0x84, 0x01, 0x7d, 0x6f, 0xad, 0x77, 0x80, 0x5c,
+ 0x20, 0x85, 0x90, 0xad, 0x89, 0x00, 0x80, 0x84, 0xe1,
+}
+
+var ContentFilterList = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x98, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x88, 0xe2, 0x5c, 0x68, 0xe9, 0x88, 0xe7, 0xc8, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe3, 0x8c, 0x9c, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0x68, 0xe9, 0x88, 0xe1,
+}
+
+var ContentFlag = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x84, 0x68, 0x00,
+ 0x88, 0x60, 0xe6, 0x64, 0xe9, 0xc4, 0xe7, 0x88, 0xe8, 0x88, 0xe7, 0x35, 0x8b, 0x20, 0xcd, 0x80,
+ 0x88, 0xe7, 0x9c, 0xe8, 0x68, 0xe1,
+}
+
+var ContentFontDownload = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xdd, 0x7b, 0x86, 0xe7,
+ 0x49, 0x88, 0x00, 0x80, 0xf5, 0x77, 0xe2, 0xa0, 0x58, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0xe9, 0x77, 0xc2, 0x20, 0xb9, 0x7d, 0x74, 0xe6, 0x59, 0x7a,
+ 0x20, 0xc5, 0x7d, 0x8c, 0xe6, 0xed, 0x73, 0x20, 0x39, 0x8a, 0x4c, 0xe7, 0xb9, 0x83, 0x20, 0x39,
+ 0x8a, 0xb4, 0xe7, 0xd1, 0x7b, 0xe1,
+}
+
+var ContentForward = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0xe8, 0x60,
+ 0x21, 0xa0, 0xa0, 0x60, 0xa0, 0xe9, 0x70, 0xe6, 0x60, 0xe8, 0x70, 0xe1,
+}
+
+var ContentGesture = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x31, 0x71, 0xc5, 0x75,
+ 0xb8, 0x69, 0x81, 0x91, 0x7e, 0xcd, 0x82, 0x4d, 0x7d, 0x71, 0x83, 0x8d, 0x7d, 0xfd, 0x80, 0x69,
+ 0x80, 0xfd, 0x7f, 0x15, 0x82, 0x69, 0x7f, 0x0d, 0x83, 0x81, 0x7f, 0xd9, 0x80, 0x49, 0x7a, 0xc5,
+ 0x87, 0x49, 0x7a, 0xa1, 0x8c, 0x80, 0x91, 0x82, 0xf5, 0x80, 0xb1, 0x84, 0xb1, 0x82, 0xf5, 0x85,
+ 0x81, 0x81, 0x1d, 0x81, 0x79, 0x83, 0x75, 0x81, 0x49, 0x85, 0xed, 0x80, 0x25, 0x82, 0x61, 0x7f,
+ 0xe9, 0x83, 0x35, 0x7d, 0x21, 0x86, 0x79, 0x7a, 0x6d, 0x82, 0x05, 0x7d, 0xa9, 0x85, 0x21, 0x79,
+ 0x29, 0x88, 0x21, 0x79, 0x45, 0x83, 0x80, 0x4d, 0x83, 0x05, 0x82, 0x85, 0x83, 0x99, 0x83, 0x6d,
+ 0x78, 0x4d, 0x81, 0x3d, 0x75, 0x59, 0x87, 0x3d, 0x75, 0xc5, 0x8a, 0x80, 0x31, 0x80, 0xa4, 0xb5,
+ 0x83, 0xa4, 0xb0, 0x41, 0x83, 0x80, 0x99, 0x88, 0x59, 0x7d, 0x61, 0x89, 0xcd, 0x73, 0xe6, 0xa4,
+ 0xe9, 0x76, 0xe7, 0x11, 0x7b, 0xb9, 0xb5, 0x7f, 0xb5, 0x7c, 0xd1, 0x7d, 0x9d, 0x77, 0xf1, 0x77,
+ 0x9d, 0x77, 0x81, 0x7b, 0x80, 0xa1, 0x77, 0xd1, 0x83, 0x21, 0x76, 0xb1, 0x85, 0xd9, 0x7e, 0x75,
+ 0x81, 0xe5, 0x7b, 0xf5, 0x84, 0x71, 0x7b, 0x75, 0x85, 0x7d, 0x7f, 0x99, 0x80, 0xa9, 0x7e, 0xb1,
+ 0x81, 0xc5, 0x7d, 0xb1, 0x81, 0x1d, 0x7f, 0x80, 0x91, 0x7e, 0x55, 0x7e, 0x45, 0x7f, 0x29, 0x7c,
+ 0xb5, 0x80, 0xd1, 0x7d, 0xcd, 0x82, 0x49, 0x7a, 0xb5, 0x83, 0xf9, 0x78, 0x91, 0x81, 0xb9, 0x7d,
+ 0x99, 0x82, 0x29, 0x7c, 0x99, 0x82, 0x71, 0x79, 0x80, 0x99, 0x7b, 0xb9, 0x7c, 0x39, 0x7a, 0xfd,
+ 0x7a, 0x39, 0x7a, 0x5d, 0x7d, 0x80, 0x11, 0x7b, 0x84, 0x8d, 0x7a, 0x85, 0x82, 0x4d, 0x7f, 0xb9,
+ 0x80, 0xb1, 0x7e, 0x51, 0x81, 0x41, 0x7e, 0xd9, 0x81, 0x20, 0x85, 0x83, 0x69, 0x83, 0xe3, 0x95,
+ 0x92, 0x59, 0x97, 0xb2, 0x61, 0x7f, 0x80, 0x89, 0x7e, 0x7d, 0x7f, 0x89, 0x7e, 0x8d, 0x7e, 0x80,
+ 0xcd, 0x7e, 0x75, 0x81, 0x99, 0x7b, 0xc1, 0x85, 0x79, 0x7a, 0x61, 0x7f, 0x65, 0x85, 0x21, 0x7d,
+ 0xfd, 0x86, 0xb9, 0x7b, 0xfd, 0x86, 0xe1,
+}
+
+var ContentInbox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0xfd,
+ 0x71, 0xb0, 0xcd, 0x7d, 0x80, 0x0d, 0x7c, 0xcd, 0x81, 0x0d, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0,
+ 0x80, 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0x80, 0xb0, 0xe7, 0x70, 0xb0, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0x90, 0x74, 0x51,
+ 0x7d, 0x74, 0x74, 0xe6, 0xfd, 0x71, 0xe8, 0x64, 0xe6, 0x9c, 0xe9, 0xa8, 0xe1,
+}
+
+var ContentLink = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x6f, 0x80, 0xb0,
+ 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79, 0xe7, 0x90, 0xe8, 0x6c, 0xe7,
+ 0x70, 0xa0, 0x7d, 0x70, 0x6c, 0x58, 0x7d, 0x7a, 0x58, 0x80, 0x90, 0x7d, 0x84, 0x94, 0x94, 0x94,
+ 0xe7, 0x90, 0xe9, 0x35, 0x7c, 0xe7, 0x70, 0xb0, 0x95, 0x7c, 0x80, 0xcd, 0x79, 0x39, 0x7d, 0xcd,
+ 0x79, 0xcd, 0x79, 0xe3, 0x35, 0x88, 0x84, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x70, 0xe9, 0x88, 0xe3,
+ 0xa4, 0x68, 0xe7, 0x70, 0xe9, 0xcd, 0x83, 0xe7, 0x90, 0xb0, 0x6d, 0x83, 0x80, 0x35, 0x86, 0xc9,
+ 0x82, 0x35, 0x86, 0x35, 0x86, 0x90, 0x39, 0x7d, 0x35, 0x86, 0xcd, 0x79, 0x35, 0x86, 0xe7, 0x70,
+ 0xe8, 0x94, 0xe7, 0x90, 0xb0, 0x85, 0x85, 0x80, 0x94, 0x85, 0x7b, 0x94, 0x6c, 0x90, 0x85, 0x7b,
+ 0x6c, 0x6c, 0x6c, 0xe1,
+}
+
+var ContentLowPriority = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x64, 0xe7, 0xa0,
+ 0xe9, 0x88, 0xe6, 0x88, 0xe3, 0x80, 0x96, 0xe7, 0xa0, 0xe9, 0x88, 0xe6, 0x88, 0xe3, 0x80, 0x96,
+ 0xe7, 0xa0, 0xe9, 0x88, 0xe6, 0x88, 0xe2, 0x58, 0x7e, 0xb0, 0x80, 0x2d, 0x87, 0xd5, 0x85, 0x9a,
+ 0x9a, 0x9a, 0xe7, 0x82, 0xe9, 0x88, 0x21, 0x8c, 0x74, 0x74, 0x74, 0xe9, 0x88, 0xe7, 0x7e, 0xb0,
+ 0x0d, 0x7b, 0x80, 0x6e, 0xf5, 0x7b, 0x6e, 0x6e, 0x90, 0x0d, 0x84, 0x6e, 0x92, 0x6e, 0xe7, 0x8e,
+ 0xe9, 0x78, 0xe7, 0x72, 0xa0, 0xd5, 0x71, 0x64, 0x58, 0xd5, 0x77, 0x58, 0x7e, 0xe1,
+}
+
+var ContentMail = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x90,
+ 0x01, 0x80, 0x84, 0x60, 0x70, 0xe9, 0x78, 0x21, 0xa0, 0x94, 0xa0, 0x6c, 0xe9, 0x88, 0xe1,
+}
+
+var ContentMarkUnread = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x90,
+ 0x01, 0x80, 0x84, 0x60, 0x70, 0xe9, 0x78, 0x21, 0xa0, 0x94, 0xa0, 0x6c, 0xe9, 0x88, 0xe1,
+}
+
+var ContentMoveToInbox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0xfd,
+ 0x71, 0xb0, 0xcd, 0x7d, 0x80, 0x0d, 0x7c, 0xcd, 0x81, 0x0d, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0,
+ 0x80, 0x35, 0x82, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0x80, 0xb0, 0xe7, 0x70, 0xb0, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0x90, 0x74, 0x51,
+ 0x7d, 0x74, 0x74, 0xe6, 0xfd, 0x71, 0xe8, 0x64, 0xe6, 0x9c, 0xe9, 0xa8, 0xe3, 0x74, 0x6c, 0xe7,
+ 0x78, 0xe9, 0x74, 0xe7, 0x70, 0xe9, 0x8c, 0xe7, 0x78, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe1,
+}
+
+var ContentNextWeek = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x6c, 0xe7, 0x70,
+ 0xe9, 0x78, 0xb0, 0x80, 0xe9, 0x7e, 0x91, 0x7f, 0xe9, 0x7d, 0xd1, 0x7e, 0x31, 0x7d, 0xa0, 0x19,
+ 0x86, 0x71, 0x6e, 0x19, 0x85, 0x5c, 0x88, 0x5c, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe9, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88,
+ 0xe9, 0xac, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe3, 0x58, 0x78, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0x78, 0xe3, 0x84, 0xb6, 0x25,
+ 0x7c, 0x7c, 0x8c, 0x74, 0x74, 0x74, 0x84, 0x7c, 0x90, 0x90, 0x70, 0x90, 0xe1,
+}
+
+var ContentRedo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x8c, 0x35, 0x7d,
+ 0xa0, 0x1d, 0x89, 0xf9, 0x79, 0x4d, 0x84, 0x70, 0x7e, 0x70, 0xb0, 0xb5, 0x76, 0x80, 0xd5, 0x6e,
+ 0x11, 0x86, 0x15, 0x6c, 0x71, 0x8e, 0x00, 0xd1, 0x6f, 0x90, 0xb1, 0x19, 0x82, 0x9d, 0x79, 0x19,
+ 0x88, 0x6a, 0x31, 0x8f, 0x6a, 0xe9, 0x83, 0x80, 0x75, 0x87, 0x71, 0x81, 0x3d, 0x8a, 0xc5, 0x83,
+ 0x00, 0x84, 0x90, 0xe7, 0xa4, 0xe8, 0x6c, 0x20, 0xcd, 0x78, 0x35, 0x87, 0xe1,
+}
+
+var ContentRemove = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x84, 0xe6, 0x64,
+ 0xe9, 0x78, 0xe7, 0xb8, 0xe9, 0x88, 0xe1,
+}
+
+var ContentRemoveCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x94, 0xac, 0xe6, 0x6c, 0xe9, 0x78, 0xe7,
+ 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var ContentRemoveCircleOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x7c, 0xe9, 0x88,
+ 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x6c, 0xe2, 0x80, 0x58, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74,
+ 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b,
+ 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80,
+ 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0,
+ 0xe1,
+}
+
+var ContentReply = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x74, 0xe9, 0x70,
+ 0x00, 0x5c, 0x80, 0x20, 0x9c, 0x9c, 0xe9, 0xcd, 0x77, 0xb1, 0x94, 0x80, 0xa2, 0x35, 0x83, 0xac,
+ 0x35, 0x8a, 0x7c, 0x6c, 0x70, 0x58, 0x54, 0x54, 0xe1,
+}
+
+var ContentReplyAll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x70, 0xe9, 0x74,
+ 0x00, 0x50, 0x80, 0x20, 0x9c, 0x9c, 0xe9, 0x74, 0x21, 0x70, 0x70, 0x90, 0x70, 0xe3, 0x98, 0x84,
+ 0xe9, 0x70, 0x00, 0x68, 0x80, 0x20, 0x9c, 0x9c, 0xe9, 0xcd, 0x77, 0xb1, 0x94, 0x80, 0xa2, 0x35,
+ 0x83, 0xac, 0x35, 0x8a, 0x7c, 0x6c, 0x70, 0x58, 0x54, 0x54, 0xe1,
+}
+
+var ContentReport = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x75, 0x87, 0x5c, 0xe6,
+ 0x8d, 0x78, 0x00, 0x5c, 0x8d, 0x78, 0xe9, 0xe9, 0x8e, 0x00, 0x8d, 0x78, 0xa4, 0xe7, 0xe9, 0x8e,
+ 0x00, 0xa4, 0x75, 0x87, 0xe8, 0x8d, 0x78, 0x00, 0x75, 0x87, 0x5c, 0xe2, 0x80, 0x99, 0x8a, 0xb3,
+ 0x91, 0x7e, 0x80, 0x69, 0x7d, 0xd9, 0x7e, 0x69, 0x7d, 0x69, 0x7d, 0x80, 0x91, 0x7e, 0x2d, 0x81,
+ 0x69, 0x7d, 0x99, 0x82, 0x69, 0x7d, 0x71, 0x81, 0x80, 0x99, 0x82, 0x29, 0x81, 0x99, 0x82, 0x99,
+ 0x82, 0x80, 0x71, 0x81, 0xd5, 0x7e, 0x99, 0x82, 0x69, 0x7d, 0x99, 0x82, 0xe3, 0x84, 0x69, 0x77,
+ 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0x98, 0xe1,
+}
+
+var ContentSave = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x6c, 0x20, 0x70, 0x70, 0xe2, 0x80, 0x9c, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74,
+ 0x92, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x8c,
+ 0xe3, 0x8c, 0x58, 0xe6, 0x64, 0xe9, 0x70, 0xe7, 0xa8, 0xe9, 0x90, 0xe1,
+}
+
+var ContentSelectAll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe2, 0x5c, 0x74, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe2, 0x84, 0x5c, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x98, 0x80, 0xe9, 0x88, 0xe7, 0x88, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x64, 0xa4, 0xe9, 0x78, 0xe6, 0x5c, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x78, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe2, 0x74, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x88, 0xc8,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xa0, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x50, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80,
+ 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x90, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x40, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe2,
+ 0x6c, 0x94, 0xe7, 0xa8, 0xe8, 0x6c, 0xe6, 0x6c, 0xe9, 0xa8, 0xe3, 0x88, 0x60, 0xe7, 0x98, 0xe9,
+ 0x98, 0xe6, 0x74, 0xe8, 0x74, 0xe1,
+}
+
+var ContentSend = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x6c, 0xa4, 0x02,
+ 0xac, 0x80, 0x05, 0x6c, 0x5c, 0x58, 0x78, 0x21, 0xbc, 0x88, 0x44, 0x88, 0xe1,
+}
+
+var ContentSort = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x98, 0xe7, 0x98,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x50, 0xe9, 0x88, 0xe7, 0xc8, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe3, 0x80, 0x9c, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe1,
+}
+
+var ContentTextFormat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x94, 0xe9, 0x88,
+ 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x64, 0xe3, 0x92, 0x99, 0x77, 0xe7, 0x94, 0x20, 0xcd, 0x81, 0x69,
+ 0x84, 0xe6, 0x96, 0x00, 0x81, 0x81, 0x60, 0xe7, 0x7a, 0x00, 0x6a, 0x8c, 0xe7, 0x35, 0x84, 0x20,
+ 0xcd, 0x81, 0x99, 0x7b, 0xe3, 0x8a, 0x5d, 0x72, 0x00, 0xbd, 0x83, 0x7c, 0xe7, 0x85, 0x78, 0x00,
+ 0x80, 0xf5, 0x73, 0xe1,
+}
+
+var ContentUnarchive = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x91, 0x75, 0x72,
+ 0x20, 0x3d, 0x7d, 0xa5, 0x7c, 0xa0, 0xc5, 0x8d, 0x71, 0x6e, 0xf1, 0x8c, 0x5c, 0x98, 0x5c, 0xe6,
+ 0x68, 0xb0, 0x11, 0x7f, 0x80, 0x3d, 0x7e, 0x71, 0x80, 0xb1, 0x7d, 0x19, 0x81, 0x20, 0x3d, 0x7d,
+ 0x5d, 0x83, 0xa0, 0x59, 0x6e, 0x29, 0x73, 0x5c, 0x09, 0x74, 0x5c, 0x6a, 0xe9, 0xb2, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x6a, 0xb0, 0x80, 0x09, 0x7f, 0xa9, 0x7f, 0x29, 0x7e, 0x19, 0x7f, 0x75, 0x7d,
+ 0xe2, 0x80, 0x76, 0x20, 0x96, 0x96, 0xe7, 0x72, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x72,
+ 0x20, 0x96, 0x6a, 0xe3, 0x41, 0x72, 0x6e, 0x20, 0xa1, 0x81, 0x7c, 0xe7, 0xb0, 0x20, 0xe1, 0x81,
+ 0x84, 0xe7, 0x81, 0x64, 0xe1,
+}
+
+var ContentUndo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x82, 0x70, 0xb0, 0xb5,
+ 0x7a, 0x80, 0xe5, 0x75, 0xf9, 0x81, 0x35, 0x72, 0x35, 0x85, 0x00, 0x58, 0x6c, 0xe9, 0xa4, 0xe7,
+ 0xa4, 0x20, 0xc5, 0x78, 0xc5, 0x78, 0xa0, 0x8d, 0x79, 0x71, 0x7e, 0x19, 0x7d, 0x7a, 0x82, 0x7a,
+ 0xb0, 0x19, 0x87, 0x80, 0x19, 0x8d, 0x9d, 0x84, 0x31, 0x8f, 0x96, 0x20, 0xbd, 0x84, 0x71, 0x7e,
+ 0xa0, 0x2d, 0x92, 0x11, 0x7e, 0x4d, 0x8a, 0x70, 0x82, 0x70, 0xe1,
+}
+
+var ContentWeekend = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x78, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c, 0xe6, 0x64, 0xe9, 0x74, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x90, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x94, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x80, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x74, 0x6c,
+ 0xe6, 0x68, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x51, 0x84, 0xb0, 0x51,
+ 0x82, 0xd5, 0x80, 0x88, 0x09, 0x83, 0x88, 0xa1, 0x85, 0xe8, 0x88, 0xe7, 0xb0, 0xe9, 0xf1, 0x7b,
+ 0xb0, 0x80, 0x69, 0x7d, 0xb1, 0x81, 0x35, 0x7b, 0x88, 0x61, 0x7a, 0xe8, 0x6c, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var DeviceAccessAlarm = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x71, 0x73, 0x22,
+ 0xd1, 0x76, 0x4d, 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31, 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73,
+ 0xe2, 0xc5, 0x77, 0xc9, 0x6e, 0x20, 0x71, 0x7d, 0xf1, 0x7c, 0x00, 0x58, 0x71, 0x73, 0x21, 0x91,
+ 0x82, 0x11, 0x83, 0x31, 0x89, 0x4d, 0x78, 0xe2, 0x82, 0x70, 0xe7, 0x7a, 0xe9, 0x98, 0x20, 0x7d,
+ 0x89, 0xb5, 0x85, 0x00, 0x92, 0x3d, 0x87, 0x20, 0x70, 0x45, 0x7b, 0xe8, 0x70, 0xe3, 0xfd, 0x7e,
+ 0x70, 0xa0, 0x0d, 0x76, 0x60, 0x5c, 0x11, 0x78, 0x5c, 0x84, 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91,
+ 0xa4, 0x81, 0xa4, 0xf1, 0x8b, 0xa4, 0x84, 0xf1, 0x89, 0x60, 0xfd, 0x7f, 0x60, 0xe2, 0x80, 0xa0,
+ 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c,
+ 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var DeviceAccessAlarms = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x81, 0x73, 0x23,
+ 0xcd, 0x76, 0x4d, 0x78, 0x69, 0x7d, 0x19, 0x83, 0x35, 0x89, 0xb5, 0x87, 0x99, 0x82, 0xe9, 0x7c,
+ 0xe2, 0xb5, 0x77, 0xcd, 0x6e, 0x20, 0x69, 0x7d, 0xe9, 0x7c, 0x00, 0x58, 0x81, 0x73, 0x21, 0x99,
+ 0x82, 0x19, 0x83, 0x19, 0x89, 0x35, 0x78, 0xe2, 0x82, 0x70, 0xe7, 0x7a, 0xe9, 0x98, 0x22, 0x81,
+ 0x89, 0xb5, 0x85, 0x81, 0x81, 0x81, 0x7d, 0x70, 0x4d, 0x7b, 0xe8, 0x70, 0xe3, 0x7e, 0x70, 0xa0,
+ 0x6c, 0x60, 0x5c, 0x19, 0x78, 0x5c, 0x84, 0x90, 0x90, 0xa4, 0xa4, 0xa4, 0xb0, 0xe9, 0x89, 0x80,
+ 0xa4, 0xe9, 0x77, 0xa4, 0x5c, 0x80, 0xe9, 0x89, 0x60, 0x80, 0x60, 0xe3, 0x80, 0xc0, 0xb0, 0x4d,
+ 0x78, 0x80, 0x64, 0xb5, 0x79, 0x64, 0x64, 0x91, 0x4d, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x4d, 0x86,
+ 0x9c, 0x9c, 0xb0, 0x80, 0xcd, 0x87, 0xb5, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var DeviceAccessTime = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2,
+ 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80,
+ 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x82, 0x4c, 0xe7,
+ 0x7a, 0xe9, 0x98, 0x20, 0x7d, 0x8a, 0x4d, 0x86, 0x00, 0x94, 0xd9, 0x85, 0x20, 0x6e, 0xa9, 0x7a,
+ 0xe1,
+}
+
+var DeviceAddAlarm = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xc5, 0x77, 0xc9, 0x6e,
+ 0x20, 0x71, 0x7d, 0xf1, 0x7c, 0x00, 0x58, 0x71, 0x73, 0x21, 0x91, 0x82, 0x11, 0x83, 0x31, 0x89,
+ 0x4d, 0x78, 0xe2, 0xa8, 0x71, 0x73, 0x22, 0xd1, 0x76, 0x4d, 0x78, 0x71, 0x7d, 0x11, 0x83, 0x31,
+ 0x89, 0xb5, 0x87, 0x00, 0xa8, 0x71, 0x73, 0xe2, 0xfd, 0x7f, 0x60, 0xa0, 0x0d, 0x76, 0x60, 0x5c,
+ 0x11, 0x78, 0x5c, 0x84, 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0x81, 0xa4, 0xf1, 0x8b, 0xa4,
+ 0x84, 0xf1, 0x89, 0x60, 0xfd, 0x7f, 0x60, 0xe2, 0x80, 0xa0, 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd,
+ 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79,
+ 0x9c, 0x64, 0x9c, 0xe3, 0x84, 0x54, 0xe7, 0x78, 0xe9, 0x8c, 0xe7, 0x74, 0xe9, 0x88, 0xe7, 0x8c,
+ 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0x8c, 0xe9, 0x78, 0xe7, 0x74, 0xe9, 0x74, 0xe1,
+}
+
+var DeviceAirplaneModeActive = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x90, 0xe9, 0x78,
+ 0x00, 0x84, 0x74, 0xe8, 0x5e, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a,
+ 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x96, 0x00, 0x58, 0x88, 0xe9, 0x88, 0x20, 0xa0, 0x76, 0xe9, 0x96,
+ 0x20, 0x78, 0x86, 0xe9, 0x86, 0x21, 0x8e, 0x7c, 0x8e, 0x84, 0xe9, 0x7a, 0x20, 0x78, 0x7a, 0xe8,
+ 0x86, 0x20, 0xa0, 0x8a, 0xe1,
+}
+
+var DeviceAirplaneModeInactive = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x74, 0xe8, 0x5e,
+ 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe9,
+ 0x5d, 0x87, 0x20, 0xa9, 0x8f, 0xa9, 0x8f, 0x00, 0xa4, 0x90, 0xe9, 0x78, 0x00, 0x84, 0x74, 0xe2,
+ 0x5c, 0x8d, 0x72, 0x20, 0xf9, 0x89, 0xf9, 0x89, 0x00, 0x58, 0x88, 0xe9, 0x88, 0x20, 0xa0, 0x76,
+ 0xe9, 0x96, 0x20, 0x78, 0x86, 0xe9, 0x86, 0x21, 0x8e, 0x7c, 0x8e, 0x84, 0xe9, 0x7a, 0x20, 0x78,
+ 0x7a, 0xe9, 0x8d, 0x78, 0x03, 0x75, 0x8d, 0xa4, 0xa0, 0x75, 0x8f, 0x8d, 0x70, 0x60, 0x5c, 0x8d,
+ 0x72, 0xe1,
+}
+
+var DeviceBattery20 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x94, 0xe9, 0x55,
+ 0x87, 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79,
+ 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x94, 0xe6, 0x6c, 0xe1, 0xa1,
+ 0x4c, 0x7f, 0x80, 0xc1, 0x94, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87,
+ 0x60, 0xe6, 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60,
+ 0x6c, 0x31, 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x94, 0xe7, 0xa8, 0xe8, 0xad, 0x72, 0xe1,
+}
+
+var DeviceBattery30 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x94, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8,
+ 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c,
+ 0xad, 0x72, 0xe8, 0x8c, 0xe7, 0xa8, 0xe8, 0xad, 0x72, 0xe1, 0xc0, 0x6c, 0x8c, 0xe9, 0x55, 0x8b,
+ 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81,
+ 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x8c, 0xe6, 0x6c, 0xe1,
+}
+
+var DeviceBattery50 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x94, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8,
+ 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c,
+ 0xad, 0x72, 0xe8, 0x84, 0xe7, 0xa8, 0xe8, 0xad, 0x72, 0xe1, 0xc0, 0x6c, 0x84, 0xe9, 0x55, 0x8f,
+ 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81,
+ 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x84, 0xe6, 0x6c, 0xe1,
+}
+
+var DeviceBattery60 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x94, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8,
+ 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c,
+ 0xad, 0x72, 0xe8, 0x7c, 0xe7, 0xa8, 0xe8, 0xad, 0x72, 0xe1, 0xc0, 0x6c, 0x7c, 0xe9, 0x55, 0x93,
+ 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81,
+ 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x7c, 0xe6, 0x6c, 0xe1,
+}
+
+var DeviceBattery80 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x94, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8,
+ 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c,
+ 0xad, 0x72, 0xe8, 0x74, 0xe7, 0xa8, 0xe9, 0xad, 0x78, 0xe1, 0xc0, 0x6c, 0x74, 0xe9, 0x55, 0x97,
+ 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81,
+ 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x74, 0xe6, 0x6c, 0xe1,
+}
+
+var DeviceBattery90 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x94, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8,
+ 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c,
+ 0xad, 0x72, 0xe8, 0x70, 0xe7, 0xa8, 0xe9, 0xad, 0x7a, 0xe1, 0xc0, 0x6c, 0x70, 0xe9, 0x55, 0x99,
+ 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81,
+ 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x70, 0xe6, 0x6c, 0xe1,
+}
+
+var DeviceBatteryAlert = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x55, 0x87, 0x60, 0xe6,
+ 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31,
+ 0x71, 0x6c, 0xad, 0x72, 0xe9, 0xad, 0x9e, 0xb0, 0x80, 0x79, 0x81, 0x31, 0x81, 0xad, 0x82, 0xad,
+ 0x82, 0xad, 0x82, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82,
+ 0x55, 0x7d, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe2,
+ 0x84, 0x98, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe8,
+ 0x74, 0xe7, 0x88, 0xe9, 0x94, 0xe1,
+}
+
+var DeviceBatteryCharging20 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0xa0, 0xe9, 0x74,
+ 0xe7, 0x70, 0xe9, 0x55, 0x87, 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7,
+ 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x94,
+ 0xe7, 0x35, 0x77, 0x00, 0x7c, 0xa0, 0xe1, 0xa1, 0x4c, 0x7f, 0x80, 0xc1, 0x55, 0x87, 0x60, 0xe6,
+ 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31,
+ 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x94, 0xe7, 0x90, 0xe9, 0x76, 0xe7, 0x78, 0x20, 0x90, 0x62, 0xe9,
+ 0x96, 0xe7, 0x88, 0x20, 0x35, 0x7b, 0x92, 0xe6, 0x94, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71,
+ 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe1,
+}
+
+var DeviceBatteryCharging30 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31,
+ 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x8a, 0xe7, 0x88, 0x20, 0x90, 0x62, 0xe9,
+ 0x96, 0xe7, 0x88, 0x20, 0xe1, 0x7d, 0x88, 0xe6, 0x94, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71,
+ 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe1, 0xc0, 0x7c, 0xa0, 0xe8, 0x8a, 0xe7, 0x70, 0xe9, 0x55,
+ 0x8c, 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79,
+ 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x8a, 0xe7, 0xe1, 0x79, 0x00,
+ 0x7c, 0xa0, 0xe1,
+}
+
+var DeviceBatteryCharging50 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xf1, 0x84, 0x86, 0x00,
+ 0x7c, 0xa0, 0xe8, 0x8a, 0xe7, 0x78, 0x20, 0x11, 0x81, 0x7c, 0xe6, 0x6c, 0xe9, 0x55, 0x8e, 0xa0,
+ 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78, 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80,
+ 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d, 0xe8, 0x86, 0xe7, 0xed, 0x7a, 0xe1, 0xa1, 0x4c,
+ 0x7f, 0x80, 0xc1, 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad,
+ 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x86, 0xe7, 0x11, 0x85,
+ 0x00, 0x84, 0x6c, 0xe9, 0x96, 0xe7, 0x88, 0x20, 0xf1, 0x7e, 0x84, 0xe6, 0x94, 0xe8, 0xad, 0x72,
+ 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe1,
+}
+
+var DeviceBatteryCharging60 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31,
+ 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x7c, 0xe7, 0xbd, 0x87, 0x00, 0x84, 0x6c,
+ 0xe9, 0x90, 0xe7, 0x90, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87,
+ 0x60, 0xe1, 0xc0, 0x84, 0x82, 0xe7, 0x88, 0x20, 0x70, 0x9e, 0xe8, 0x8a, 0xe7, 0x78, 0x20, 0xbd,
+ 0x83, 0x72, 0xe6, 0x6c, 0xe9, 0x55, 0x93, 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78,
+ 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d,
+ 0xe8, 0x7c, 0xe7, 0x70, 0xe9, 0x86, 0xe1,
+}
+
+var DeviceBatteryCharging80 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31,
+ 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x74, 0xe7, 0xe1, 0x89, 0x00, 0x84, 0x6c,
+ 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0xad, 0x78, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87,
+ 0x60, 0xe1, 0xc0, 0x84, 0x82, 0xe7, 0x88, 0x20, 0x70, 0x9e, 0xe8, 0x8a, 0xe7, 0x78, 0x20, 0xe1,
+ 0x85, 0x6a, 0xe6, 0x6c, 0xe9, 0x55, 0x97, 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78,
+ 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d,
+ 0xe8, 0x74, 0xe7, 0x70, 0xe9, 0x8e, 0xe1,
+}
+
+var DeviceBatteryCharging90 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x55, 0x87, 0x60, 0xe6, 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31,
+ 0x77, 0x60, 0x6c, 0x31, 0x71, 0x6c, 0xad, 0x72, 0xe8, 0x70, 0xe7, 0xf1, 0x8a, 0x00, 0x84, 0x6c,
+ 0xe9, 0x84, 0xe7, 0x90, 0xe9, 0xad, 0x7a, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87,
+ 0x60, 0xe1, 0xc0, 0x84, 0x82, 0xe7, 0x88, 0x20, 0x70, 0x9e, 0xe8, 0x8a, 0xe7, 0x78, 0x20, 0xf1,
+ 0x86, 0x66, 0xe6, 0x6c, 0xe9, 0x55, 0x99, 0xa0, 0x6c, 0xcd, 0x92, 0x31, 0x77, 0xa8, 0xad, 0x78,
+ 0xa8, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82, 0x55, 0x7d,
+ 0xe8, 0x70, 0xe7, 0x70, 0xe9, 0x92, 0xe1,
+}
+
+var DeviceBatteryChargingFull = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x55, 0x87, 0x60, 0xe6,
+ 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31,
+ 0x71, 0x6c, 0xad, 0x72, 0xe9, 0xad, 0x9e, 0xb0, 0x80, 0x79, 0x81, 0x31, 0x81, 0xad, 0x82, 0xad,
+ 0x82, 0xad, 0x82, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82,
+ 0x55, 0x7d, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe2,
+ 0x7c, 0xa0, 0xe8, 0x8a, 0xe7, 0x78, 0x20, 0x90, 0x62, 0xe9, 0x96, 0xe7, 0x88, 0x20, 0x70, 0x9e,
+ 0xe1,
+}
+
+var DeviceBatteryFull = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x55, 0x87, 0x60, 0xe6,
+ 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31,
+ 0x71, 0x6c, 0xad, 0x72, 0xe9, 0xad, 0x9e, 0xb0, 0x80, 0x79, 0x81, 0x31, 0x81, 0xad, 0x82, 0xad,
+ 0x82, 0xad, 0x82, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82,
+ 0x55, 0x7d, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe1,
+}
+
+var DeviceBatteryStd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x55, 0x87, 0x60, 0xe6,
+ 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31,
+ 0x71, 0x6c, 0xad, 0x72, 0xe9, 0xad, 0x9e, 0xb0, 0x80, 0x79, 0x81, 0x31, 0x81, 0xad, 0x82, 0xad,
+ 0x82, 0xad, 0x82, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82,
+ 0x55, 0x7d, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe1,
+}
+
+var DeviceBatteryUnknown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x55, 0x87, 0x60, 0xe6,
+ 0x88, 0xe8, 0x58, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0xad, 0x7c, 0xa0, 0x31, 0x77, 0x60, 0x6c, 0x31,
+ 0x71, 0x6c, 0xad, 0x72, 0xe9, 0xad, 0x9e, 0xb0, 0x80, 0x79, 0x81, 0x31, 0x81, 0xad, 0x82, 0xad,
+ 0x82, 0xad, 0x82, 0xe7, 0xad, 0x8e, 0xb0, 0x79, 0x81, 0x80, 0xad, 0x82, 0xd1, 0x7e, 0xad, 0x82,
+ 0x55, 0x7d, 0xe8, 0xad, 0x72, 0xa0, 0x94, 0x31, 0x71, 0xd1, 0x88, 0x60, 0x55, 0x87, 0x60, 0xe2,
+ 0xe9, 0x81, 0xe9, 0x8b, 0xe7, 0x35, 0x7c, 0xe9, 0x35, 0x7c, 0xe7, 0xcd, 0x83, 0xe9, 0xcd, 0x83,
+ 0xe3, 0xb5, 0x82, 0x7d, 0x75, 0x90, 0x3d, 0x7f, 0xd9, 0x80, 0xa9, 0x7e, 0x6d, 0x81, 0xb0, 0x09,
+ 0x7f, 0xf9, 0x80, 0x59, 0x7e, 0x4d, 0x82, 0x59, 0x7e, 0x35, 0x83, 0xe7, 0xcd, 0x7c, 0xb0, 0x80,
+ 0x59, 0x7e, 0xed, 0x80, 0xf5, 0x7c, 0xdd, 0x81, 0x05, 0x7c, 0x20, 0xdd, 0x81, 0x1d, 0x7e, 0xb1,
+ 0x8d, 0x80, 0x75, 0x7f, 0xe1, 0x80, 0xb5, 0x7e, 0xe1, 0x80, 0xe1, 0x7d, 0x80, 0x59, 0x7e, 0xa9,
+ 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe7, 0x7a, 0xb0, 0x80, 0xb1, 0x7c,
+ 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x90, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0xb0, 0x80, 0x51, 0x81, 0x79,
+ 0x7f, 0x85, 0x82, 0x99, 0x7e, 0x61, 0x83, 0xe1,
+}
+
+var DeviceBluetooth = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x8b, 0x69, 0x77,
+ 0x00, 0x80, 0x58, 0xe7, 0x7c, 0xe9, 0x2d, 0x8f, 0x05, 0xd5, 0x74, 0x64, 0x64, 0xd5, 0x74, 0x2d,
+ 0x7d, 0x80, 0x64, 0x2d, 0x8b, 0xd5, 0x74, 0x9c, 0x7c, 0xd5, 0x84, 0xe8, 0xa8, 0xe7, 0x84, 0x20,
+ 0x69, 0x8b, 0x99, 0x74, 0x00, 0xd5, 0x82, 0x80, 0x20, 0x95, 0x88, 0x69, 0x77, 0xe2, 0x84, 0xa9,
+ 0x73, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0x00, 0x84, 0x2d, 0x7b, 0xe9, 0x7d, 0x78, 0xe3, 0xc5, 0x83,
+ 0xf1, 0x94, 0x00, 0x84, 0x59, 0x8c, 0xe9, 0x7d, 0x78, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0xe1,
+}
+
+var DeviceBluetoothConnected = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x80, 0x23, 0x78,
+ 0x78, 0x78, 0x88, 0x88, 0x88, 0x88, 0x78, 0xe3, 0x69, 0x95, 0x69, 0x77, 0x00, 0x80, 0x58, 0xe7,
+ 0x7c, 0xe9, 0x2d, 0x8f, 0x05, 0xd5, 0x74, 0x64, 0x64, 0xd5, 0x74, 0x2d, 0x7d, 0x80, 0x64, 0x2d,
+ 0x8b, 0xd5, 0x74, 0x9c, 0x7c, 0xd5, 0x84, 0xe8, 0xa8, 0xe7, 0x84, 0x20, 0x69, 0x8b, 0x99, 0x74,
+ 0x00, 0xd5, 0x82, 0x80, 0x20, 0x95, 0x88, 0x69, 0x77, 0xe2, 0x84, 0xa9, 0x73, 0x20, 0xc5, 0x83,
+ 0xc5, 0x83, 0x00, 0x84, 0x2d, 0x7b, 0xe9, 0x7d, 0x78, 0xe3, 0xc5, 0x83, 0xf1, 0x94, 0x00, 0x84,
+ 0x59, 0x8c, 0xe9, 0x7d, 0x78, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0xe2, 0x9c, 0x78, 0x23, 0x78, 0x88,
+ 0x88, 0x88, 0x88, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var DeviceBluetoothDisabled = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0xd5, 0x73, 0x23,
+ 0xc5, 0x83, 0xc5, 0x83, 0xcd, 0x7c, 0x35, 0x83, 0xd5, 0x82, 0xd5, 0x82, 0x09, 0x86, 0xf9, 0x79,
+ 0x00, 0x80, 0x2d, 0x6c, 0xe7, 0x7c, 0xe9, 0x11, 0x8a, 0x20, 0x88, 0x88, 0xe9, 0x99, 0x79, 0xe2,
+ 0xd5, 0x72, 0x2d, 0x70, 0x00, 0x60, 0x66, 0x20, 0x2d, 0x8d, 0x2d, 0x8d, 0x00, 0x64, 0x59, 0x8b,
+ 0x20, 0xd5, 0x82, 0xd5, 0x82, 0x00, 0x7c, 0x8a, 0xe9, 0x2d, 0x8f, 0xe7, 0x84, 0x21, 0x99, 0x88,
+ 0x69, 0x77, 0x99, 0x84, 0x99, 0x84, 0x01, 0xa0, 0x59, 0x8d, 0xd5, 0x72, 0x2d, 0x70, 0xe2, 0x84,
+ 0x85, 0x8c, 0xe8, 0x8a, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0x00, 0x84, 0x85, 0x8c, 0xe1,
+}
+
+var DeviceBluetoothSearching = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7d, 0x84, 0x05, 0x80,
+ 0x20, 0xa5, 0x84, 0xa5, 0x84, 0xb1, 0x91, 0x80, 0x8d, 0x7e, 0xe1, 0x80, 0xfd, 0x7c, 0xe1, 0x80,
+ 0x59, 0x7b, 0x80, 0x61, 0x7e, 0xb1, 0x7f, 0xd1, 0x7c, 0x25, 0x7f, 0x61, 0x7b, 0x20, 0x59, 0x7b,
+ 0xa9, 0x84, 0xe3, 0x95, 0x8a, 0x69, 0x75, 0x20, 0x79, 0x7d, 0x89, 0x82, 0xb0, 0x41, 0x81, 0x69,
+ 0x82, 0xf9, 0x81, 0x25, 0x85, 0xf9, 0x81, 0x0d, 0x88, 0x90, 0x49, 0x7f, 0xa1, 0x85, 0x09, 0x7e,
+ 0x0d, 0x88, 0x20, 0x69, 0x82, 0x69, 0x82, 0xb1, 0xf1, 0x81, 0xe9, 0x7c, 0x11, 0x83, 0x45, 0x79,
+ 0x11, 0x83, 0x61, 0x75, 0x80, 0x31, 0x7c, 0xe9, 0x7e, 0xa1, 0x78, 0x11, 0x7d, 0x99, 0x75, 0xe3,
+ 0x59, 0x78, 0xfd, 0x81, 0x00, 0x78, 0x58, 0xe7, 0x7c, 0xe9, 0x2d, 0x8f, 0x05, 0xd5, 0x70, 0x64,
+ 0x5c, 0xd5, 0x74, 0x2d, 0x79, 0x80, 0x5c, 0x2d, 0x8b, 0xd5, 0x70, 0x9c, 0x74, 0xd5, 0x84, 0xe8,
+ 0xa8, 0xe7, 0x84, 0x20, 0x69, 0x8b, 0x99, 0x74, 0x00, 0xd5, 0x7e, 0x80, 0x20, 0x95, 0x88, 0x69,
+ 0x77, 0xe2, 0x7c, 0xa9, 0x73, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0x00, 0x7c, 0x2d, 0x7b, 0xe9, 0x7d,
+ 0x78, 0xe3, 0xc5, 0x83, 0xf1, 0x94, 0x00, 0x7c, 0x59, 0x8c, 0xe9, 0x7d, 0x78, 0x20, 0xc5, 0x83,
+ 0xc5, 0x83, 0xe1,
+}
+
+var DeviceBrightnessAuto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x7d, 0x4d, 0x81,
+ 0xe7, 0x99, 0x84, 0x00, 0x80, 0x74, 0x20, 0xb5, 0x7d, 0x4d, 0x87, 0xe2, 0xa0, 0x61, 0x79, 0xe8,
+ 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61, 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89,
+ 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0, 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96,
+ 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0x01, 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe2,
+ 0x99, 0x84, 0x90, 0x20, 0x99, 0x7e, 0x78, 0xe7, 0x99, 0x79, 0x20, 0x99, 0x7e, 0x88, 0xe7, 0x35,
+ 0x7c, 0x00, 0x7c, 0x6c, 0xe7, 0x88, 0x20, 0x69, 0x86, 0xa4, 0xe7, 0x35, 0x7c, 0xe1,
+}
+
+var DeviceBrightnessHigh = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x61, 0x79, 0xe8,
+ 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61, 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89,
+ 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0, 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96,
+ 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0x01, 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe2,
+ 0x80, 0x98, 0xb0, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x92, 0x61, 0x85, 0x68, 0x98,
+ 0x68, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xe3, 0x80, 0x58, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe1,
+}
+
+var DeviceBrightnessLow = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xa1, 0x86, 0x01,
+ 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe8, 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61,
+ 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89, 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0,
+ 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96, 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0xe2,
+ 0x80, 0x98, 0xb0, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x92, 0x61, 0x85, 0x68, 0x98,
+ 0x68, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xe1,
+}
+
+var DeviceBrightnessMedium = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xa1, 0x86, 0x01,
+ 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe8, 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61,
+ 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89, 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0,
+ 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96, 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0xe2,
+ 0x80, 0x98, 0xe8, 0x68, 0xb0, 0xa1, 0x86, 0x80, 0x98, 0x61, 0x85, 0x98, 0x98, 0x90, 0xa1, 0x7a,
+ 0x98, 0x68, 0x98, 0xe1,
+}
+
+var DeviceDataUsage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x19, 0x6c, 0xe9,
+ 0x11, 0x86, 0xb1, 0xc9, 0x86, 0xf9, 0x80, 0x98, 0xcd, 0x86, 0x98, 0xd9, 0x8d, 0x80, 0xcd, 0x81,
+ 0xa9, 0x7f, 0x81, 0x83, 0x0d, 0x7f, 0x11, 0x85, 0x20, 0x35, 0x85, 0x11, 0x83, 0xa0, 0x59, 0x93,
+ 0xa9, 0x85, 0xa8, 0xe9, 0x82, 0xa8, 0x80, 0xb0, 0x80, 0xa1, 0x75, 0x1d, 0x78, 0x1d, 0x6d, 0x5c,
+ 0x19, 0x6c, 0xe2, 0x80, 0x9c, 0xb1, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79, 0x64, 0x64, 0x80, 0xf5,
+ 0x78, 0x39, 0x85, 0x21, 0x73, 0x98, 0x29, 0x72, 0xe8, 0x19, 0x6c, 0xa0, 0xe1, 0x73, 0x19, 0x6d,
+ 0x58, 0xa1, 0x75, 0x58, 0x80, 0xb1, 0x80, 0x0d, 0x8b, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa1,
+ 0x86, 0x80, 0x79, 0x8c, 0xc5, 0x7c, 0x1d, 0x90, 0xd1, 0x77, 0x20, 0xd1, 0x7a, 0xf1, 0x7c, 0xa0,
+ 0x59, 0x88, 0xf5, 0x8b, 0x6d, 0x84, 0x9c, 0x80, 0x9c, 0xe1,
+}
+
+var DeviceDeveloperMode = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x2d, 0x72, 0xe7,
+ 0xa8, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78,
+ 0x05, 0x7c, 0x20, 0x58, 0xfd, 0x7f, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0xd5, 0x90, 0x2d, 0x97, 0x02, 0xa0, 0x2d, 0x80, 0xd5, 0x86,
+ 0x6e, 0x88, 0xd5, 0x79, 0x20, 0x59, 0x86, 0x59, 0x86, 0x00, 0x88, 0x85, 0x86, 0x20, 0xd5, 0x82,
+ 0xd1, 0x82, 0xe2, 0x78, 0x85, 0x86, 0x20, 0xa9, 0x79, 0xa9, 0x79, 0x02, 0x78, 0xd5, 0x79, 0x2d,
+ 0x79, 0x6e, 0x60, 0x2d, 0x80, 0x20, 0x2d, 0x89, 0x2d, 0x89, 0x00, 0x78, 0x85, 0x86, 0xe3, 0x9c,
+ 0xa9, 0x87, 0xe6, 0x6c, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var DeviceDevices = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe7, 0xc8,
+ 0xe8, 0x60, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xac, 0xe6,
+ 0x50, 0xe9, 0x8c, 0xe7, 0xb8, 0xe9, 0x74, 0xe6, 0x60, 0xe8, 0x68, 0xe3, 0xcc, 0x88, 0xe6, 0x94,
+ 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xa8, 0xb0, 0x80, 0x19, 0x81, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0x98, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8,
+ 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0xa4, 0xe7, 0x70, 0xe8,
+ 0x78, 0xe7, 0x90, 0xe9, 0x9c, 0xe1,
+}
+
+var DeviceDVR = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xac, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe3, 0x78, 0x5c, 0xe6, 0x70, 0xe9, 0x88, 0xe7, 0xac, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe6, 0x70,
+ 0xe9, 0x88, 0xe7, 0xac, 0xe9, 0x78, 0xe3, 0x50, 0x70, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe1,
+}
+
+var DeviceGPSFixed = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe3, 0xe1, 0x91, 0x8c, 0xa0, 0xf5, 0x90, 0xa9, 0x75,
+ 0x59, 0x8a, 0x0d, 0x6f, 0x84, 0x21, 0x6e, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x21, 0x84, 0xa0, 0xa9,
+ 0x75, 0x0d, 0x6f, 0x0d, 0x6f, 0xa9, 0x75, 0x21, 0x6e, 0x7c, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x21,
+ 0x84, 0xb0, 0xed, 0x80, 0x59, 0x88, 0x8d, 0x87, 0xf5, 0x8e, 0xe1, 0x8f, 0xe1, 0x8f, 0xe8, 0xac,
+ 0xe7, 0x88, 0xe9, 0xe1, 0x7b, 0xb0, 0x59, 0x88, 0x15, 0x7f, 0xf5, 0x8e, 0x75, 0x78, 0xe1, 0x8f,
+ 0x21, 0x70, 0xe6, 0xac, 0xe9, 0x78, 0xe7, 0xe1, 0x7b, 0xe2, 0x80, 0x9c, 0xb0, 0x45, 0x78, 0x80,
+ 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c,
+ 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var DeviceGPSNotFixed = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x91, 0x7c, 0xa0,
+ 0xf5, 0x90, 0xa9, 0x75, 0x59, 0x8a, 0x0d, 0x6f, 0x84, 0x21, 0x6e, 0xe8, 0x54, 0xe7, 0x78, 0xe9,
+ 0x21, 0x84, 0xa0, 0xa9, 0x75, 0x0d, 0x6f, 0x0d, 0x6f, 0xa9, 0x75, 0x21, 0x6e, 0x7c, 0xe6, 0x54,
+ 0xe9, 0x88, 0xe7, 0x21, 0x84, 0xb0, 0xed, 0x80, 0x59, 0x88, 0x8d, 0x87, 0xf5, 0x8e, 0xe1, 0x8f,
+ 0xe1, 0x8f, 0xe8, 0xac, 0xe7, 0x88, 0xe9, 0xe1, 0x7b, 0xb0, 0x59, 0x88, 0x15, 0x7f, 0xf5, 0x8e,
+ 0x75, 0x78, 0xe1, 0x8f, 0x21, 0x70, 0xe6, 0xac, 0xe9, 0x78, 0xe7, 0xe1, 0x7b, 0xe2, 0x80, 0x9c,
+ 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c,
+ 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var DeviceGPSOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x91, 0x7c, 0xa0,
+ 0xf5, 0x90, 0xa9, 0x75, 0x59, 0x8a, 0x0d, 0x6f, 0x84, 0x21, 0x6e, 0xe8, 0x54, 0xe7, 0x78, 0xe9,
+ 0x21, 0x84, 0xb0, 0xbd, 0x7d, 0x41, 0x80, 0xa1, 0x7b, 0xf1, 0x80, 0xb1, 0x79, 0xf1, 0x81, 0x20,
+ 0x86, 0x86, 0xa0, 0x55, 0x7c, 0x61, 0x72, 0x21, 0x7e, 0x64, 0x80, 0x64, 0xb1, 0xbd, 0x87, 0x80,
+ 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0x80, 0xe1, 0x81, 0xa1, 0x7f, 0xad, 0x83, 0xf5, 0x7e, 0x51, 0x85,
+ 0x20, 0x86, 0x86, 0xb0, 0x05, 0x81, 0x11, 0x7e, 0xb1, 0x81, 0xf5, 0x7b, 0xf1, 0x81, 0xb1, 0x79,
+ 0xe6, 0xac, 0xe9, 0x78, 0xe7, 0xe1, 0x7b, 0xe2, 0x5c, 0x8d, 0x70, 0x20, 0x11, 0x84, 0x11, 0x84,
+ 0xa0, 0xf1, 0x6f, 0x3d, 0x77, 0x85, 0x6e, 0x75, 0x7a, 0x21, 0x6e, 0x7c, 0xe6, 0x54, 0xe9, 0x88,
+ 0xe7, 0x21, 0x84, 0xb0, 0xed, 0x80, 0x59, 0x88, 0x8d, 0x87, 0xf5, 0x8e, 0xe1, 0x8f, 0xe1, 0x8f,
+ 0xe8, 0xac, 0xe7, 0x88, 0xe9, 0xe1, 0x7b, 0xb0, 0x8d, 0x83, 0x9d, 0x7f, 0xc5, 0x86, 0x31, 0x7e,
+ 0x61, 0x89, 0x0d, 0x7c, 0x03, 0x75, 0x8f, 0xa4, 0xa4, 0x75, 0x8f, 0x8d, 0x70, 0x5c, 0x5c, 0x8d,
+ 0x70, 0xe3, 0x89, 0x9a, 0x89, 0x9a, 0xa0, 0x2d, 0x86, 0xe9, 0x8c, 0x39, 0x83, 0x9c, 0x80, 0x9c,
+ 0xb1, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79, 0x64, 0x64, 0x80, 0xc9, 0x7c, 0x19, 0x81, 0xd5, 0x79,
+ 0xed, 0x82, 0x79, 0x77, 0x20, 0x9d, 0x93, 0x9d, 0x93, 0xe1,
+}
+
+var DeviceGraphicEq = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x98, 0xe7, 0x88,
+ 0xe8, 0x68, 0xe7, 0x78, 0xe9, 0xb0, 0xe3, 0x90, 0x90, 0xe7, 0x88, 0xe8, 0x58, 0xe7, 0x78, 0xe9,
+ 0xd0, 0xe2, 0x5c, 0x88, 0xe7, 0x88, 0xe9, 0x70, 0xe6, 0x5c, 0xe9, 0x90, 0xe3, 0xb0, 0x90, 0xe7,
+ 0x88, 0xe8, 0x68, 0xe7, 0x78, 0xe9, 0xb0, 0xe3, 0x90, 0x60, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70,
+ 0xe7, 0x78, 0xe1,
+}
+
+var DeviceLocationDisabled = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x91, 0x2d, 0x7e,
+ 0xa0, 0xf5, 0x90, 0xd5, 0x75, 0x59, 0x8a, 0x35, 0x6f, 0x84, 0x4d, 0x6e, 0xe8, 0x2d, 0x6a, 0xe7,
+ 0x78, 0xe9, 0x21, 0x84, 0xb0, 0xbd, 0x7d, 0x41, 0x80, 0xa1, 0x7b, 0xf1, 0x80, 0xb1, 0x79, 0xf1,
+ 0x81, 0x20, 0x86, 0x86, 0xb2, 0xa5, 0x81, 0x55, 0x7f, 0x71, 0x83, 0xf5, 0x7e, 0x51, 0x85, 0xf5,
+ 0x7e, 0xbd, 0x87, 0x80, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0x80, 0xe1, 0x81, 0xa1, 0x7f, 0xad, 0x83,
+ 0xf5, 0x7e, 0x51, 0x85, 0x20, 0x86, 0x86, 0xb0, 0x05, 0x81, 0x11, 0x7e, 0xb1, 0x81, 0xf5, 0x7b,
+ 0xf1, 0x81, 0xb1, 0x79, 0xe6, 0xac, 0xe9, 0x78, 0xe7, 0xe1, 0x7b, 0xe2, 0x5c, 0xb9, 0x70, 0x20,
+ 0x11, 0x84, 0x11, 0x84, 0xb0, 0xdd, 0x7d, 0xa1, 0x82, 0x71, 0x7c, 0xd9, 0x85, 0x0d, 0x7c, 0x61,
+ 0x89, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x21, 0x84, 0xb0, 0xed, 0x80, 0x59, 0x88, 0x8d, 0x87, 0xf5,
+ 0x8e, 0xe1, 0x8f, 0xe1, 0x8f, 0xe9, 0x21, 0x84, 0xe7, 0x88, 0xe9, 0xe1, 0x7b, 0xb0, 0x8d, 0x83,
+ 0x9d, 0x7f, 0xc5, 0x86, 0x31, 0x7e, 0x61, 0x89, 0x0d, 0x7c, 0x20, 0x15, 0x84, 0x15, 0x84, 0x02,
+ 0xa4, 0xa1, 0x8f, 0x8d, 0x70, 0x2d, 0x6e, 0x5c, 0xb9, 0x70, 0xe3, 0x89, 0x9a, 0x89, 0x9a, 0xb2,
+ 0xa5, 0x7d, 0xd1, 0x81, 0xb1, 0x7a, 0xed, 0x82, 0x79, 0x77, 0xed, 0x82, 0x45, 0x78, 0x80, 0x64,
+ 0xbd, 0x79, 0x64, 0x64, 0x80, 0xc9, 0x7c, 0x19, 0x81, 0xd5, 0x79, 0xed, 0x82, 0x79, 0x77, 0x20,
+ 0x9d, 0x93, 0x9d, 0x93, 0xe1,
+}
+
+var DeviceLocationSearching = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x91, 0x2d, 0x7e,
+ 0xa0, 0xf5, 0x90, 0xd5, 0x75, 0x59, 0x8a, 0x35, 0x6f, 0x84, 0x4d, 0x6e, 0xe8, 0x2d, 0x6a, 0xe7,
+ 0x78, 0xe9, 0x21, 0x84, 0xb0, 0xa9, 0x77, 0xed, 0x80, 0x0d, 0x71, 0x8d, 0x87, 0x21, 0x70, 0xe1,
+ 0x8f, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x21, 0x84, 0xb0, 0xed, 0x80, 0x59, 0x88, 0x8d, 0x87, 0xf5,
+ 0x8e, 0xe1, 0x8f, 0xe1, 0x8f, 0xe9, 0x21, 0x84, 0xe7, 0x88, 0xe9, 0xe1, 0x7b, 0xb0, 0x59, 0x88,
+ 0x15, 0x7f, 0xf5, 0x8e, 0x75, 0x78, 0xe1, 0x8f, 0x21, 0x70, 0xe6, 0xac, 0xe9, 0x78, 0xe7, 0xe1,
+ 0x7b, 0xe2, 0x80, 0x2d, 0x8e, 0xb0, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45,
+ 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var DeviceNetworkCell = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x58, 0xa8, 0xe7, 0xd0, 0xe8, 0x58, 0xe1, 0xc0, 0x94, 0x6c, 0x00, 0x58, 0xa8, 0xe7, 0xbc, 0xe1,
+}
+
+var DeviceNetworkWiFi = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x05, 0x80, 0xfd, 0x92, 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70,
+ 0xb9, 0x68, 0x70, 0x80, 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68, 0x6c, 0x22, 0x45, 0x97, 0xfd, 0x9c,
+ 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xe1, 0xc0, 0x11, 0x6f, 0xe9, 0x7d, 0x23, 0xed,
+ 0x90, 0x11, 0x95, 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xed, 0x90, 0xf1, 0x6a, 0xa0,
+ 0x15, 0x90, 0x41, 0x7d, 0xa1, 0x89, 0x70, 0x80, 0x70, 0xb0, 0x61, 0x76, 0x80, 0xed, 0x6f, 0x41,
+ 0x85, 0x11, 0x6f, 0xe9, 0x85, 0xe1,
+}
+
+var DeviceNFC = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x2d, 0x6c, 0xe6,
+ 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe9, 0x40, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc8, 0xe6, 0x60,
+ 0xe9, 0x40, 0xe7, 0xc0, 0xe9, 0xc0, 0xe3, 0x78, 0x48, 0xe6, 0x84, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8d, 0x84, 0xb1, 0xd1, 0x7e, 0xb1, 0x80, 0x7c, 0xf9, 0x81, 0x7c,
+ 0x75, 0x83, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x90, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xb0, 0x80, 0x85, 0x7e, 0x31, 0x7f, 0x41, 0x7d, 0x7c, 0x8d, 0x7c, 0xe9, 0x75, 0x7b, 0xe7, 0x8c,
+ 0xe9, 0xa0, 0xe6, 0x70, 0xe9, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0xb0, 0xe7, 0xb0,
+ 0xe9, 0x50, 0xe1,
+}
+
+var DeviceScreenLockLandscape = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x64, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xb0, 0xe6, 0x64, 0xe8,
+ 0x6c, 0xe7, 0xb8, 0xe9, 0xa8, 0xe3, 0x5c, 0x7c, 0xe7, 0x90, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19,
+ 0x7f, 0x84, 0x7c, 0xe9, 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe9, 0x7c,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x90, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9,
+ 0x84, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x8c, 0xb0, 0x80, 0x19, 0x81,
+ 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x99, 0x81, 0x68, 0xb1, 0x80, 0xad, 0x7e, 0x11, 0x81, 0x99,
+ 0x7d, 0x69, 0x82, 0x99, 0x7d, 0x55, 0x81, 0x80, 0x69, 0x82, 0x15, 0x81, 0x69, 0x82, 0x69, 0x82,
+ 0xe9, 0x84, 0xe7, 0x35, 0x7b, 0xe9, 0x7c, 0xe1,
+}
+
+var DeviceScreenLockPortrait = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x90, 0xe7, 0x90,
+ 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x1d,
+ 0x7f, 0x7c, 0x7c, 0x7c, 0xe9, 0x7c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x90,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x84, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84,
+ 0xe9, 0x8c, 0xb0, 0x80, 0x19, 0x81, 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x99, 0x81, 0x68, 0xb1,
+ 0x80, 0xad, 0x7e, 0x11, 0x81, 0x99, 0x7d, 0x69, 0x82, 0x99, 0x7d, 0x55, 0x81, 0x80, 0x69, 0x82,
+ 0x15, 0x81, 0x69, 0x82, 0x69, 0x82, 0xe9, 0x84, 0xe7, 0x35, 0x7b, 0xe9, 0x7c, 0xe2, 0x94, 0x54,
+ 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc8, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc8, 0xe6,
+ 0x6c, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe1,
+}
+
+var DeviceScreenLockRotation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x85, 0x96, 0x8d, 0x81,
+ 0x23, 0xdd, 0x7a, 0xdd, 0x7a, 0x2d, 0x7d, 0xd5, 0x82, 0x71, 0x84, 0x71, 0x84, 0xb1, 0x74, 0x51,
+ 0x8b, 0x01, 0x09, 0x71, 0x59, 0x78, 0x59, 0x7c, 0x09, 0x6d, 0x22, 0x31, 0x84, 0x31, 0x84, 0xd5,
+ 0x82, 0x2d, 0x7d, 0x19, 0x7b, 0x19, 0x7b, 0xb0, 0xd5, 0x7e, 0xd5, 0x7e, 0xf1, 0x7c, 0xd5, 0x7e,
+ 0xc5, 0x7b, 0x80, 0x00, 0x7d, 0x6d, 0x39, 0x76, 0xb0, 0xd5, 0x7e, 0x2d, 0x81, 0xd5, 0x7e, 0x11,
+ 0x83, 0x80, 0x3d, 0x84, 0x00, 0x89, 0x85, 0x81, 0x92, 0xb0, 0x2d, 0x81, 0x2d, 0x81, 0x11, 0x83,
+ 0x2d, 0x81, 0x3d, 0x84, 0x80, 0x00, 0x81, 0x96, 0xc5, 0x85, 0xb0, 0x31, 0x81, 0xd9, 0x7e, 0x31,
+ 0x81, 0xf1, 0x7c, 0x05, 0x80, 0xc5, 0x7b, 0xe2, 0xf1, 0x78, 0xf9, 0x90, 0xa0, 0x69, 0x72, 0xe1,
+ 0x8d, 0xb5, 0x6d, 0x85, 0x87, 0x5a, 0x80, 0xe6, 0x54, 0xb1, 0x05, 0x81, 0x51, 0x8c, 0x51, 0x8b,
+ 0xac, 0xe9, 0x97, 0xac, 0x75, 0x80, 0x80, 0xe1, 0x80, 0xf5, 0x7f, 0x55, 0x81, 0xf1, 0x7f, 0x00,
+ 0x99, 0x7b, 0x4d, 0x8e, 0x20, 0x55, 0x7d, 0xad, 0x82, 0xe2, 0x90, 0x74, 0xe7, 0x94, 0xb0, 0x1d,
+ 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x60, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f, 0x7c,
+ 0x7c, 0x7c, 0xe8, 0x5a, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d,
+ 0x82, 0x76, 0x8a, 0xe9, 0x82, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90,
+ 0xb0, 0x80, 0x19, 0x81, 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x99, 0x81, 0x66, 0xb0, 0x80, 0x21,
+ 0x7e, 0x85, 0x81, 0x99, 0x7c, 0x69, 0x83, 0x99, 0x7c, 0x90, 0x69, 0x83, 0x85, 0x81, 0x69, 0x83,
+ 0x69, 0x83, 0xe9, 0x82, 0xe7, 0x35, 0x79, 0xe8, 0x5a, 0xe1,
+}
+
+var DeviceScreenRotation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xf9, 0x88, 0x09, 0x6d,
+ 0xa0, 0x81, 0x8f, 0x21, 0x70, 0x31, 0x94, 0x7d, 0x76, 0xe9, 0x94, 0x7c, 0xe7, 0x86, 0xa0, 0xe1,
+ 0x96, 0xb1, 0x71, 0x95, 0x8c, 0x50, 0x80, 0x50, 0xb0, 0x8d, 0x7f, 0x80, 0x21, 0x7f, 0x0d, 0x80,
+ 0xad, 0x7e, 0x11, 0x80, 0x00, 0x4d, 0x86, 0xb5, 0x6f, 0x20, 0xad, 0x82, 0x55, 0x7d, 0xe2, 0x75,
+ 0x7c, 0x7d, 0x6b, 0xb0, 0xd5, 0x7e, 0xd5, 0x7e, 0xf1, 0x7c, 0xd5, 0x7e, 0xc5, 0x7b, 0x80, 0x00,
+ 0x7d, 0x6b, 0x39, 0x78, 0xb0, 0xd5, 0x7e, 0x2d, 0x81, 0xd5, 0x7e, 0x11, 0x83, 0x80, 0x3d, 0x84,
+ 0x00, 0x89, 0x83, 0x81, 0x94, 0xb0, 0x2d, 0x81, 0x2d, 0x81, 0x11, 0x83, 0x2d, 0x81, 0x3d, 0x84,
+ 0x80, 0x00, 0x81, 0x94, 0xc5, 0x87, 0xb0, 0x2d, 0x81, 0xd5, 0x7e, 0x2d, 0x81, 0xf1, 0x7c, 0x80,
+ 0xc5, 0x7b, 0x00, 0x75, 0x7c, 0x7d, 0x6b, 0xe3, 0x35, 0x89, 0xe5, 0xa6, 0x01, 0x9d, 0x6d, 0x59,
+ 0x7a, 0x59, 0x7a, 0x9d, 0x6d, 0x21, 0x0d, 0x98, 0x0d, 0x98, 0x49, 0x73, 0xbd, 0x8c, 0xe3, 0x61,
+ 0x71, 0x99, 0x80, 0xa0, 0x81, 0x70, 0xe1, 0x8f, 0xd1, 0x6b, 0x85, 0x89, 0x19, 0x6b, 0x84, 0xe7,
+ 0x7a, 0xa0, 0x21, 0x69, 0x51, 0x8e, 0x6d, 0x73, 0xb0, 0x80, 0xb0, 0xb0, 0x75, 0x80, 0x80, 0xe1,
+ 0x80, 0xf5, 0x7f, 0x55, 0x81, 0xf1, 0x7f, 0x00, 0xb5, 0x79, 0x4d, 0x90, 0x20, 0x55, 0x7d, 0xad,
+ 0x82, 0xe1,
+}
+
+var DeviceSDStorage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x58, 0xe6, 0x78,
+ 0x01, 0x0d, 0x70, 0x70, 0x60, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80, 0x70, 0xe7, 0x78, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x90,
+ 0xe3, 0x8c, 0x80, 0xe7, 0x78, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x90, 0xe3, 0x8c, 0x80, 0xe7, 0x78,
+ 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var DeviceSettingsSystemDaydream = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x90, 0xe7, 0x9a,
+ 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x90, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe7,
+ 0xe9, 0x7f, 0xb1, 0x85, 0x7f, 0x9d, 0x7c, 0xa1, 0x7c, 0x74, 0x19, 0x79, 0x74, 0x35, 0x7d, 0x80,
+ 0xcd, 0x7a, 0xa9, 0x81, 0xb1, 0x79, 0x0d, 0x84, 0xe7, 0xad, 0x7f, 0xa0, 0x59, 0x76, 0x5d, 0x7c,
+ 0x68, 0xe9, 0x7e, 0x68, 0x84, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe2, 0xa4,
+ 0x5c, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x09,
+ 0xa0, 0xe6, 0x5c, 0xe8, 0xf9, 0x71, 0xe7, 0xc8, 0xe9, 0x11, 0x9c, 0xe1,
+}
+
+var DeviceSignalCellular0Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x58, 0xa8, 0xe7, 0xd0, 0xe8, 0x58, 0xe1,
+}
+
+var DeviceSignalCellular1Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x58, 0xa8, 0xe7, 0xd0, 0xe8, 0x58, 0xe1, 0xc0, 0x80, 0x80, 0x00, 0x58, 0xa8, 0xe7, 0xa8, 0xe1,
+}
+
+var DeviceSignalCellular2Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x58, 0xa8, 0xe7, 0xd0, 0xe8, 0x58, 0xe1, 0xc0, 0x88, 0x78, 0x00, 0x58, 0xa8, 0xe7, 0xb0, 0xe1,
+}
+
+var DeviceSignalCellular3Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x58, 0xa8, 0xe7, 0xd0, 0xe8, 0x58, 0xe1, 0xc0, 0x94, 0x6c, 0x00, 0x58, 0xa8, 0xe7, 0xbc, 0xe1,
+}
+
+var DeviceSignalCellular4Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0xa8, 0xe7, 0xd0,
+ 0xe8, 0x58, 0xe1,
+}
+
+var DeviceSignalCellularConnectedNoInternet0Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa8, 0x70, 0xe8, 0x58, 0x00, 0x58, 0xa8, 0xe7, 0xc0, 0xe8, 0x70, 0xe1, 0xc0, 0xa0, 0xa8, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x50, 0xe9, 0xa0, 0xe7, 0x88, 0xe8, 0x78,
+ 0xe7, 0x78, 0xe1,
+}
+
+var DeviceSignalCellularConnectedNoInternet1Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa8, 0x70, 0xe8, 0x58, 0x00, 0x58, 0xa8, 0xe7, 0xc0, 0xe8, 0x70, 0xe1, 0xc0, 0xa0, 0x78, 0xe9,
+ 0xa0, 0xe7, 0x88, 0xe8, 0x78, 0xe7, 0x78, 0xe2, 0x80, 0xa8, 0xe8, 0x80, 0x00, 0x58, 0xa8, 0xe7,
+ 0xa8, 0xe3, 0xa0, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var DeviceSignalCellularConnectedNoInternet2Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa8, 0x70, 0xe8, 0x58, 0x00, 0x58, 0xa8, 0xe7, 0xc0, 0xe8, 0x70, 0xe1, 0xc0, 0x88, 0xa8, 0xe8,
+ 0x78, 0x00, 0x58, 0xa8, 0xe7, 0xb0, 0xe3, 0x98, 0x50, 0xe9, 0xa0, 0xe7, 0x88, 0xe8, 0x78, 0xe7,
+ 0x78, 0xe3, 0x80, 0xb0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var DeviceSignalCellularConnectedNoInternet3Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa8, 0x70, 0xe8, 0x58, 0x00, 0x58, 0xa8, 0xe7, 0xc0, 0xe8, 0x70, 0xe1, 0xc0, 0x94, 0xa8, 0xe8,
+ 0x6c, 0x00, 0x58, 0xa8, 0xe7, 0xbc, 0xe3, 0x8c, 0x50, 0xe9, 0xa0, 0xe7, 0x88, 0xe8, 0x78, 0xe7,
+ 0x78, 0xe3, 0x80, 0xb0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var DeviceSignalCellularConnectedNoInternet4Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x98, 0xe7, 0x88,
+ 0xe8, 0x78, 0xe7, 0x78, 0xe9, 0xa0, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe2, 0x58, 0xa8, 0xe7, 0xc0, 0xe8, 0x70, 0xe7, 0x90, 0xe8, 0x58, 0x00, 0x58, 0xa8, 0xe1,
+}
+
+var DeviceSignalCellularNoSIM = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x8d, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x3d, 0x7e, 0x78, 0x05, 0x7c, 0x78, 0xe6, 0x78, 0x20, 0x51, 0x7b, 0xb1, 0x84,
+ 0x01, 0x9c, 0x5d, 0x89, 0xfd, 0x8d, 0x64, 0xe2, 0x4d, 0x6f, 0xc5, 0x6f, 0x01, 0xc5, 0x6c, 0x4d,
+ 0x72, 0x64, 0x8d, 0x77, 0xe8, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0x05, 0x94, 0xb0, 0xb5, 0x80, 0x80, 0x59, 0x81, 0xcd, 0x7f, 0xed, 0x81, 0x7d, 0x7f, 0x21, 0xc5,
+ 0x83, 0xc5, 0x83, 0x8d, 0x82, 0x75, 0x7d, 0x00, 0x4d, 0x6f, 0xc5, 0x6f, 0xe1,
+}
+
+var DeviceSignalCellularNull = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xa9, 0x75, 0xe8,
+ 0xa0, 0xe6, 0xa9, 0x75, 0x00, 0xa0, 0xa9, 0x75, 0xe2, 0xa8, 0x58, 0x00, 0x58, 0xa8, 0xe7, 0xd0,
+ 0xe8, 0x58, 0xe1,
+}
+
+var DeviceSignalCellularOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x54, 0x01, 0xd1,
+ 0x80, 0x31, 0x7b, 0xa4, 0x5d, 0x8c, 0xe8, 0x54, 0xe2, 0x8d, 0x71, 0x62, 0x00, 0x5e, 0x8d, 0x73,
+ 0x20, 0xbd, 0x8c, 0xbd, 0x8c, 0x00, 0x54, 0xa4, 0xe7, 0x75, 0xa3, 0x20, 0x88, 0x88, 0x01, 0xa8,
+ 0x75, 0x93, 0x8d, 0x71, 0x62, 0xe1,
+}
+
+var DeviceSignalWiFi0Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x05, 0x80, 0xfd, 0x92, 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70,
+ 0xb9, 0x68, 0x70, 0xa0, 0x91, 0x72, 0x5c, 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68, 0x6c, 0x22, 0x45,
+ 0x97, 0xfd, 0x9c, 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xe1,
+}
+
+var DeviceSignalWiFi1Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x05, 0x80, 0xfd, 0x92, 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70,
+ 0xb9, 0x68, 0x70, 0x80, 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68, 0x6c, 0x22, 0x45, 0x97, 0xfd, 0x9c,
+ 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xe1, 0xc0, 0x59, 0x75, 0xb9, 0x85, 0x23, 0xa9,
+ 0x8a, 0x45, 0x8d, 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xa9, 0x8a, 0xbd, 0x72, 0xa0,
+ 0x21, 0x8a, 0x51, 0x85, 0x11, 0x86, 0x84, 0x80, 0x84, 0x90, 0xe1, 0x75, 0x51, 0x83, 0x59, 0x75,
+ 0xb9, 0x83, 0xe1,
+}
+
+var DeviceSignalWiFi1BarLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa2, 0x76, 0xb0, 0xb5, 0x80, 0x80, 0x69, 0x81, 0x19, 0x80, 0x19, 0x82, 0x35, 0x80, 0x20, 0x35,
+ 0x84, 0xcd, 0x7a, 0xb0, 0x19, 0x7f, 0x4d, 0x7f, 0x35, 0x76, 0x70, 0xb5, 0x68, 0x70, 0x80, 0x99,
+ 0x69, 0x4d, 0x75, 0xb5, 0x68, 0x6c, 0x00, 0x80, 0xa6, 0x20, 0x8e, 0x4d, 0x77, 0xe8, 0x8a, 0xb0,
+ 0x80, 0x81, 0x7a, 0x81, 0x84, 0x6c, 0x94, 0x6c, 0xe1, 0xc0, 0xac, 0x90, 0xe9, 0x7a, 0xb0, 0x80,
+ 0x35, 0x7d, 0xcd, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x35, 0x82, 0x76, 0x8a, 0xe9, 0x86, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80,
+ 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x70,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x80, 0xe7, 0x74, 0xe9, 0x7a,
+ 0xb0, 0x80, 0x4d, 0x7e, 0x4d, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x4d, 0x81, 0x86, 0x86, 0xe9,
+ 0x86, 0xe3, 0x4d, 0x61, 0xb5, 0x7d, 0x20, 0x92, 0x35, 0x8b, 0x00, 0x80, 0xa6, 0x20, 0x8e, 0x35,
+ 0x77, 0xe8, 0x8a, 0xb2, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x19, 0x80, 0x99, 0x7e, 0x35, 0x7e, 0x35,
+ 0x7f, 0xcd, 0x7b, 0x69, 0x7e, 0xe9, 0x78, 0x69, 0x7e, 0xe9, 0x79, 0x80, 0xe9, 0x75, 0x4d, 0x83,
+ 0x4d, 0x75, 0xb5, 0x83, 0xe1,
+}
+
+var DeviceSignalWiFi2Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x05, 0x80, 0xfd, 0x92, 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70,
+ 0xb9, 0x68, 0x70, 0x80, 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68, 0x6c, 0x22, 0x45, 0x97, 0xfd, 0x9c,
+ 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xe1, 0xc0, 0x95, 0x71, 0x09, 0x81, 0x23, 0x69,
+ 0x8e, 0xf5, 0x91, 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0x69, 0x8e, 0x0d, 0x6e, 0xa0,
+ 0xb5, 0x8d, 0x79, 0x80, 0x35, 0x88, 0x78, 0x80, 0x78, 0x90, 0x4d, 0x72, 0x79, 0x84, 0x95, 0x71,
+ 0x09, 0x85, 0xe1,
+}
+
+var DeviceSignalWiFi2BarLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa2, 0x76, 0xb0, 0xb5, 0x80, 0x80, 0x69, 0x81, 0x19, 0x80, 0x19, 0x82, 0x35, 0x80, 0x20, 0x35,
+ 0x84, 0xcd, 0x7a, 0xb0, 0x19, 0x7f, 0x4d, 0x7f, 0x19, 0x76, 0x70, 0xb5, 0x68, 0x70, 0x80, 0x99,
+ 0x69, 0x4d, 0x75, 0xb5, 0x68, 0x6c, 0x00, 0x80, 0xa6, 0x20, 0x8e, 0x4d, 0x77, 0xe8, 0x8a, 0xb0,
+ 0x80, 0x81, 0x7a, 0x81, 0x84, 0x6c, 0x94, 0x6c, 0xe1, 0xc0, 0xac, 0x90, 0xe9, 0x7a, 0xb0, 0x80,
+ 0x35, 0x7d, 0xcd, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x35, 0x82, 0x76, 0x8a, 0xe9, 0x86, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80,
+ 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x70,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x80, 0xe7, 0x74, 0xe9, 0x7a,
+ 0xb0, 0x80, 0x4d, 0x7e, 0x4d, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x4d, 0x81, 0x86, 0x86, 0xe9,
+ 0x86, 0xe2, 0x99, 0x71, 0x82, 0x00, 0x80, 0xa6, 0x20, 0x8e, 0x4d, 0x77, 0xe8, 0x8a, 0xb0, 0x80,
+ 0x69, 0x7d, 0x82, 0x76, 0xb5, 0x82, 0x35, 0x79, 0xa0, 0x35, 0x87, 0x7a, 0xe9, 0x83, 0x78, 0x80,
+ 0x78, 0xb0, 0xcd, 0x77, 0x80, 0x4d, 0x72, 0x81, 0x84, 0x99, 0x71, 0x8a, 0xe1,
+}
+
+var DeviceSignalWiFi3Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0x05, 0x80, 0xfd, 0x92, 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70,
+ 0xb9, 0x68, 0x70, 0x80, 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68, 0x6c, 0x22, 0x45, 0x97, 0xfd, 0x9c,
+ 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xe1, 0xc0, 0x11, 0x6f, 0xe9, 0x7d, 0x23, 0xed,
+ 0x90, 0x11, 0x95, 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xed, 0x90, 0xf1, 0x6a, 0xa0,
+ 0x15, 0x90, 0x41, 0x7d, 0xa1, 0x89, 0x70, 0x80, 0x70, 0xb0, 0x61, 0x76, 0x80, 0xed, 0x6f, 0x41,
+ 0x85, 0x11, 0x6f, 0xe9, 0x85, 0xe1,
+}
+
+var DeviceSignalWiFi3BarLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x4c, 0x7f, 0x80, 0xc1,
+ 0xa2, 0x76, 0xb0, 0xb5, 0x80, 0x80, 0x69, 0x81, 0x19, 0x80, 0x19, 0x82, 0x35, 0x80, 0x20, 0x35,
+ 0x84, 0xcd, 0x7a, 0xb0, 0x19, 0x7f, 0x4d, 0x7f, 0x35, 0x76, 0x70, 0xb5, 0x68, 0x70, 0x80, 0x99,
+ 0x69, 0x4d, 0x75, 0xb5, 0x68, 0x6c, 0x00, 0x80, 0xa6, 0x20, 0x8e, 0x4d, 0x77, 0xe8, 0x8a, 0xb0,
+ 0x80, 0x81, 0x7a, 0x81, 0x84, 0x6c, 0x94, 0x6c, 0xe1, 0xc0, 0xac, 0x90, 0xe9, 0x7a, 0xb0, 0x80,
+ 0x35, 0x7d, 0xcd, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x35, 0x82, 0x76, 0x8a, 0xe9, 0x86, 0xb0,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80,
+ 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x70,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x80, 0xe7, 0x74, 0xe9, 0x7a,
+ 0xb0, 0x80, 0x4d, 0x7e, 0x4d, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x4d, 0x81, 0x86, 0x86, 0xe9,
+ 0x86, 0xe2, 0x19, 0x6f, 0xe9, 0x7d, 0x00, 0x80, 0xa6, 0x20, 0x8e, 0x4d, 0x77, 0xe8, 0x8a, 0xb0,
+ 0x80, 0xb5, 0x7b, 0xb5, 0x82, 0x70, 0x81, 0x86, 0x99, 0x76, 0xa0, 0x99, 0x8a, 0x74, 0xe9, 0x85,
+ 0x70, 0x80, 0x70, 0xb0, 0x69, 0x76, 0x80, 0xe9, 0x6f, 0x35, 0x85, 0x19, 0x6f, 0xe9, 0x85, 0xe1,
+}
+
+var DeviceSignalWiFi4Bar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x80, 0xfd, 0x92,
+ 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70, 0xb9, 0x68, 0x70, 0x80,
+ 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68, 0x6c, 0x22, 0x45, 0x97, 0xfd, 0x9c, 0x05, 0x80, 0x05, 0x80,
+ 0x05, 0x80, 0xfd, 0x7f, 0xe1,
+}
+
+var DeviceSignalWiFi4BarLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa2, 0x76, 0xb0, 0xb9,
+ 0x80, 0x80, 0x69, 0x81, 0x15, 0x80, 0x19, 0x82, 0x39, 0x80, 0x00, 0x49, 0x97, 0x6c, 0xb0, 0x19,
+ 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70, 0xb9, 0x68, 0x70, 0x80, 0xa1, 0x69, 0x51, 0x75, 0xb9, 0x68,
+ 0x6c, 0x23, 0x45, 0x97, 0xfd, 0x9c, 0x05, 0x80, 0x05, 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xfd, 0x86,
+ 0x4d, 0x77, 0xe8, 0x8a, 0xb0, 0x80, 0x7d, 0x7a, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0xe3, 0x8a, 0x9a,
+ 0xe9, 0x7a, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76,
+ 0x8a, 0xe9, 0x86, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80,
+ 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f,
+ 0x84, 0x7c, 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x80,
+ 0xe7, 0x74, 0xe9, 0x7a, 0xb0, 0x80, 0x59, 0x7e, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0xe9, 0x86, 0xe1,
+}
+
+var DeviceSignalWiFiOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x49, 0x97, 0x6c, 0xb1,
+ 0x19, 0x7f, 0x51, 0x7f, 0x29, 0x76, 0x70, 0xb9, 0x68, 0x70, 0xfd, 0x7c, 0x80, 0x39, 0x7a, 0x61,
+ 0x80, 0xb5, 0x77, 0xf5, 0x80, 0x01, 0x5d, 0x8c, 0x99, 0x83, 0x49, 0x97, 0x6c, 0xe2, 0x8d, 0x6e,
+ 0xe5, 0x6a, 0x00, 0x58, 0x71, 0x6d, 0x20, 0x1d, 0x84, 0x1d, 0x84, 0xb0, 0xb9, 0x7b, 0xf9, 0x81,
+ 0x15, 0x79, 0x19, 0x84, 0x9d, 0x78, 0x75, 0x84, 0x24, 0x45, 0x97, 0xfd, 0x9c, 0x05, 0x80, 0x05,
+ 0x80, 0x05, 0x80, 0xfd, 0x7f, 0xcd, 0x87, 0x49, 0x76, 0xa1, 0x86, 0xa1, 0x86, 0x01, 0xa2, 0x59,
+ 0x8d, 0x8d, 0x6e, 0xe5, 0x6a, 0xe1,
+}
+
+var DeviceStorage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0xa0, 0xe7, 0xd0,
+ 0xe9, 0x70, 0xe6, 0x58, 0xe9, 0x90, 0xe3, 0x88, 0x74, 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x60, 0xe9,
+ 0x78, 0xe2, 0x58, 0x60, 0xe9, 0x90, 0xe7, 0xd0, 0xe8, 0x60, 0xe6, 0x58, 0xe3, 0x90, 0x8c, 0xe6,
+ 0x60, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe2, 0x58, 0x88, 0xe7, 0xd0, 0xe9, 0x70, 0xe6, 0x58,
+ 0xe9, 0x90, 0xe3, 0x88, 0x74, 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x60, 0xe9, 0x78, 0xe1,
+}
+
+var DeviceUSB = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x6c, 0xe9, 0x90,
+ 0xe7, 0x84, 0xe9, 0x88, 0xe7, 0x74, 0xe8, 0x64, 0xe7, 0x88, 0x21, 0x74, 0x70, 0x74, 0x90, 0xe7,
+ 0x88, 0xe9, 0xa0, 0xe7, 0x74, 0xe9, 0xdd, 0x7b, 0xb3, 0x69, 0x81, 0x45, 0x7f, 0x69, 0x82, 0xd9,
+ 0x7d, 0x69, 0x82, 0x25, 0x7c, 0x80, 0x91, 0x7d, 0x09, 0x7e, 0x99, 0x7b, 0x99, 0x7b, 0x99, 0x7b,
+ 0x91, 0x7d, 0x80, 0x99, 0x7b, 0xf9, 0x81, 0x99, 0x7b, 0x69, 0x84, 0x80, 0xb5, 0x81, 0xfd, 0x80,
+ 0x21, 0x83, 0x69, 0x82, 0xdd, 0x83, 0xe8, 0x84, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0x8c, 0xe9, 0x19, 0x86, 0xb3, 0x95, 0x7e, 0xbd, 0x80, 0x99, 0x7d, 0x31, 0x82, 0x99,
+ 0x7d, 0xe9, 0x83, 0x80, 0x71, 0x82, 0xf9, 0x81, 0x69, 0x84, 0x69, 0x84, 0x69, 0x84, 0x71, 0x82,
+ 0x80, 0x69, 0x84, 0x09, 0x7e, 0x69, 0x84, 0x99, 0x7b, 0x80, 0x4d, 0x7e, 0x05, 0x7f, 0xd5, 0x7c,
+ 0x99, 0x7d, 0x19, 0x7c, 0xe8, 0x8c, 0xe7, 0x8c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe9, 0x78, 0xe7, 0x84, 0xe9, 0x70, 0xe7, 0x70, 0xe1,
+}
+
+var DeviceWallpaper = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x60, 0xe7, 0x9c,
+ 0xe8, 0x58, 0xe6, 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0x9c, 0xe7,
+ 0x88, 0xe8, 0x60, 0xe3, 0x98, 0xa4, 0x20, 0x70, 0x94, 0xe7, 0xb0, 0x21, 0x74, 0x70, 0xf1, 0x7b,
+ 0x6d, 0x85, 0x00, 0x78, 0x84, 0xe3, 0x9c, 0x6e, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x7a, 0x92, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86,
+ 0x7a, 0xe3, 0x8c, 0x66, 0xe6, 0x84, 0xe9, 0x88, 0xe7, 0x9c, 0xe9, 0x9c, 0xe7, 0x88, 0xe8, 0x60,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc8, 0xe6, 0x84, 0xe9, 0x88,
+ 0xe7, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x84, 0xe7, 0x78, 0xe9,
+ 0x9c, 0xe2, 0x60, 0x84, 0xe6, 0x58, 0xe9, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0x9c, 0xe9, 0x78, 0xe6, 0x60, 0xe8, 0x84, 0xe1,
+}
+
+var DeviceWidgets = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x84, 0xe9, 0xa0,
+ 0xe7, 0xa0, 0xe8, 0x84, 0xe6, 0x84, 0xe2, 0x5c, 0xa4, 0xe7, 0xa0, 0xe8, 0x84, 0xe6, 0x5c, 0xe9,
+ 0xa0, 0xe2, 0x5c, 0x5c, 0xe9, 0xa0, 0xe7, 0xa0, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0x51, 0x9b, 0x61,
+ 0x7d, 0x01, 0x7c, 0xb1, 0x76, 0x51, 0x89, 0x84, 0x20, 0x51, 0x8b, 0xb1, 0x74, 0x00, 0x51, 0x89,
+ 0x61, 0x6b, 0xe1,
+}
+
+var DeviceWiFiLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa2, 0x76, 0xb0, 0x91,
+ 0x80, 0x80, 0x19, 0x81, 0x15, 0x80, 0xa1, 0x81, 0x29, 0x80, 0x00, 0xb0, 0x68, 0xb0, 0x51, 0x79,
+ 0xfd, 0x7a, 0x62, 0x70, 0x50, 0x70, 0x80, 0xb1, 0x6e, 0xfd, 0x6e, 0x50, 0x68, 0x21, 0xb0, 0xc0,
+ 0x8e, 0xad, 0x76, 0xe8, 0x8a, 0xb0, 0x80, 0x7d, 0x7a, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0xe3, 0x8a,
+ 0x9a, 0xe9, 0x7a, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82,
+ 0x76, 0x8a, 0xe9, 0x86, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0,
+ 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19,
+ 0x7f, 0x84, 0x7c, 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c,
+ 0x80, 0xe7, 0x74, 0xe9, 0x7a, 0xb0, 0x80, 0x59, 0x7e, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86,
+ 0x59, 0x81, 0x86, 0x86, 0xe9, 0x86, 0xe1,
+}
+
+var DeviceWiFiTethering = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x7c, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x98, 0x88, 0xb0, 0x80, 0x61, 0x79, 0xa1, 0x7a,
+ 0x68, 0x68, 0x68, 0x90, 0x68, 0x61, 0x85, 0x68, 0x98, 0xb0, 0x80, 0x71, 0x84, 0x69, 0x82, 0x4d,
+ 0x88, 0xfd, 0x85, 0x61, 0x8a, 0x20, 0x05, 0x82, 0x85, 0x7c, 0xa0, 0xa1, 0x79, 0x85, 0x87, 0x70,
+ 0xf5, 0x84, 0x70, 0x84, 0xb0, 0x80, 0x95, 0x7b, 0x95, 0x83, 0x70, 0x90, 0x70, 0x90, 0x90, 0x95,
+ 0x83, 0x90, 0x90, 0xb0, 0x80, 0xf5, 0x82, 0x61, 0x7e, 0x85, 0x85, 0xfd, 0x7b, 0xe5, 0x86, 0x20,
+ 0x05, 0x82, 0x7d, 0x83, 0xa0, 0x99, 0x89, 0x4d, 0x8a, 0x98, 0x71, 0x86, 0x98, 0x84, 0xe2, 0x80,
+ 0x5c, 0xa0, 0xf5, 0x74, 0x5c, 0x58, 0xf5, 0x76, 0x58, 0x84, 0xb0, 0x80, 0x65, 0x87, 0x05, 0x84,
+ 0xd5, 0x8d, 0xfd, 0x89, 0x4d, 0x91, 0x20, 0x84, 0x8d, 0x7c, 0xa0, 0x39, 0x73, 0x11, 0x8d, 0x60,
+ 0xe9, 0x87, 0x60, 0x84, 0xb0, 0x80, 0x29, 0x77, 0x29, 0x87, 0x60, 0xa0, 0x60, 0x90, 0xa0, 0x29,
+ 0x87, 0xa0, 0xa0, 0xb0, 0x80, 0xe9, 0x85, 0xc9, 0x7c, 0x11, 0x8b, 0x05, 0x78, 0xd9, 0x8d, 0x20,
+ 0x84, 0x75, 0x83, 0xa0, 0xfd, 0x8f, 0xd5, 0x8f, 0xa8, 0x65, 0x89, 0xa8, 0x84, 0xb0, 0x80, 0xf5,
+ 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58, 0xe1,
+}
+
+var EditorAttachFile = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x92, 0x68, 0xe9, 0xae,
+ 0xb0, 0x80, 0x6d, 0x84, 0x6d, 0x7c, 0x90, 0x70, 0x90, 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0xe8,
+ 0x64, 0xb0, 0x80, 0x3d, 0x7d, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x90, 0x8a, 0x3d, 0x82, 0x8a, 0x8a,
+ 0xe9, 0xaa, 0xb1, 0x80, 0x19, 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe5, 0x7e, 0x80, 0x7c, 0x19,
+ 0x7f, 0x7c, 0x7c, 0xe8, 0x68, 0xe7, 0x7a, 0xe9, 0xa6, 0xb0, 0x80, 0xc5, 0x82, 0x3d, 0x82, 0x8a,
+ 0x8a, 0x8a, 0x90, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xe8, 0x64, 0xb0, 0x80, 0x95, 0x7b, 0x6d, 0x7c,
+ 0x70, 0x70, 0x70, 0x90, 0x70, 0x95, 0x83, 0x70, 0x90, 0xe9, 0xb2, 0xb0, 0x80, 0x15, 0x86, 0xf1,
+ 0x84, 0x96, 0x96, 0x96, 0x90, 0x96, 0x15, 0x7b, 0x96, 0x6a, 0xe8, 0x68, 0xe7, 0x7a, 0xe1,
+}
+
+var EditorAttachMoney = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x99, 0x7f, 0xcd, 0x7d,
+ 0xb2, 0x75, 0x7b, 0xd1, 0x7e, 0x74, 0x9d, 0x7d, 0x74, 0xb5, 0x7b, 0x80, 0xd1, 0x7d, 0x05, 0x82,
+ 0x4d, 0x7c, 0x69, 0x85, 0x4d, 0x7c, 0x91, 0x83, 0x80, 0xe1, 0x84, 0xb5, 0x81, 0x8a, 0x35, 0x84,
+ 0xe7, 0x6d, 0x84, 0xb0, 0xe1, 0x7f, 0x8d, 0x7c, 0xc5, 0x7d, 0x69, 0x79, 0x95, 0x79, 0x61, 0x78,
+ 0xe8, 0x5c, 0xe7, 0x74, 0xe9, 0x51, 0x84, 0xb4, 0x21, 0x7c, 0xd9, 0x80, 0x72, 0x59, 0x83, 0x72,
+ 0x39, 0x87, 0x80, 0xa1, 0x84, 0xd5, 0x83, 0xed, 0x86, 0x69, 0x89, 0x45, 0x88, 0x05, 0x85, 0x35,
+ 0x81, 0x8c, 0xf5, 0x82, 0x8c, 0xd5, 0x84, 0x80, 0x61, 0x81, 0x09, 0x7f, 0x91, 0x83, 0x99, 0x7a,
+ 0x91, 0x83, 0xe1, 0x7b, 0x80, 0x41, 0x7a, 0x29, 0x7e, 0x0d, 0x7a, 0xcd, 0x7b, 0xe7, 0x99, 0x7b,
+ 0xb0, 0x41, 0x80, 0x61, 0x84, 0x85, 0x83, 0xd5, 0x86, 0x61, 0x87, 0xa9, 0x87, 0xe8, 0xa4, 0xe7,
+ 0x8c, 0xe9, 0xb5, 0x7b, 0xb1, 0xe5, 0x83, 0x41, 0x7f, 0x8e, 0x7a, 0x8e, 0xe5, 0x78, 0x80, 0x59,
+ 0x7a, 0x25, 0x7b, 0x69, 0x78, 0x99, 0x76, 0x35, 0x77, 0xe1,
+}
+
+var EditorBorderAll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x5c, 0xe9, 0xc8,
+ 0xe7, 0xc8, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0xa0, 0xc0, 0xe6, 0x64, 0xe8, 0x84, 0xe7, 0x98, 0xe9,
+ 0x98, 0xe3, 0x80, 0x60, 0xe6, 0x64, 0xe8, 0x64, 0xe7, 0x98, 0xe9, 0x98, 0xe3, 0xa0, 0xa0, 0xe6,
+ 0x84, 0xe8, 0x84, 0xe7, 0x98, 0xe9, 0x98, 0xe3, 0x80, 0x60, 0xe6, 0x84, 0xe8, 0x64, 0xe7, 0x98,
+ 0xe9, 0x98, 0xe1,
+}
+
+var EditorBorderBottom = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x7c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x90, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe2, 0x74, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x90, 0xa0, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe2, 0x64, 0x5c, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe3, 0xa0, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x90, 0x90,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe2, 0x84, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe8, 0x5c, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x88,
+ 0xa8, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x64, 0x6c, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3,
+ 0xb8, 0x70, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3, 0x80, 0x98, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x48, 0x88, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe2, 0x5c, 0xa4, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x88, 0x68, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe1,
+}
+
+var EditorBorderClear = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x64, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x70, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe2, 0x5c, 0xa4, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80,
+ 0x70, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0xa0, 0xa0, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xa0, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3,
+ 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x50, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x60, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x9c, 0x5c, 0xe9, 0x88,
+ 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3, 0x60, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x90, 0xc0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderColor = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x81, 0x8b, 0x6c, 0x00,
+ 0x88, 0x81, 0x6e, 0x20, 0x58, 0xa8, 0xe8, 0x94, 0xe7, 0x81, 0x87, 0x20, 0xa8, 0x58, 0xe3, 0xe9,
+ 0x85, 0x19, 0x7a, 0xb0, 0xc9, 0x80, 0x39, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0x80, 0x2d, 0x7d, 0x00,
+ 0xbd, 0x8c, 0x99, 0x68, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f, 0x2d, 0x7d, 0x80,
+ 0x00, 0x8c, 0x81, 0x6c, 0x21, 0x81, 0x87, 0x81, 0x87, 0xe9, 0x83, 0x19, 0x7c, 0xe1, 0xa1, 0x5b,
+ 0x7f, 0x80, 0xc1, 0x50, 0xa0, 0xe7, 0xe0, 0xe9, 0x90, 0xe6, 0x50, 0xe1,
+}
+
+var EditorBorderHorizontal = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x88, 0x48, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe2, 0x5c, 0x94, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x90, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x64, 0x5c, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0xa0, 0x80,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x70, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x98,
+ 0xb8, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x60, 0x90, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x84, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe2,
+ 0x9c, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3, 0x80, 0x98, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x7c, 0x94, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x90, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderInner = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x78, 0x48, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe2, 0x5c, 0x94, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe2, 0x74, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe3, 0x70, 0x80, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0xb0, 0x80,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x88, 0x98, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3, 0x70,
+ 0xc8, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x84, 0x5c, 0xe7, 0x78, 0xe9, 0xa0,
+ 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0xa0, 0xe7, 0x88, 0xe8, 0x84, 0xe7, 0xa0, 0xe9, 0x78,
+ 0xe6, 0x84, 0xe8, 0x5c, 0xe3, 0x98, 0xc8, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3,
+ 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderLeft = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x80, 0x50, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe3, 0x70, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x40,
+ 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0xa4, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x5c, 0xe9, 0xc8, 0xe3, 0xc0,
+ 0x50, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0xb0, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3,
+ 0x80, 0x48, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3, 0x80, 0xa8, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x70, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderOuter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe2, 0x5c, 0x5c, 0xe9,
+ 0xc8, 0xe7, 0xc8, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0xc0, 0xc0, 0xe6, 0x64, 0xe8, 0x64, 0xe7, 0xb8,
+ 0xe9, 0xb8, 0xe3, 0x68, 0x70, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x70, 0x70,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe1,
+}
+
+var EditorBorderRight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x64, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0xa4, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe3, 0xa0, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x84,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0xa0,
+ 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x70, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x58, 0xe9, 0xc8, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3,
+ 0x70, 0xc8, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x40, 0xe7, 0x88, 0xe8,
+ 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderStyle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x50, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xa0, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x5c,
+ 0xe9, 0xc8, 0xe7, 0x88, 0xe8, 0x64, 0xe7, 0xc0, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0xc0, 0x98, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderTop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x94, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x60,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe9, 0x88, 0xe3, 0xa0, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0xa0,
+ 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x5c, 0x5c, 0xe9, 0x88, 0xe7, 0xc8, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3,
+ 0xc0, 0xb8, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x90, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x50, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0xa0, 0xb0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x60, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBorderVertical = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x74, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe8, 0x5c, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x90, 0xc0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x80, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x50, 0xe7, 0x88, 0xe8, 0x5c, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe3, 0xb0, 0xb0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x60,
+ 0x90, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0xc8, 0xe3, 0xa0, 0x80, 0xe7, 0x88, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3,
+ 0x80, 0x58, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe3, 0x80, 0x98, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x70, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x80, 0xc0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorBubbleChart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0xcd, 0x84, 0xd1,
+ 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0xcd, 0x8c, 0x80, 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0x35,
+ 0x73, 0x80, 0xe2, 0x99, 0x81, 0x98, 0xd1, 0x88, 0x88, 0x00, 0x04, 0x90, 0x80, 0x88, 0x88, 0x00,
+ 0x04, 0x70, 0x80, 0xe2, 0xcd, 0x7c, 0x99, 0x79, 0xd1, 0x99, 0x89, 0x99, 0x89, 0x00, 0x04, 0x35,
+ 0x93, 0x80, 0x99, 0x89, 0x99, 0x89, 0x00, 0x04, 0xcd, 0x6c, 0x80, 0xe1,
+}
+
+var EditorDragHandle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x74, 0xe6, 0x60,
+ 0xe9, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe2, 0x60, 0x8c, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x60, 0xe9,
+ 0x88, 0xe1,
+}
+
+var EditorFormatAlignCenter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x8c, 0xe9, 0x88,
+ 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x6c, 0xe2, 0x5c, 0xa4, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x60, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x90, 0x68, 0xe9,
+ 0x88, 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x6c, 0xe2, 0x5c, 0x5c, 0xe9, 0x88, 0xe7, 0xc8, 0xe8, 0x5c,
+ 0xe6, 0x5c, 0xe1,
+}
+
+var EditorFormatAlignJustify = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0xa4, 0xe7, 0xc8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x70, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7,
+ 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe2, 0x5c, 0x5c, 0xe9, 0x88, 0xe7, 0xc8, 0xe8, 0x5c,
+ 0xe6, 0x5c, 0xe1,
+}
+
+var EditorFormatAlignLeft = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x8c, 0xe6, 0x5c,
+ 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xe3, 0x80, 0x60, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe2, 0x5c, 0x84, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xe7,
+ 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe2, 0x5c, 0x5c, 0xe9, 0x88, 0xe7, 0xc8, 0xe8, 0x5c,
+ 0xe6, 0x5c, 0xe1,
+}
+
+var EditorFormatAlignRight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0xa4, 0xe7, 0xc8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x98, 0x70, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0x74, 0xe9,
+ 0x88, 0xe2, 0x5c, 0x84, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x98, 0x70, 0xe7,
+ 0xb0, 0xe9, 0x78, 0xe6, 0x74, 0xe9, 0x88, 0xe2, 0x5c, 0x5c, 0xe9, 0x88, 0xe7, 0xc8, 0xe8, 0x5c,
+ 0xe6, 0x5c, 0xe1,
+}
+
+var EditorFormatBold = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x35, 0x87, 0x95, 0x7d,
+ 0xb1, 0xf1, 0x81, 0xa9, 0x7e, 0x4d, 0x83, 0x79, 0x7c, 0x4d, 0x83, 0x6d, 0x7a, 0x80, 0x7d, 0x7b,
+ 0x85, 0x7c, 0x70, 0x70, 0x70, 0xe6, 0x6c, 0xe9, 0xb8, 0xe7, 0x15, 0x8e, 0xb1, 0x31, 0x84, 0x80,
+ 0x6d, 0x87, 0x99, 0x7c, 0x6d, 0x87, 0x6d, 0x78, 0x80, 0xf5, 0x7c, 0x45, 0x7e, 0x61, 0x7a, 0xb5,
+ 0x7b, 0x29, 0x79, 0xe2, 0x78, 0x6a, 0xe7, 0x8c, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0x90, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe7, 0x74, 0xe9, 0x74, 0xe3, 0x8e, 0xa4, 0xe7, 0x72,
+ 0xe9, 0x74, 0xe7, 0x8e, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0x59, 0x81, 0x86, 0x86, 0x90, 0xa9, 0x7e,
+ 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var EditorFormatClear = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8d, 0x6e, 0x64, 0x00,
+ 0x58, 0x8d, 0x74, 0x20, 0xf1, 0x8d, 0xf1, 0x8d, 0x00, 0x6a, 0x9c, 0xe7, 0x8c, 0x20, 0x25, 0x83,
+ 0xb1, 0x78, 0x03, 0x75, 0x89, 0xa4, 0x98, 0x75, 0x8f, 0x19, 0x6f, 0x8d, 0x72, 0x8d, 0x6e, 0x64,
+ 0xe2, 0x68, 0x64, 0xe9, 0x5d, 0x80, 0x00, 0xa5, 0x79, 0x70, 0xe7, 0xcd, 0x84, 0x21, 0x91, 0x7e,
+ 0x59, 0x83, 0x35, 0x84, 0x35, 0x84, 0x00, 0x71, 0x84, 0x70, 0xe6, 0xa0, 0xe9, 0x74, 0xe6, 0x68,
+ 0xe1,
+}
+
+var EditorFormatColorFill = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x21, 0x89, 0xe1, 0x79,
+ 0x00, 0x3d, 0x77, 0x50, 0x21, 0x2d, 0x7d, 0xd5, 0x82, 0xc5, 0x84, 0xc5, 0x84, 0x00, 0xe1, 0x6e,
+ 0xe1, 0x79, 0xb0, 0xd5, 0x7e, 0x2d, 0x81, 0xd5, 0x7e, 0x11, 0x83, 0x80, 0x3d, 0x84, 0x20, 0x96,
+ 0x96, 0xb0, 0x95, 0x80, 0x99, 0x80, 0x59, 0x81, 0xe1, 0x80, 0x21, 0x82, 0xe1, 0x80, 0x90, 0x8d,
+ 0x81, 0xb5, 0x7f, 0x21, 0x82, 0x21, 0x7f, 0x20, 0x96, 0x6a, 0xb0, 0x2d, 0x81, 0xd5, 0x7e, 0x2d,
+ 0x81, 0xf1, 0x7c, 0x80, 0xc5, 0x7b, 0xe2, 0x69, 0x72, 0x78, 0x01, 0x78, 0x6d, 0x72, 0x99, 0x85,
+ 0x78, 0xe6, 0x69, 0x72, 0xe2, 0x9c, 0x7e, 0x90, 0x78, 0x55, 0x84, 0x78, 0x8e, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x90, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xb0, 0x80, 0x55, 0x7d,
+ 0x78, 0x72, 0x78, 0x72, 0xe1, 0xa1, 0x5b, 0x7f, 0x80, 0xc1, 0x50, 0xa0, 0xe7, 0xe0, 0xe9, 0x90,
+ 0xe6, 0x50, 0xe1,
+}
+
+var EditorFormatColorReset = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x88, 0xb0, 0x80,
+ 0x70, 0x68, 0x69, 0x6a, 0x68, 0x69, 0x6a, 0x90, 0x59, 0x7d, 0x05, 0x83, 0x89, 0x7a, 0x0d, 0x87,
+ 0x00, 0xb5, 0x8b, 0x9d, 0x86, 0xb0, 0x31, 0x80, 0x29, 0x7f, 0x4d, 0x80, 0x4d, 0x7e, 0x4d, 0x80,
+ 0x65, 0x7d, 0xe3, 0x3d, 0x7e, 0x3d, 0x86, 0x02, 0x82, 0x82, 0x8d, 0x72, 0x8d, 0x72, 0x60, 0x19,
+ 0x75, 0x20, 0xa5, 0x86, 0xa5, 0x86, 0xa0, 0x1d, 0x75, 0xa9, 0x7e, 0x68, 0x95, 0x81, 0x68, 0x88,
+ 0xb1, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0x0d, 0x83, 0x80, 0xcd, 0x85, 0xdd, 0x7e,
+ 0xe9, 0x87, 0x7a, 0x22, 0x45, 0x85, 0x45, 0x85, 0x8d, 0x82, 0x75, 0x7d, 0x85, 0x7a, 0x85, 0x7a,
+ 0xe1,
+}
+
+var EditorFormatColorText = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xa1, 0x5b, 0x7f, 0x80, 0xc1,
+ 0x50, 0xa0, 0xe7, 0xe0, 0xe9, 0x90, 0xe6, 0x50, 0xe1, 0xc0, 0x7c, 0x5c, 0x00, 0x66, 0x94, 0xe7,
+ 0x81, 0x84, 0x20, 0x41, 0x82, 0x74, 0xe7, 0x81, 0x8c, 0x20, 0x41, 0x82, 0x8c, 0xe6, 0x9a, 0x00,
+ 0x84, 0x5c, 0xe7, 0x78, 0xe3, 0x41, 0x7d, 0xa4, 0x01, 0x80, 0x55, 0x73, 0xc1, 0x84, 0x80, 0xe7,
+ 0x81, 0x76, 0xe1,
+}
+
+var EditorFormatIndentDecrease = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x94, 0xe7, 0xa8,
+ 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x88, 0xe2, 0x5c, 0x80, 0x20, 0x90, 0x90, 0xe8, 0x70, 0x20, 0x70,
+ 0x90, 0xe3, 0x80, 0xa4, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe2, 0x5c, 0x5c, 0xe9,
+ 0x88, 0xe7, 0xc8, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0xa0, 0x98, 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x7c,
+ 0xe9, 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x88, 0xe1,
+}
+
+var EditorFormatIndentIncrease = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0xa4, 0xe7, 0xc8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x4c, 0xe9, 0xa0, 0x21, 0x90, 0x70, 0x70, 0x70,
+ 0xe3, 0xa0, 0xa4, 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x88, 0xe2, 0x5c, 0x5c, 0xe9, 0x88,
+ 0xe7, 0xc8, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0xa0, 0x98, 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x7c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x90, 0xe7, 0xa8, 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x88, 0xe1,
+}
+
+var EditorFormatItalic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x60, 0xe9, 0x8c,
+ 0xe7, 0x71, 0x84, 0x20, 0x25, 0x79, 0xa0, 0xe6, 0x68, 0xe9, 0x8c, 0xe7, 0xa0, 0xe9, 0x74, 0xe7,
+ 0x91, 0x7b, 0x20, 0xdd, 0x86, 0x60, 0xe6, 0x98, 0xe8, 0x60, 0xe1,
+}
+
+var EditorFormatLineSpacing = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x6c, 0xe7, 0x8a,
+ 0x21, 0x72, 0x72, 0x72, 0x8e, 0xe7, 0x8a, 0xe9, 0xa8, 0xe6, 0x56, 0x21, 0x8e, 0x8e, 0x8e, 0x72,
+ 0xe7, 0x76, 0xe8, 0x6c, 0xe3, 0x90, 0x78, 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0x78, 0xe3,
+ 0x80, 0xb8, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe6, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var EditorFormatListBulleted = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x7a, 0xb0, 0x59,
+ 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe2, 0x60, 0x62, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e,
+ 0x7a, 0x7a, 0x7a, 0xe3, 0x80, 0xb0, 0xb0, 0x55, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92,
+ 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xad, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3,
+ 0x8c, 0x8a, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe7, 0xb8, 0xe9,
+ 0x78, 0xe6, 0x6c, 0xe9, 0x88, 0xe3, 0x80, 0x60, 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c,
+ 0xe1,
+}
+
+var EditorFormatListNumbered = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x94, 0xe7, 0x88,
+ 0xe9, 0x82, 0xe6, 0x5c, 0xe9, 0x84, 0xe7, 0x84, 0xe9, 0x82, 0xe6, 0x58, 0xe9, 0x84, 0xe7, 0x8c,
+ 0xe9, 0x70, 0xe6, 0x58, 0xe9, 0x84, 0xe3, 0x84, 0x5c, 0xe7, 0x84, 0xe8, 0x60, 0xe6, 0x58, 0xe9,
+ 0x84, 0xe7, 0x84, 0xe9, 0x8c, 0xe3, 0x7c, 0x8c, 0xe7, 0x99, 0x83, 0x00, 0x58, 0x35, 0x82, 0xe8,
+ 0x88, 0xe7, 0x8c, 0xe9, 0x7c, 0xe6, 0x69, 0x6e, 0x20, 0x99, 0x83, 0xcd, 0x7b, 0xe8, 0x78, 0xe6,
+ 0x58, 0xe9, 0x84, 0xe3, 0x94, 0x68, 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c, 0xe3, 0x80,
+ 0xb8, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x6c, 0xe9, 0x88, 0xe3, 0x80, 0x68, 0xe7, 0xb8, 0xe9, 0x78,
+ 0xe6, 0x6c, 0xe9, 0x88, 0xe1,
+}
+
+var EditorFormatPaint = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0xe8, 0x5c,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x64, 0xb0, 0xe9, 0x7e, 0x80, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xb0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x7c, 0xe7, 0x84, 0xe9, 0x90,
+ 0xe6, 0x74, 0xe9, 0xac, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x88, 0xb0,
+ 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x80, 0xe7, 0xa0, 0xe8, 0x60, 0xe7, 0x74,
+ 0xe1,
+}
+
+var EditorFormatQuote = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x94, 0xe7, 0x8c,
+ 0x20, 0x88, 0x70, 0xe8, 0x6c, 0xe6, 0x64, 0xe9, 0x98, 0xe7, 0x8c, 0xe3, 0xa0, 0x80, 0xe7, 0x8c,
+ 0x20, 0x88, 0x70, 0xe8, 0x6c, 0xe6, 0x84, 0xe9, 0x98, 0xe7, 0x8c, 0xe1,
+}
+
+var EditorFormatShapes = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x6c, 0xe8, 0x54,
+ 0xe6, 0x94, 0xe9, 0x88, 0xe6, 0x6c, 0xe8, 0x54, 0xe6, 0x54, 0xe9, 0x98, 0xe7, 0x88, 0xe9, 0xa8,
+ 0xe6, 0x54, 0xe9, 0x98, 0xe7, 0x98, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe7, 0x98, 0xe8, 0x94,
+ 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe2, 0x5c, 0x5c, 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x5c, 0xe8,
+ 0x5c, 0xe3, 0x88, 0xc8, 0xe6, 0x5c, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0xb0, 0x78, 0xe6,
+ 0x6c, 0xe9, 0x78, 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe9, 0xa8, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x78, 0x40, 0xe8, 0x5c, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe2, 0x79, 0x83,
+ 0x88, 0xe6, 0x81, 0x7c, 0x20, 0x8d, 0x7e, 0x88, 0xe7, 0xc1, 0x7c, 0x20, 0xcd, 0x86, 0x5c, 0xe7,
+ 0xd1, 0x82, 0x20, 0xd1, 0x86, 0xa4, 0xe7, 0xbd, 0x7c, 0x20, 0x85, 0x7e, 0x78, 0xe3, 0xe9, 0x79,
+ 0x7d, 0x7d, 0xe7, 0x39, 0x85, 0x21, 0x65, 0x7d, 0x59, 0x78, 0x65, 0x7d, 0xa9, 0x87, 0xe1,
+}
+
+var EditorFormatSize = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x60, 0xe9, 0x8c,
+ 0xe7, 0x94, 0xe9, 0xb0, 0xe7, 0x8c, 0xe8, 0x6c, 0xe7, 0x94, 0xe8, 0x60, 0xe6, 0x74, 0xe2, 0x5c,
+ 0x80, 0xe7, 0x8c, 0xe9, 0x9c, 0xe7, 0x8c, 0xe8, 0x80, 0xe7, 0x8c, 0xe9, 0x74, 0xe6, 0x5c, 0xe9,
+ 0x8c, 0xe1,
+}
+
+var EditorFormatStrikethrough = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x9c, 0xe7, 0x90,
+ 0xe9, 0x74, 0xe7, 0x70, 0xe9, 0x8c, 0xe2, 0x64, 0x60, 0xe9, 0x8c, 0xe7, 0x94, 0xe9, 0x8c, 0xe7,
+ 0x90, 0xe9, 0x74, 0xe7, 0x94, 0xe8, 0x60, 0xe6, 0x64, 0xe2, 0x5c, 0x88, 0xe7, 0xc8, 0xe9, 0x78,
+ 0xe6, 0x5c, 0xe9, 0x88, 0xe1,
+}
+
+var EditorFormatTextDirectionLToR = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x78, 0xe9, 0x94,
+ 0xe7, 0x88, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0xac, 0xe7, 0x88, 0xe8, 0x60, 0xe7, 0x88, 0xe8, 0x58,
+ 0xe6, 0x74, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x90, 0x95, 0x83, 0x90, 0x90,
+ 0x90, 0xe3, 0xb0, 0xa0, 0x20, 0x70, 0x70, 0xe9, 0x8c, 0xe6, 0x64, 0xe9, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x8c, 0x20, 0x90, 0x70, 0xe1,
+}
+
+var EditorFormatTextDirectionRToL = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x78, 0xe9, 0x94,
+ 0xe7, 0x88, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0xac, 0xe7, 0x88, 0xe8, 0x60, 0xe7, 0x88, 0xe8, 0x58,
+ 0xe6, 0x78, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x90, 0x95, 0x83, 0x90, 0x90,
+ 0x90, 0xe3, 0x78, 0x9c, 0xe9, 0x74, 0x21, 0x70, 0x90, 0x90, 0x90, 0xe9, 0x74, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe6, 0x70, 0xe1,
+}
+
+var EditorFormatUnderlined = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x94, 0xb0, 0xa1,
+ 0x86, 0x80, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0xe8, 0x5c, 0xe7, 0x76, 0xe9, 0xa0, 0xb0, 0x80, 0xe1,
+ 0x83, 0xe1, 0x7c, 0x8e, 0x72, 0x8e, 0x90, 0x72, 0xe1, 0x7c, 0x72, 0x72, 0xe8, 0x5c, 0xe7, 0x76,
+ 0xe9, 0xa0, 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe3, 0x64, 0x88, 0xe9, 0x88,
+ 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x64, 0xe1,
+}
+
+var EditorFunctions = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0xe6, 0x68,
+ 0xe9, 0x88, 0x21, 0x9a, 0x98, 0x66, 0x98, 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x74, 0xe6, 0x7c, 0x21,
+ 0x94, 0x6c, 0x6c, 0x6c, 0xe7, 0x9c, 0xe1,
+}
+
+var EditorHighlight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x88, 0x20, 0x8c,
+ 0x8c, 0xe9, 0x94, 0xe7, 0x98, 0xe8, 0x94, 0x20, 0x8c, 0x74, 0xe8, 0x74, 0xe6, 0x68, 0xe2, 0x7c,
+ 0x58, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x78, 0xe2, 0x5e, 0xc1, 0x73, 0x22, 0xd5, 0x82, 0x2d, 0x7d,
+ 0x41, 0x84, 0x41, 0x84, 0x31, 0x7d, 0xd5, 0x82, 0xe3, 0xed, 0x9a, 0x6d, 0x81, 0x21, 0x3d, 0x84,
+ 0xc5, 0x7b, 0xd5, 0x82, 0xd5, 0x82, 0x00, 0xc1, 0x8c, 0x70, 0xe1,
+}
+
+var EditorInsertChart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x94, 0xe7, 0x78, 0xe8,
+ 0x78, 0xe7, 0x88, 0xe9, 0x9c, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0xa8,
+ 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var EditorInsertComment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0x20, 0x90, 0x90, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xb0, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe3,
+ 0x80, 0x74, 0xe6, 0x68, 0xe9, 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe6, 0x68, 0xe9,
+ 0x78, 0xe7, 0xb0, 0xe9, 0x88, 0xe1,
+}
+
+var EditorInsertDriveFile = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x58, 0xa0, 0xcd,
+ 0x71, 0x58, 0x05, 0x70, 0xcd, 0x6d, 0x05, 0x70, 0x60, 0x00, 0x60, 0xa0, 0xb0, 0x80, 0x35, 0x82,
+ 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe6, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x70, 0x00, 0x88, 0x58, 0xe6, 0x68, 0xe3, 0x9c, 0x9c, 0xe8, 0x5e, 0x20, 0x96, 0x96,
+ 0xe6, 0x84, 0xe1,
+}
+
+var EditorInsertEmoticon = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2,
+ 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80,
+ 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x8e, 0x5c, 0xb0,
+ 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x64, 0x80, 0xb0, 0xa9, 0x81, 0x80, 0x86,
+ 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0xe3, 0x8e, 0x9a, 0xb0, 0xa9, 0x84, 0x80, 0x9d, 0x88, 0x19, 0x7d, 0x35,
+ 0x8a, 0x72, 0xe6, 0xcd, 0x75, 0xb0, 0x99, 0x81, 0x19, 0x84, 0x8d, 0x85, 0x8e, 0x35, 0x8a, 0x8e,
+ 0xe1,
+}
+
+var EditorInsertInvitation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x80, 0xe6, 0x80,
+ 0xe9, 0x94, 0xe7, 0x94, 0xe8, 0x80, 0xe2, 0x90, 0x54, 0xe9, 0x88, 0xe6, 0x70, 0xe8, 0x54, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x5c, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe7, 0x7c, 0xe8, 0x54, 0xe7, 0x78, 0xe3, 0x8c, 0xc8, 0xe6, 0x64, 0xe8, 0x70, 0xe7,
+ 0xb8, 0xe9, 0xac, 0xe1,
+}
+
+var EditorInsertLink = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x6f, 0x80, 0xb0,
+ 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79, 0xe7, 0x90, 0xe8, 0x6c, 0xe7,
+ 0x70, 0xa0, 0x7d, 0x70, 0x6c, 0x58, 0x7d, 0x7a, 0x58, 0x80, 0x90, 0x7d, 0x84, 0x94, 0x94, 0x94,
+ 0xe7, 0x90, 0xe9, 0x35, 0x7c, 0xe7, 0x70, 0xb0, 0x95, 0x7c, 0x80, 0xcd, 0x79, 0x39, 0x7d, 0xcd,
+ 0x79, 0xcd, 0x79, 0xe3, 0x35, 0x88, 0x84, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x70, 0xe9, 0x88, 0xe3,
+ 0xa4, 0x68, 0xe7, 0x70, 0xe9, 0xcd, 0x83, 0xe7, 0x90, 0xb0, 0x6d, 0x83, 0x80, 0x35, 0x86, 0xc9,
+ 0x82, 0x35, 0x86, 0x35, 0x86, 0x90, 0x39, 0x7d, 0x35, 0x86, 0xcd, 0x79, 0x35, 0x86, 0xe7, 0x70,
+ 0xe8, 0x94, 0xe7, 0x90, 0xb0, 0x85, 0x85, 0x80, 0x94, 0x85, 0x7b, 0x94, 0x6c, 0x90, 0x85, 0x7b,
+ 0x6c, 0x6c, 0x6c, 0xe1,
+}
+
+var EditorInsertPhoto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x9c, 0xe8, 0x64,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe2, 0x72, 0x86, 0x20, 0x8a, 0x05,
+ 0x86, 0x00, 0x8a, 0x80, 0x20, 0x92, 0x98, 0xe6, 0x64, 0x20, 0x8e, 0x6e, 0xe1,
+}
+
+var EditorLinearScale = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9e, 0x76, 0xb0, 0xf5,
+ 0x7d, 0x80, 0x31, 0x7c, 0x3d, 0x81, 0x6d, 0x7b, 0x86, 0xe7, 0x29, 0x7a, 0xb0, 0x3d, 0x7f, 0x3d,
+ 0x7e, 0x79, 0x7d, 0x7a, 0x6d, 0x7b, 0x7a, 0x90, 0x31, 0x7c, 0x3d, 0x81, 0x6d, 0x7b, 0x86, 0xe7,
+ 0x29, 0x7a, 0xb1, 0x3d, 0x7f, 0x3d, 0x7e, 0x79, 0x7d, 0x7a, 0x6d, 0x7b, 0x7a, 0x3d, 0x7d, 0x80,
+ 0x76, 0x3d, 0x82, 0x76, 0x8a, 0x90, 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0xb0, 0x0d, 0x82, 0x80, 0xd1,
+ 0x83, 0xc5, 0x7e, 0x95, 0x84, 0x7a, 0xe7, 0xd9, 0x85, 0xb0, 0xc5, 0x80, 0xc5, 0x81, 0x89, 0x82,
+ 0x86, 0x95, 0x84, 0x86, 0x90, 0xd1, 0x83, 0xc5, 0x7e, 0x95, 0x84, 0x7a, 0xe7, 0xd9, 0x85, 0xb1,
+ 0xc5, 0x80, 0xc5, 0x81, 0x89, 0x82, 0x86, 0x95, 0x84, 0x86, 0xc5, 0x82, 0x80, 0x8a, 0xc5, 0x7d,
+ 0x8a, 0x76, 0x90, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe1,
+}
+
+var EditorMergeType = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0xd5, 0x90, 0x03,
+ 0xd5, 0x8c, 0x9c, 0x8c, 0x2d, 0x87, 0x2d, 0x83, 0x94, 0x94, 0xd5, 0x90, 0xe2, 0x6e, 0x70, 0xe7,
+ 0x8e, 0xe9, 0x2d, 0x8b, 0x01, 0x2d, 0x73, 0x9c, 0x6c, 0xd5, 0x90, 0x20, 0x98, 0x68, 0xe8, 0x70,
+ 0xe7, 0x8e, 0x21, 0x6e, 0x6e, 0x6e, 0x92, 0xe1,
+}
+
+var EditorModeComment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x93, 0x60, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x3d, 0x7e, 0x78, 0x05, 0x7c, 0x78, 0xe6, 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58,
+ 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0x21, 0x90, 0x90, 0xfd, 0x7f, 0x38, 0xe1,
+}
+
+var EditorModeEdit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x81, 0x8a, 0xe8,
+ 0xa4, 0xe7, 0x81, 0x87, 0x21, 0x21, 0x96, 0xe1, 0x69, 0x81, 0x78, 0x81, 0x78, 0x00, 0x5c, 0x81,
+ 0x8a, 0xe3, 0x69, 0xa3, 0x99, 0x6b, 0xb0, 0xc9, 0x80, 0x39, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0x80,
+ 0x2d, 0x7d, 0x20, 0x55, 0x7b, 0x55, 0x7b, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f,
+ 0x2d, 0x7d, 0x80, 0x22, 0x59, 0x7c, 0xa9, 0x83, 0x81, 0x87, 0x81, 0x87, 0xa9, 0x83, 0x59, 0x7c,
+ 0xe1,
+}
+
+var EditorMonetizationOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0xd5, 0x82, 0x31, 0xa0, 0xe8, 0xa0, 0xe6,
+ 0x81, 0x7d, 0xe9, 0x25, 0x7c, 0xb0, 0x99, 0x7c, 0x45, 0x7f, 0xad, 0x79, 0x15, 0x7d, 0x75, 0x79,
+ 0x31, 0x79, 0xe7, 0xe9, 0x83, 0xb4, 0x35, 0x80, 0x19, 0x82, 0xa5, 0x81, 0xbd, 0x83, 0x4d, 0x85,
+ 0xbd, 0x83, 0xf1, 0x83, 0x80, 0xcd, 0x84, 0x0d, 0x7e, 0xcd, 0x84, 0xd1, 0x7c, 0x80, 0x59, 0x7e,
+ 0x1d, 0x7f, 0xc9, 0x7c, 0xad, 0x7a, 0xb9, 0x7b, 0x0d, 0x7b, 0xd1, 0x7e, 0xa5, 0x77, 0xc5, 0x7c,
+ 0xa5, 0x77, 0xa9, 0x78, 0x80, 0x91, 0x7c, 0xc5, 0x82, 0x55, 0x7a, 0x39, 0x86, 0x95, 0x79, 0xe8,
+ 0x60, 0xe7, 0x55, 0x85, 0xe9, 0xe5, 0x83, 0xb0, 0xb9, 0x83, 0xe9, 0x80, 0x95, 0x85, 0xb9, 0x83,
+ 0xb5, 0x85, 0xc5, 0x86, 0xe6, 0x99, 0x84, 0xb4, 0xe5, 0x7f, 0xc9, 0x7d, 0xb9, 0x7e, 0x45, 0x7c,
+ 0x91, 0x7b, 0x45, 0x7c, 0x7a, 0x80, 0x35, 0x7b, 0x59, 0x81, 0x35, 0x7b, 0x4d, 0x83, 0x80, 0xb1,
+ 0x81, 0x4d, 0x81, 0xc5, 0x82, 0x55, 0x85, 0xd1, 0x83, 0x0d, 0x84, 0x0d, 0x81, 0x5d, 0x88, 0xc5,
+ 0x82, 0x5d, 0x88, 0xd1, 0x87, 0x80, 0xa9, 0x83, 0x3d, 0x7d, 0xa9, 0x85, 0xc9, 0x79, 0x51, 0x86,
+ 0xe1,
+}
+
+var EditorMoneyOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x82, 0xcd, 0x75, 0xb0,
+ 0x91, 0x83, 0x80, 0xe1, 0x84, 0xb5, 0x81, 0x8a, 0x35, 0x84, 0xe7, 0x6d, 0x84, 0xb0, 0xdd, 0x7f,
+ 0x91, 0x7c, 0xc5, 0x7d, 0x69, 0x79, 0x95, 0x79, 0x61, 0x78, 0xe8, 0x5c, 0xe7, 0x74, 0xe9, 0x51,
+ 0x84, 0xb0, 0xf1, 0x7e, 0x3d, 0x80, 0xf1, 0x7d, 0x99, 0x80, 0x0d, 0x7d, 0x15, 0x81, 0x20, 0xf1,
+ 0x82, 0xf1, 0x82, 0xb0, 0xd1, 0x80, 0xa9, 0x7f, 0xd1, 0x81, 0x75, 0x7f, 0x05, 0x83, 0x75, 0x7f,
+ 0xe2, 0xa9, 0x72, 0x21, 0x70, 0x01, 0x21, 0x70, 0xa9, 0x72, 0x6e, 0x8d, 0x79, 0xb0, 0x80, 0x29,
+ 0x84, 0x21, 0x83, 0x6d, 0x86, 0xd1, 0x87, 0xd1, 0x87, 0x20, 0x05, 0x87, 0x05, 0x87, 0xb1, 0x51,
+ 0x7f, 0xf5, 0x80, 0xe9, 0x7d, 0xd1, 0x81, 0x29, 0x7b, 0xd1, 0x81, 0xe1, 0x7b, 0x80, 0x45, 0x7a,
+ 0x29, 0x7e, 0x0d, 0x7a, 0xcd, 0x7b, 0xe7, 0x99, 0x7b, 0xb0, 0x3d, 0x80, 0x61, 0x84, 0x85, 0x83,
+ 0xd9, 0x86, 0x5d, 0x87, 0xa9, 0x87, 0xe8, 0xa4, 0xe7, 0x8c, 0xe9, 0xb5, 0x7b, 0xb0, 0xed, 0x81,
+ 0xa5, 0x7f, 0xa5, 0x83, 0xe9, 0x7e, 0xe9, 0x84, 0xc5, 0x7d, 0x21, 0x71, 0x84, 0x71, 0x84, 0x8d,
+ 0x82, 0x75, 0x7d, 0x00, 0xa9, 0x72, 0x21, 0x70, 0xe1,
+}
+
+var EditorMultilineChart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0xd9, 0x75, 0x21,
+ 0x31, 0x7d, 0x31, 0x7d, 0x4d, 0x7a, 0x69, 0x86, 0xa1, 0x5d, 0x87, 0xd1, 0x74, 0xa9, 0x81, 0x64,
+ 0x39, 0x7b, 0x64, 0x71, 0x75, 0x64, 0x25, 0x70, 0x51, 0x74, 0x58, 0x70, 0x20, 0xd9, 0x82, 0xd9,
+ 0x82, 0xb1, 0x69, 0x83, 0x05, 0x7d, 0xb5, 0x87, 0x29, 0x7b, 0x61, 0x8c, 0x29, 0x7b, 0x7d, 0x85,
+ 0x80, 0x31, 0x8a, 0x85, 0x82, 0x8d, 0x8d, 0x7d, 0x86, 0x00, 0x86, 0xf5, 0x82, 0x20, 0x70, 0x70,
+ 0x00, 0x58, 0xfd, 0x89, 0x23, 0x86, 0x86, 0x98, 0xfd, 0x73, 0x90, 0x90, 0x19, 0x88, 0xe9, 0x76,
+ 0xb0, 0x81, 0x81, 0xb5, 0x82, 0x81, 0x82, 0xcd, 0x85, 0xe5, 0x82, 0x19, 0x89, 0xe6, 0xa4, 0xb0,
+ 0x91, 0x7f, 0x65, 0x7b, 0x1d, 0x7e, 0x39, 0x77, 0xe9, 0x7b, 0xbd, 0x73, 0x00, 0xa8, 0xd9, 0x75,
+ 0xe1,
+}
+
+var EditorPieChart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x58, 0xe9, 0xd0,
+ 0xa0, 0xdd, 0x73, 0xfd, 0x92, 0x58, 0x6d, 0x8a, 0x58, 0x80, 0x80, 0xdd, 0x73, 0x5a, 0x7c, 0x58,
+ 0xe3, 0x11, 0x84, 0x80, 0xe9, 0xfd, 0x91, 0xe6, 0xa8, 0xa0, 0x11, 0x93, 0x7d, 0x74, 0x89, 0x8b,
+ 0xf1, 0x6c, 0x11, 0x82, 0x58, 0xe3, 0x80, 0x05, 0x96, 0xe8, 0xa8, 0xb0, 0x79, 0x89, 0x11, 0x7f,
+ 0xa2, 0x85, 0x77, 0xf1, 0x91, 0x05, 0x6e, 0xe6, 0x11, 0x82, 0xe1,
+}
+
+var EditorPieChartOutlined = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0x6a,
+ 0x58, 0x58, 0x6a, 0x58, 0x80, 0x91, 0x92, 0xa8, 0xa8, 0xa8, 0xa8, 0x6e, 0xa8, 0x58, 0x80, 0x96,
+ 0x58, 0x80, 0x58, 0xe3, 0x84, 0x21, 0x84, 0xb0, 0x35, 0x87, 0xe9, 0x80, 0xf9, 0x8c, 0xa9, 0x86,
+ 0xe1, 0x8d, 0xe1, 0x8d, 0xe6, 0x84, 0xe8, 0x21, 0x70, 0xe2, 0x60, 0x80, 0xb0, 0x80, 0xe1, 0x77,
+ 0x21, 0x86, 0x21, 0x71, 0x9c, 0x21, 0x70, 0xe9, 0xbd, 0x9f, 0xa0, 0x21, 0x76, 0xe1, 0x8e, 0x60,
+ 0x21, 0x88, 0x60, 0x80, 0xe3, 0xa4, 0xe1, 0x8f, 0xe8, 0x84, 0xe7, 0xe1, 0x8d, 0xb0, 0x19, 0x7f,
+ 0x35, 0x87, 0x59, 0x79, 0xf9, 0x8c, 0x21, 0x72, 0xe1, 0x8d, 0xe1,
+}
+
+var EditorPublish = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x60, 0xe9, 0x88,
+ 0xe7, 0xb8, 0xe8, 0x60, 0xe6, 0x64, 0xe3, 0x80, 0xa8, 0xe7, 0x90, 0xe9, 0x98, 0xe7, 0x98, 0xe8,
+ 0x88, 0xe7, 0x90, 0x01, 0x80, 0x6c, 0x64, 0x88, 0xe1,
+}
+
+var EditorShortText = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x74, 0xe7, 0xc0,
+ 0xe9, 0x88, 0xe6, 0x60, 0xe3, 0x80, 0x90, 0xe7, 0xa8, 0xe9, 0x88, 0xe6, 0x60, 0xe1,
+}
+
+var EditorShowChart = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5e, 0xfd, 0x8c, 0x23,
+ 0x98, 0xf9, 0x73, 0x90, 0x90, 0xa2, 0xe1, 0x6c, 0x31, 0x7d, 0x2d, 0x7d, 0x00, 0x86, 0xf5, 0x82,
+ 0x20, 0x70, 0x70, 0x00, 0x58, 0xfd, 0x89, 0xe1,
+}
+
+var EditorSpaceBar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x74, 0xe9, 0x90,
+ 0xe6, 0x68, 0xe9, 0x70, 0xe6, 0x60, 0xe9, 0x98, 0xe7, 0xc0, 0xe8, 0x74, 0xe1,
+}
+
+var EditorStrikethroughS = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x79, 0x76, 0x7d, 0x79,
+ 0xb2, 0x7d, 0x7f, 0x0d, 0x7f, 0x3d, 0x7f, 0xf1, 0x7d, 0x3d, 0x7f, 0xad, 0x7c, 0x80, 0xc9, 0x7e,
+ 0x45, 0x80, 0xad, 0x7d, 0xcd, 0x80, 0xa9, 0x7c, 0x89, 0x80, 0xfd, 0x7e, 0x45, 0x81, 0x21, 0x7e,
+ 0x3d, 0x82, 0x6d, 0x7d, 0x90, 0x19, 0x82, 0xbd, 0x7e, 0x69, 0x83, 0x59, 0x7e, 0xa0, 0x75, 0x7d,
+ 0x35, 0x6e, 0xe5, 0x7e, 0x5c, 0x79, 0x80, 0x5c, 0xb3, 0xa1, 0x81, 0x80, 0x19, 0x83, 0x39, 0x80,
+ 0x69, 0x84, 0xad, 0x80, 0x51, 0x81, 0x75, 0x80, 0x75, 0x82, 0x11, 0x81, 0x65, 0x83, 0xe1, 0x81,
+ 0xf1, 0x80, 0xcd, 0x80, 0xa9, 0x81, 0xc1, 0x81, 0x2d, 0x82, 0xdd, 0x82, 0x85, 0x80, 0x19, 0x81,
+ 0xc5, 0x80, 0x51, 0x82, 0xc5, 0x80, 0xa1, 0x83, 0xe7, 0xfd, 0x79, 0xb9, 0x80, 0x65, 0x7f, 0xe9,
+ 0x7f, 0xd1, 0x7e, 0xb5, 0x7f, 0x4d, 0x7e, 0xd1, 0x7f, 0x79, 0x7f, 0x85, 0x7f, 0x05, 0x7f, 0x1d,
+ 0x7f, 0xa9, 0x7e, 0x99, 0x7f, 0xa1, 0x7f, 0x19, 0x7f, 0x55, 0x7f, 0x81, 0x7e, 0x21, 0x7f, 0x69,
+ 0x7f, 0xcd, 0x7f, 0xb1, 0x7e, 0xb1, 0x7f, 0xe1, 0x7d, 0xb1, 0x7f, 0x35, 0x7f, 0x80, 0x89, 0x7e,
+ 0x19, 0x80, 0xf1, 0x7d, 0x45, 0x80, 0x6d, 0x7f, 0x2d, 0x80, 0xf1, 0x7e, 0x69, 0x80, 0x91, 0x7e,
+ 0xb9, 0x80, 0xa1, 0x7f, 0x51, 0x80, 0x55, 0x7f, 0xad, 0x80, 0x25, 0x7f, 0x15, 0x81, 0xd1, 0x7f,
+ 0x69, 0x80, 0xb5, 0x7f, 0xdd, 0x80, 0xb5, 0x7f, 0x51, 0x81, 0x80, 0xf5, 0x80, 0x7d, 0x80, 0xc5,
+ 0x81, 0x79, 0x81, 0x6d, 0x82, 0xc1, 0x80, 0x7d, 0x80, 0x89, 0x81, 0xf5, 0x80, 0xd1, 0x82, 0x69,
+ 0x81, 0xe7, 0xc9, 0x76, 0xb0, 0xe9, 0x7f, 0xd5, 0x7f, 0xcd, 0x7f, 0xad, 0x7f, 0xb1, 0x7f, 0x7d,
+ 0x7f, 0xe2, 0xa4, 0x80, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x41, 0x93, 0xbb, 0x5d, 0x80,
+ 0x25, 0x80, 0xcd, 0x80, 0x49, 0x80, 0x19, 0x81, 0x69, 0x80, 0xbd, 0x80, 0x55, 0x80, 0x51, 0x81,
+ 0xb1, 0x80, 0xbd, 0x81, 0x05, 0x81, 0x6d, 0x80, 0x59, 0x80, 0xb5, 0x80, 0xbd, 0x80, 0xd9, 0x80,
+ 0x21, 0x81, 0x29, 0x80, 0x69, 0x80, 0x39, 0x80, 0xe1, 0x80, 0x39, 0x80, 0x61, 0x81, 0x80, 0x79,
+ 0x80, 0xe9, 0x7f, 0xe9, 0x80, 0xbd, 0x7f, 0x51, 0x81, 0xd1, 0x7f, 0x69, 0x80, 0x8d, 0x7f, 0xc5,
+ 0x80, 0x2d, 0x7f, 0x0d, 0x81, 0xa1, 0x7f, 0x4d, 0x80, 0x29, 0x7f, 0x85, 0x80, 0x95, 0x7e, 0xb1,
+ 0x80, 0x6d, 0x7f, 0x2d, 0x80, 0xc1, 0x7e, 0x41, 0x80, 0xf9, 0x7d, 0x41, 0x80, 0x21, 0x7f, 0x80,
+ 0x59, 0x7e, 0xe9, 0x7f, 0xa1, 0x7d, 0xbd, 0x7f, 0x4d, 0x7f, 0xd5, 0x7f, 0xb1, 0x7e, 0x8d, 0x7f,
+ 0x31, 0x7e, 0x2d, 0x7f, 0x81, 0x7f, 0xa1, 0x7f, 0x1d, 0x7f, 0x21, 0x7f, 0xd5, 0x7e, 0x85, 0x7e,
+ 0xb9, 0x7f, 0x65, 0x7f, 0x7d, 0x7f, 0x79, 0x7e, 0x7d, 0x7f, 0x99, 0x7d, 0xe6, 0xcd, 0x74, 0xb1,
+ 0x80, 0x19, 0x81, 0x29, 0x80, 0x45, 0x82, 0x79, 0x80, 0x29, 0x83, 0x51, 0x80, 0xe9, 0x80, 0xc1,
+ 0x80, 0xb5, 0x81, 0x4d, 0x81, 0x6d, 0x82, 0x91, 0x35, 0x81, 0x51, 0x81, 0xf5, 0x81, 0xd9, 0x81,
+ 0x91, 0x81, 0xf5, 0x80, 0x71, 0x82, 0x4d, 0x81, 0xb2, 0xe1, 0x80, 0x59, 0x80, 0xcd, 0x81, 0x9d,
+ 0x80, 0xc5, 0x82, 0xc5, 0x80, 0xf5, 0x80, 0x2d, 0x80, 0xf1, 0x81, 0x41, 0x80, 0xe5, 0x82, 0x41,
+ 0x80, 0x99, 0x81, 0x80, 0x11, 0x83, 0xd1, 0x7f, 0x5d, 0x84, 0x75, 0x7f, 0x90, 0x6d, 0x82, 0x19,
+ 0x7f, 0x59, 0x83, 0x6d, 0x7e, 0xb0, 0xed, 0x80, 0x51, 0x7f, 0xa1, 0x81, 0x79, 0x7e, 0x25, 0x82,
+ 0x75, 0x7d, 0x90, 0xc1, 0x80, 0xd9, 0x7d, 0xc1, 0x80, 0x91, 0x7c, 0xb1, 0x80, 0xcd, 0x7e, 0xcd,
+ 0x7f, 0xb9, 0x7d, 0x61, 0x7f, 0xc5, 0x7c, 0xe9, 0x7f, 0xc5, 0x7f, 0xcd, 0x7f, 0x8d, 0x7f, 0xa9,
+ 0x7f, 0x55, 0x7f, 0xe6, 0xa4, 0xe1,
+}
+
+var EditorTextFields = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5a, 0x60, 0xe9, 0x8c,
+ 0xe7, 0x94, 0xe9, 0xb0, 0xe7, 0x8c, 0xe8, 0x6c, 0xe7, 0x94, 0xe8, 0x60, 0xe6, 0x5a, 0xe3, 0xcc,
+ 0x94, 0xe6, 0x82, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x9c, 0xe7, 0x8c, 0xe8, 0x80, 0xe7, 0x8c, 0xe9,
+ 0x74, 0xe1,
+}
+
+var EditorTitle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x60, 0xe9, 0x8c,
+ 0xe7, 0x96, 0xe9, 0xb0, 0xe7, 0x8c, 0xe8, 0x6c, 0xe7, 0x96, 0xe8, 0x60, 0xe1,
+}
+
+var EditorVerticalAlignBottom = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x84, 0xe7, 0x74,
+ 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0xa8, 0xe7, 0x74, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe2, 0x60, 0x9c,
+ 0xe9, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x60, 0xe1,
+}
+
+var EditorVerticalAlignCenter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x9c, 0xe7, 0x8c,
+ 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x8c, 0x21, 0x70, 0x70, 0x70, 0x90, 0xe3, 0xa0, 0x48,
+ 0xe7, 0x74, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x90, 0xe7, 0x74, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe2,
+ 0x60, 0x7c, 0xe9, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x60, 0xe1,
+}
+
+var EditorVerticalAlignTop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x7c, 0xe7, 0x8c,
+ 0xe9, 0xa8, 0xe7, 0x88, 0xe8, 0x7c, 0xe7, 0x8c, 0x21, 0x70, 0x70, 0x70, 0x90, 0xe2, 0x60, 0x5c,
+ 0xe9, 0x88, 0xe7, 0xc0, 0xe8, 0x5c, 0xe6, 0x60, 0xe1,
+}
+
+var EditorWrapText = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x9c, 0xe7, 0x98,
+ 0xe9, 0x78, 0xe6, 0x60, 0xe9, 0x88, 0xe3, 0xc0, 0x48, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0xc0, 0xe9,
+ 0x78, 0xe3, 0x74, 0x98, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0x81, 0x9a, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0xcd, 0x81, 0x88, 0x88, 0x90, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe6, 0x8c, 0xe9, 0x78, 0x21, 0x74,
+ 0x8c, 0x8c, 0x8c, 0xe9, 0x78, 0xe7, 0x88, 0xb0, 0x69, 0x84, 0x80, 0x90, 0x69, 0x7c, 0x90, 0x70,
+ 0x90, 0x69, 0x7c, 0x70, 0x70, 0x70, 0xe1,
+}
+
+var FileAttachment = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6e, 0x98, 0xa0, 0xed,
+ 0x70, 0x98, 0x58, 0x11, 0x87, 0x58, 0x82, 0x90, 0xed, 0x84, 0x6a, 0x96, 0x6a, 0xe7, 0xaa, 0xb0,
+ 0x6d, 0x84, 0x80, 0x90, 0x95, 0x83, 0x90, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70, 0x90, 0xe6, 0x76,
+ 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x90, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0xe7,
+ 0x9e, 0xe9, 0x86, 0xe6, 0x76, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x90, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0xa2, 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x90,
+ 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe6, 0x6e, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90,
+ 0x90, 0x95, 0x83, 0x90, 0x90, 0x90, 0xe7, 0xa6, 0xe9, 0x86, 0xe6, 0x6e, 0xe1,
+}
+
+var FileCloud = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe1,
+}
+
+var FileCloudCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x92, 0xb8, 0xe6, 0x70, 0xb0, 0xb1, 0x7c,
+ 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x90, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x20, 0x45, 0x80, 0x09,
+ 0x80, 0xb1, 0xe5, 0x80, 0x8d, 0x7c, 0x88, 0xf9, 0x79, 0xbd, 0x87, 0xf9, 0x79, 0x6d, 0x84, 0x80,
+ 0x90, 0x95, 0x83, 0x90, 0x90, 0xe7, 0x82, 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0x3d, 0x82, 0x8a, 0x8a,
+ 0x90, 0xc5, 0x7d, 0x8a, 0x76, 0x8a, 0xe1,
+}
+
+var FileCloudDone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe2,
+ 0x78, 0x94, 0x21, 0x72, 0x72, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x78, 0x59, 0x84, 0x21, 0x59, 0x8a,
+ 0xa9, 0x75, 0xd5, 0x82, 0xd5, 0x82, 0x00, 0x78, 0x94, 0xe1,
+}
+
+var FileCloudDownload = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe2,
+ 0x94, 0x84, 0x01, 0x80, 0x98, 0x6c, 0x84, 0xe7, 0x8c, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe7,
+ 0x8c, 0xe1,
+}
+
+var FileCloudOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x0d, 0x7d, 0x80, 0x4d, 0x7a,
+ 0xe1, 0x80, 0xfd, 0x77, 0x59, 0x82, 0x20, 0xed, 0x82, 0xed, 0x82, 0xa0, 0x71, 0x7c, 0x79, 0x74,
+ 0x29, 0x7e, 0x68, 0x80, 0x68, 0xb0, 0x15, 0x86, 0x80, 0x96, 0xed, 0x84, 0x96, 0x96, 0xe9, 0x82,
+ 0xe7, 0x86, 0xb1, 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x80, 0x45, 0x82, 0xbd, 0x7e,
+ 0x39, 0x84, 0xe1, 0x7c, 0x3d, 0x85, 0x20, 0xe9, 0x82, 0xe9, 0x82, 0xa0, 0x51, 0x96, 0x55, 0x8c,
+ 0xb0, 0x61, 0x89, 0xb0, 0x8c, 0xb0, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11,
+ 0x76, 0xe2, 0x5c, 0x8d, 0x72, 0x20, 0x81, 0x85, 0x7d, 0x85, 0xa0, 0x21, 0x6d, 0x4d, 0x78, 0x50,
+ 0x8d, 0x7d, 0x50, 0x88, 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0x75, 0x97,
+ 0x20, 0x88, 0x88, 0x02, 0xa4, 0x75, 0x91, 0x8d, 0x70, 0x60, 0x5c, 0x8d, 0x72, 0xe2, 0x75, 0x77,
+ 0x78, 0x20, 0xa0, 0xa0, 0xe6, 0x68, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x90,
+ 0x95, 0x83, 0x70, 0x90, 0x70, 0xe7, 0x75, 0x83, 0xe1,
+}
+
+var FileCloudQueue = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe2,
+ 0x9c, 0x98, 0xe6, 0x68, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x90, 0x95, 0x83,
+ 0x70, 0x90, 0x70, 0xe7, 0x6d, 0x81, 0xb1, 0x51, 0x81, 0x65, 0x7b, 0x8d, 0x85, 0x70, 0x95, 0x8a,
+ 0x70, 0x15, 0x86, 0x80, 0x96, 0xed, 0x84, 0x96, 0x96, 0xe9, 0x82, 0xe7, 0x86, 0xb0, 0x51, 0x83,
+ 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x90, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0xe1,
+}
+
+var FileCloudUpload = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe2,
+ 0x88, 0x84, 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0x74, 0x21, 0x94, 0x6c, 0x94, 0x94, 0xe7,
+ 0x74, 0xe1,
+}
+
+var FileCreateNewFolder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe6, 0x80,
+ 0x20, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x7c, 0xa0, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x74, 0xe9,
+ 0x78, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe1,
+}
+
+var FileFileDownload = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x74, 0xe7, 0x70,
+ 0xe8, 0x5c, 0xe6, 0x74, 0xe9, 0x98, 0xe7, 0x70, 0x21, 0x9c, 0x9c, 0x9c, 0x64, 0xe2, 0x64, 0x98,
+ 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x64, 0xe1,
+}
+
+var FileFileUpload = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x90, 0xe7, 0x98,
+ 0xe8, 0x78, 0xe7, 0x90, 0x01, 0x80, 0x5c, 0x64, 0x78, 0xe7, 0x90, 0xe3, 0x70, 0x88, 0xe7, 0xb8,
+ 0xe9, 0x88, 0xe6, 0x64, 0xe1,
+}
+
+var FileFolder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x80, 0x20,
+ 0x78, 0x78, 0xe1,
+}
+
+var FileFolderOpen = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe6, 0x80,
+ 0x20, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xb0, 0xe6, 0x60, 0xe8, 0x70, 0xe7, 0xc0, 0xe9, 0xa8, 0xe1,
+}
+
+var FileFolderShared = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe6, 0x80,
+ 0x20, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88,
+ 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x6c, 0x8c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x92, 0x35,
+ 0x7e, 0x88, 0x78, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe3, 0x90,
+ 0xa0, 0xe6, 0x7c, 0xe9, 0x7c, 0xb0, 0x80, 0x55, 0x7d, 0x55, 0x85, 0x78, 0x90, 0x78, 0x90, 0x90,
+ 0x55, 0x81, 0x90, 0x88, 0xe9, 0x84, 0xe1,
+}
+
+var HardwareCast = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x74, 0xe7,
+ 0xc8, 0xe9, 0xb8, 0xe6, 0x88, 0xe9, 0x88, 0xe7, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x54, 0x98,
+ 0xe9, 0x8c, 0xe7, 0x8c, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe3, 0x80, 0x70,
+ 0xe9, 0x88, 0xb0, 0x85, 0x85, 0x80, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xe7, 0x88, 0xb0, 0x80, 0x45,
+ 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0xe3, 0x80, 0x70, 0xe9, 0x88, 0xb0, 0xf1, 0x89, 0x80, 0xa4,
+ 0x11, 0x88, 0xa4, 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0xd9, 0x73, 0x29, 0x76, 0x54, 0x54, 0x54, 0xe1,
+}
+
+var HardwareCastConnected = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0x98, 0xe9, 0x8c,
+ 0xe7, 0x8c, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe3, 0x80, 0x70, 0xe9, 0x88,
+ 0xb0, 0x85, 0x85, 0x80, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xe7, 0x88, 0xb0, 0x80, 0x45, 0x78, 0xbd,
+ 0x79, 0x64, 0x64, 0x64, 0xe3, 0xc8, 0x64, 0xe6, 0x64, 0xe9, 0x45, 0x83, 0xb0, 0xed, 0x87, 0x91,
+ 0x82, 0x2d, 0x8e, 0xd1, 0x88, 0xbd, 0x90, 0xbd, 0x90, 0xe6, 0x9c, 0xe8, 0x6c, 0xe2, 0x54, 0x78,
+ 0xe9, 0x88, 0xb0, 0xf1, 0x89, 0x80, 0xa4, 0x11, 0x88, 0xa4, 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0xd9,
+ 0x73, 0x29, 0x76, 0x54, 0x54, 0x54, 0xe2, 0xa4, 0x5c, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0xc8, 0xe9, 0xb8, 0xe6, 0x88,
+ 0xe9, 0x88, 0xe7, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var HardwareComputer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x98, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xa8, 0x68, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88,
+ 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe6, 0x50, 0xe9, 0x88, 0xe7,
+ 0xe0, 0xe9, 0x78, 0xe7, 0x70, 0xe2, 0x60, 0x68, 0xe7, 0xc0, 0xe9, 0xa8, 0xe6, 0x60, 0xe8, 0x68,
+ 0xe1,
+}
+
+var HardwareDesktopMac = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x58, 0xe6, 0x5c,
+ 0xa0, 0xcd, 0x6b, 0x58, 0x54, 0xcd, 0x6d, 0x54, 0x60, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x9c, 0x20, 0x78, 0x8c, 0xe9, 0x84, 0xe7, 0xa0, 0xe9, 0x7c, 0x20,
+ 0x78, 0x74, 0xe7, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb0, 0xe6, 0x5c, 0xe8, 0x60, 0xe7,
+ 0xc8, 0xe9, 0xa8, 0xe1,
+}
+
+var HardwareDesktopWindows = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x58, 0xe6, 0x5c,
+ 0xa0, 0xcd, 0x6b, 0x58, 0x54, 0xcd, 0x6d, 0x54, 0x60, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x9c, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x9c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8,
+ 0x60, 0xe7, 0xc8, 0xe9, 0xb0, 0xe1,
+}
+
+var HardwareDeveloperBoard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x74, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x60, 0xb0,
+ 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe3, 0x70, 0xa8, 0xe6, 0x60, 0xe8, 0x64, 0xe7, 0xb8, 0xe9, 0xb8, 0xe2, 0x68, 0x84,
+ 0xe7, 0x94, 0xe9, 0x90, 0xe6, 0x68, 0xe3, 0x98, 0x68, 0xe7, 0x90, 0xe9, 0x8c, 0xe7, 0x70, 0xe3,
+ 0x68, 0x80, 0xe7, 0x94, 0xe9, 0x94, 0xe6, 0x68, 0xe3, 0x98, 0x90, 0xe7, 0x90, 0xe9, 0x98, 0xe7,
+ 0x70, 0xe1,
+}
+
+var HardwareDeviceHub = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x90, 0x20, 0x70,
+ 0x70, 0xe8, 0xa5, 0x79, 0xa0, 0x51, 0x84, 0xcd, 0x78, 0x8c, 0x99, 0x76, 0x8c, 0x68, 0xb0, 0x80,
+ 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x80, 0x74, 0xb1, 0x70, 0x74, 0x68, 0xb0, 0x80, 0x99,
+ 0x82, 0xb1, 0x81, 0xcd, 0x84, 0x88, 0xa5, 0x85, 0xe8, 0x80, 0x20, 0x70, 0x90, 0xe6, 0x5c, 0xe9,
+ 0x94, 0xe7, 0x94, 0xe9, 0xe9, 0x79, 0x21, 0x90, 0x99, 0x77, 0x90, 0x69, 0x88, 0xe8, 0xa4, 0xe7,
+ 0x94, 0xe9, 0x6c, 0xe7, 0x70, 0xe1,
+}
+
+var HardwareDevicesOther = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x68, 0xe7, 0xc8,
+ 0xe8, 0x60, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe6, 0x5c, 0xe8, 0x68,
+ 0xe3, 0xa8, 0x98, 0xe7, 0x70, 0xe9, 0x91, 0x83, 0xb0, 0xc9, 0x7e, 0x19, 0x81, 0x7c, 0xad, 0x82,
+ 0x7c, 0x71, 0x84, 0x90, 0xc9, 0x80, 0x59, 0x83, 0x84, 0x71, 0x84, 0xe8, 0xa0, 0xe7, 0x90, 0xe9,
+ 0x71, 0x7c, 0xb0, 0x39, 0x81, 0xe9, 0x7e, 0x84, 0x55, 0x7d, 0x84, 0x91, 0x7b, 0x90, 0x39, 0x7f,
+ 0xa9, 0x7c, 0x7c, 0x91, 0x7b, 0xe8, 0x80, 0xe3, 0x78, 0x96, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9,
+ 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0x86, 0xe3, 0xac, 0x5a, 0xe6, 0x90, 0xb0, 0x7e, 0x80, 0x7c, 0x82, 0x7c, 0x84, 0xe9,
+ 0xa8, 0xb0, 0x80, 0x82, 0x82, 0x84, 0x84, 0x84, 0xe7, 0x98, 0xb0, 0x82, 0x80, 0x84, 0x7e, 0x84,
+ 0x7c, 0xe8, 0x74, 0xb0, 0x80, 0x7e, 0x7e, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0xa8, 0xe7, 0x70, 0xe8,
+ 0x78, 0xe7, 0x90, 0xe9, 0xa0, 0xe1,
+}
+
+var HardwareDock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0xac, 0xe7, 0xa0,
+ 0xe9, 0x78, 0xe6, 0x70, 0xe9, 0x88, 0xe2, 0x90, 0x05, 0x6a, 0x00, 0x70, 0x54, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe2, 0x90, 0x8c, 0xe6, 0x70, 0xe8, 0x64,
+ 0xe7, 0xa0, 0xe9, 0xa8, 0xe1,
+}
+
+var HardwareGamepad = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x6e, 0xe8, 0x58,
+ 0xe6, 0x74, 0xe9, 0x96, 0x21, 0x8c, 0x8c, 0x8c, 0x74, 0xe3, 0x62, 0x86, 0xe6, 0x58, 0xe9, 0x98,
+ 0xe7, 0x96, 0x21, 0x8c, 0x74, 0x74, 0x74, 0xe3, 0x86, 0x9e, 0xe9, 0x96, 0xe7, 0x98, 0xe8, 0x92,
+ 0x21, 0x74, 0x74, 0x74, 0x8c, 0xe3, 0x9e, 0x62, 0x21, 0x74, 0x8c, 0x8c, 0x8c, 0xe7, 0x96, 0xe8,
+ 0x74, 0xe6, 0x92, 0xe1,
+}
+
+var HardwareHeadset = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x54, 0xa0, 0x11,
+ 0x76, 0x54, 0x5c, 0x11, 0x72, 0x5c, 0x78, 0xe9, 0x9c, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c,
+ 0x8c, 0x8c, 0xe7, 0x8c, 0xe8, 0x80, 0xe7, 0x70, 0xe9, 0x78, 0xb0, 0x80, 0x45, 0x78, 0x45, 0x86,
+ 0x64, 0x9c, 0x64, 0x90, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0xa0, 0xe7,
+ 0x8c, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe8, 0x78, 0xb0, 0x80, 0x11, 0x76,
+ 0xf1, 0x77, 0x5c, 0x5c, 0x5c, 0xe1,
+}
+
+var HardwareHeadsetMic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x54, 0xa0, 0x11,
+ 0x76, 0x54, 0x5c, 0x11, 0x72, 0x5c, 0x78, 0xe9, 0x9c, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c,
+ 0x8c, 0x8c, 0xe7, 0x8c, 0xe8, 0x80, 0xe7, 0x70, 0xe9, 0x78, 0xb0, 0x80, 0x45, 0x78, 0x45, 0x86,
+ 0x64, 0x9c, 0x64, 0x90, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0xa0, 0xe7,
+ 0x90, 0xe9, 0x84, 0xe6, 0x80, 0xe9, 0x88, 0xe7, 0x98, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d,
+ 0x8c, 0x74, 0xe8, 0x78, 0xb0, 0x80, 0x11, 0x76, 0xf1, 0x77, 0x5c, 0x5c, 0x5c, 0xe1,
+}
+
+var HardwareKeyboard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x64, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x94, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x5c, 0x8c,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80, 0x8c, 0xe7, 0x88, 0xe9, 0x88, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe3, 0x74, 0x74, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80,
+ 0x8c, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x7c, 0x88, 0xe7, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3,
+ 0xa4, 0x9c, 0xe6, 0x70, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x8c, 0x8c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78,
+ 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var HardwareKeyboardArrowDown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd5, 0x76, 0x6d, 0x78,
+ 0x00, 0x80, 0x99, 0x81, 0x20, 0x2d, 0x89, 0xd5, 0x76, 0x00, 0x98, 0x41, 0x7b, 0x21, 0x68, 0x98,
+ 0x68, 0x68, 0xe1,
+}
+
+var HardwareKeyboardArrowLeft = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd5, 0x86, 0xad, 0x88,
+ 0x21, 0xd5, 0x76, 0xd5, 0x76, 0x2d, 0x89, 0xd5, 0x76, 0x00, 0x88, 0x81, 0x73, 0x21, 0x68, 0x98,
+ 0x98, 0x98, 0xe1,
+}
+
+var HardwareKeyboardArrowRight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x79, 0xed, 0x88,
+ 0x21, 0x2d, 0x89, 0xd5, 0x76, 0xd5, 0x76, 0xd5, 0x76, 0x00, 0x78, 0xc1, 0x73, 0x21, 0x98, 0x98,
+ 0x68, 0x98, 0xe1,
+}
+
+var HardwareKeyboardArrowUp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd5, 0x76, 0xd5, 0x86,
+ 0x00, 0x80, 0xa9, 0x7d, 0x20, 0x2d, 0x89, 0x2d, 0x89, 0x02, 0x98, 0x88, 0x80, 0x70, 0x68, 0x88,
+ 0xe1,
+}
+
+var HardwareKeyboardBackspace = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x7c, 0xe6, 0xa9,
+ 0x75, 0x20, 0x2d, 0x87, 0xd5, 0x78, 0x01, 0x74, 0x68, 0x5c, 0x80, 0x21, 0x98, 0x98, 0xd5, 0x82,
+ 0x2d, 0x7d, 0x00, 0xa9, 0x75, 0x84, 0xe6, 0xa4, 0xe1,
+}
+
+var HardwareKeyboardCapslock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xd5, 0x78, 0x01,
+ 0x2d, 0x89, 0x84, 0x98, 0x2d, 0x7f, 0x21, 0x68, 0x68, 0x68, 0x98, 0x01, 0xd5, 0x76, 0x84, 0x80,
+ 0xd5, 0x78, 0xe2, 0x68, 0x98, 0xe7, 0xb0, 0xe9, 0x78, 0xe6, 0x68, 0xe9, 0x88, 0xe1,
+}
+
+var HardwareKeyboardHide = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x5c, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x8c, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x5c, 0x8c,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80, 0x8c, 0xe7, 0x88, 0xe9, 0x88, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe3, 0x74, 0x74, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80,
+ 0x8c, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x7c, 0x88, 0xe7, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3,
+ 0xa4, 0x9c, 0xe6, 0x70, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x8c, 0x8c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x74, 0xe7, 0x78,
+ 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe2, 0x80, 0xac, 0x20, 0x90, 0x70, 0xe6, 0x70, 0x20, 0x90,
+ 0x90, 0xe1,
+}
+
+var HardwareKeyboardReturn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x6c, 0xe9, 0x90,
+ 0xe6, 0xa9, 0x73, 0x20, 0x2d, 0x87, 0xd5, 0x78, 0x01, 0x70, 0x68, 0x58, 0x80, 0x21, 0x98, 0x98,
+ 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0xa9, 0x73, 0x84, 0xe6, 0xa4, 0xe8, 0x6c, 0xe1,
+}
+
+var HardwareKeyboardTab = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x7f, 0xd5, 0x76,
+ 0x00, 0x59, 0x86, 0x7c, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x59, 0x9c, 0x20, 0xd5, 0x78, 0x2d, 0x87,
+ 0x00, 0x84, 0x98, 0x22, 0x98, 0x68, 0x68, 0x68, 0x2d, 0x7d, 0xd5, 0x82, 0xe2, 0xa0, 0x68, 0xe9,
+ 0xb0, 0xe7, 0x88, 0xe8, 0x68, 0xe7, 0x78, 0xe1,
+}
+
+var HardwareKeyboardVoice = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x8c, 0xb0, 0x51,
+ 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x00, 0x8c, 0x68, 0xb1, 0x80, 0xb1, 0x7c,
+ 0x51, 0x7d, 0x74, 0x74, 0x74, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x98, 0xb0,
+ 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3, 0x99, 0x8a, 0x74, 0xb1, 0x80, 0x8c, 0xf1,
+ 0x7a, 0x35, 0x8a, 0x69, 0x75, 0x35, 0x8a, 0x7d, 0x7a, 0x80, 0x69, 0x75, 0xcd, 0x7b, 0x69, 0x75,
+ 0xcd, 0x75, 0xe6, 0x64, 0xb0, 0x80, 0xd5, 0x86, 0x71, 0x85, 0x79, 0x8c, 0x98, 0x71, 0x8d, 0xe8,
+ 0xa8, 0xe7, 0x88, 0xe9, 0x71, 0x79, 0xb0, 0x91, 0x86, 0x09, 0x7f, 0x98, 0x65, 0x79, 0x98, 0x91,
+ 0x72, 0xe7, 0x99, 0x7c, 0xe1,
+}
+
+var HardwareLaptop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x98, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe6, 0x50, 0xe9, 0x88, 0xe7, 0xe0, 0xe9, 0x78,
+ 0xe7, 0x70, 0xe2, 0x60, 0x68, 0xe7, 0xc0, 0xe9, 0xa8, 0xe6, 0x60, 0xe8, 0x68, 0xe1,
+}
+
+var HardwareLaptopChromebook = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x98, 0xe8, 0x5c,
+ 0xe6, 0x58, 0xe9, 0xbc, 0xe6, 0x50, 0xe9, 0x88, 0xe7, 0xe0, 0xe9, 0x78, 0xe7, 0x78, 0xe3, 0x60,
+ 0x80, 0xe7, 0x70, 0xe9, 0x7c, 0xe7, 0x90, 0xe9, 0x84, 0xe3, 0x98, 0x74, 0xe6, 0x60, 0xe8, 0x64,
+ 0xe7, 0xc0, 0xe9, 0xa8, 0xe1,
+}
+
+var HardwareLaptopMac = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x98, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xa8, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88,
+ 0xe9, 0xac, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe6, 0x50, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xd0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe7, 0x70, 0xe2, 0x60, 0x64, 0xe7, 0xc0, 0xe9, 0xac, 0xe6, 0x60, 0xe8, 0x64, 0xe3, 0xa0,
+ 0xb8, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x92, 0xe9, 0x80, 0x7c, 0x84, 0x7c,
+ 0x84, 0xe9, 0x80, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe1,
+}
+
+var HardwareLaptopWindows = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x98, 0xe9, 0x7c,
+ 0xb0, 0x35, 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xa8, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe9, 0x84, 0xe6,
+ 0x50, 0xe9, 0x88, 0xe7, 0xe0, 0xe9, 0x78, 0xe7, 0x70, 0xe2, 0x60, 0x64, 0xe7, 0xc0, 0xe9, 0xa8,
+ 0xe6, 0x60, 0xe8, 0x64, 0xe1,
+}
+
+var HardwareMemory = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x74, 0xe6, 0x74,
+ 0xe9, 0x98, 0xe7, 0x98, 0xe8, 0x74, 0xe3, 0x78, 0x90, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe3, 0xa0, 0x78, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe7, 0x78, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x78, 0xe8, 0x5c, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x78, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x88,
+ 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x88,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe3, 0x70, 0x98, 0xe6, 0x6c, 0xe8, 0x6c, 0xe7, 0xa8, 0xe9, 0xa8, 0xe1,
+}
+
+var HardwareMouse = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x25, 0x6a, 0xe8,
+ 0x74, 0xe7, 0x9c, 0xb0, 0x80, 0xd9, 0x77, 0xe5, 0x79, 0x21, 0x71, 0x64, 0x25, 0x70, 0xe2, 0x60,
+ 0x8c, 0xb0, 0x80, 0xd9, 0x88, 0x29, 0x87, 0xa0, 0xa0, 0xa0, 0x90, 0xa0, 0xd9, 0x78, 0xa0, 0x60,
+ 0xe9, 0x70, 0xe6, 0x60, 0xe9, 0x90, 0xe2, 0x7c, 0x25, 0x6a, 0xa0, 0x1d, 0x76, 0x21, 0x6b, 0x60,
+ 0xd9, 0x71, 0x60, 0x74, 0xe7, 0x9c, 0xe8, 0x25, 0x6a, 0xe1,
+}
+
+var HardwarePhoneAndroid = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x54, 0xe6, 0x70,
+ 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0xc0, 0xb0, 0x80, 0x51, 0x83, 0xb1,
+ 0x82, 0x8c, 0x8c, 0x8c, 0xe7, 0xa0, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe8,
+ 0x60, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe3, 0x78, 0xd0, 0xe7, 0x70, 0xe9,
+ 0x7c, 0xe7, 0x90, 0xe9, 0x84, 0xe3, 0x81, 0x86, 0x74, 0xe7, 0x56, 0xe8, 0x60, 0xe7, 0xaa, 0xe9,
+ 0xb8, 0xe1,
+}
+
+var HardwarePhoneIPhone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x54, 0xe6, 0x6e,
+ 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0xe9, 0xc4, 0xb0, 0x80, 0xc5, 0x82, 0x3d,
+ 0x82, 0x8a, 0x8a, 0x8a, 0xe7, 0xa0, 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xe8,
+ 0x5e, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe3, 0x70, 0xd4, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x92, 0x70, 0xe6, 0x6c, 0xe8, 0x60, 0xe7, 0xa4, 0xe9,
+ 0xb8, 0xe1,
+}
+
+var HardwarePhoneLink = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe7, 0xc8,
+ 0xe8, 0x60, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xac, 0xe6,
+ 0x50, 0xe9, 0x8c, 0xe7, 0xb8, 0xe9, 0x74, 0xe6, 0x60, 0xe8, 0x68, 0xe3, 0xcc, 0x88, 0xe6, 0x94,
+ 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xa8, 0xb0, 0x80, 0x19, 0x81, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0x98, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8,
+ 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0xa4, 0xe7, 0x70, 0xe8,
+ 0x78, 0xe7, 0x90, 0xe9, 0x9c, 0xe1,
+}
+
+var HardwarePhoneLinkOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x68, 0xe8, 0x60,
+ 0xe6, 0xa5, 0x75, 0x20, 0x88, 0x88, 0xe6, 0xa8, 0xe2, 0xd9, 0x6b, 0x4d, 0x6b, 0x00, 0x4d, 0x69,
+ 0xd9, 0x6d, 0x20, 0xa1, 0x83, 0xa1, 0x83, 0xa0, 0x5d, 0x6c, 0x29, 0x72, 0x58, 0x0d, 0x73, 0x58,
+ 0x68, 0xe9, 0xac, 0xe6, 0x50, 0xe9, 0x8c, 0xe7, 0x75, 0xa3, 0x21, 0xb5, 0x84, 0xb5, 0x84, 0x8d,
+ 0x82, 0x75, 0x7d, 0x01, 0xcd, 0x6f, 0x3d, 0x6f, 0xd9, 0x6b, 0x4d, 0x6b, 0xe2, 0x60, 0x8d, 0x74,
+ 0x00, 0x75, 0x85, 0x94, 0xe6, 0x60, 0xe8, 0x8d, 0x74, 0xe2, 0xac, 0x70, 0xe6, 0x94, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x5d, 0x88, 0x20, 0x88, 0x88, 0xe8, 0x78, 0xe7,
+ 0x90, 0xe9, 0x9c, 0xe7, 0xa5, 0x7b, 0x20, 0x8c, 0x8c, 0xe6, 0xac, 0xb0, 0x19, 0x81, 0x80, 0x84,
+ 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var HardwarePowerInput = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5a, 0x74, 0xe9, 0x88,
+ 0xe7, 0xcc, 0xe9, 0x78, 0xe6, 0x5a, 0xe3, 0x80, 0x98, 0xe7, 0x94, 0xe9, 0x78, 0xe6, 0x5a, 0xe9,
+ 0x88, 0xe3, 0x9c, 0x80, 0xe7, 0x94, 0xe9, 0x78, 0xe6, 0x76, 0xe9, 0x88, 0xe3, 0x9c, 0x80, 0xe7,
+ 0x94, 0xe9, 0x78, 0xe6, 0x92, 0xe9, 0x88, 0xe1,
+}
+
+var HardwareRouter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x55, 0x90, 0xd1, 0x73,
+ 0x00, 0xa4, 0x21, 0x72, 0xa0, 0x3d, 0x8f, 0x61, 0x6f, 0xa1, 0x8b, 0x5c, 0x90, 0x5c, 0x90, 0xc5,
+ 0x78, 0x65, 0x81, 0x6c, 0x21, 0x84, 0x20, 0xa9, 0x81, 0xa9, 0x81, 0xa0, 0xf9, 0x81, 0x81, 0x71,
+ 0xfd, 0x84, 0x59, 0x70, 0x90, 0x59, 0x70, 0x90, 0x09, 0x86, 0x29, 0x81, 0x55, 0x88, 0x75, 0x83,
+ 0xe3, 0x59, 0x7e, 0x85, 0x81, 0xb0, 0x29, 0x7e, 0x29, 0x7e, 0xc1, 0x7b, 0x3d, 0x7d, 0x55, 0x79,
+ 0x3d, 0x7d, 0x90, 0x2d, 0x7b, 0xed, 0x80, 0x55, 0x79, 0xc5, 0x82, 0x20, 0xa9, 0x81, 0xa9, 0x81,
+ 0xb1, 0x65, 0x81, 0xa1, 0x7e, 0x31, 0x83, 0xf1, 0x7d, 0x05, 0x85, 0xf1, 0x7d, 0xd1, 0x81, 0x80,
+ 0xa1, 0x83, 0xb1, 0x80, 0xfd, 0x84, 0x11, 0x82, 0x20, 0xb1, 0x81, 0x55, 0x7e, 0xe2, 0x9c, 0x84,
+ 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x78, 0xe9, 0x90, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x70, 0x98, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3,
+ 0x8e, 0x80, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x8e, 0x80, 0xe7, 0x78, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var HardwareScanner = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x85, 0x8f, 0x4d, 0x7c,
+ 0x02, 0x61, 0x70, 0x62, 0x5e, 0xc5, 0x74, 0x45, 0x8b, 0x7e, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x80, 0xb0, 0x80, 0x55,
+ 0x7e, 0xf9, 0x7e, 0xe9, 0x7c, 0x85, 0x7d, 0x4d, 0x7c, 0xe2, 0x6c, 0x92, 0xe7, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0xb0, 0x80, 0xe6, 0x74, 0xe9, 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var HardwareSecurity = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x54, 0x00, 0x5c,
+ 0x64, 0xe9, 0x98, 0xb1, 0x80, 0x1d, 0x8b, 0xad, 0x87, 0x79, 0x95, 0xa4, 0xb0, 0x55, 0x8a, 0x79,
+ 0x7d, 0xa4, 0x1d, 0x73, 0xa4, 0x50, 0xe8, 0x64, 0x00, 0x80, 0x54, 0xe3, 0x80, 0xfd, 0x95, 0xe7,
+ 0x9c, 0xb0, 0xf1, 0x7e, 0x3d, 0x88, 0x75, 0x79, 0x95, 0x8f, 0x64, 0xe1, 0x91, 0xe8, 0x80, 0xe6,
+ 0x64, 0xe8, 0x99, 0x74, 0x20, 0x9c, 0xc9, 0x79, 0xe9, 0x99, 0x91, 0xe1,
+}
+
+var HardwareSIMCard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x8f, 0x60, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x3d, 0x7e, 0x78, 0x05, 0x7c, 0x78, 0xe6, 0x78, 0x00, 0x60, 0x70, 0xe9, 0xb0,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x05, 0x98, 0xb0, 0x35, 0x82, 0x80,
+ 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x20, 0xfd, 0x7f, 0x40, 0xe2, 0x74, 0x9c, 0xe7, 0x78,
+ 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0xa0, 0x80, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe3, 0x60, 0x70, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe3, 0x90, 0x90, 0xe7,
+ 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x90, 0x88, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var HardwareSmartphone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x05, 0x6a, 0x00,
+ 0x6c, 0x54, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc8, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe2, 0x94,
+ 0x9c, 0xe6, 0x6c, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe1,
+}
+
+var HardwareSpeaker = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x58, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0xfd, 0x83, 0x88, 0xfd, 0x83, 0x00, 0x94, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80, 0x60,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x92, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe3, 0x80, 0xc0, 0xb0, 0x7d, 0x7a, 0x80,
+ 0x6c, 0x85, 0x7b, 0x6c, 0x6c, 0x92, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x94, 0xe3, 0x80, 0x60, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74,
+ 0x8c, 0x92, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x51, 0x7d, 0x74, 0x74,
+ 0x74, 0xe1,
+}
+
+var HardwareSpeakerGroup = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x8c, 0x54, 0xe6,
+ 0x99, 0x7b, 0xa0, 0x9d, 0x79, 0x54, 0x70, 0x9d, 0x6b, 0x70, 0x99, 0x6d, 0xe9, 0xcd, 0x9c, 0xb0,
+ 0x80, 0xfd, 0x81, 0x9d, 0x81, 0x95, 0x83, 0x99, 0x83, 0x95, 0x83, 0x20, 0xcd, 0x90, 0x05, 0x80,
+ 0xb0, 0xfd, 0x81, 0x80, 0x99, 0x83, 0x65, 0x7e, 0x99, 0x83, 0x69, 0x7c, 0xe8, 0x99, 0x6d, 0xa0,
+ 0xa0, 0x9d, 0x6b, 0x65, 0x8e, 0x54, 0x69, 0x8c, 0x54, 0xe2, 0x88, 0x5c, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0xcd, 0x81, 0x88, 0x88, 0x92, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe3, 0x80, 0xb6, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x6d, 0x7c, 0x70,
+ 0x70, 0x92, 0x95, 0x83, 0x70, 0x90, 0x70, 0x90, 0x95, 0x83, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70,
+ 0x90, 0xe3, 0x80, 0x66, 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0x92, 0x3d, 0x82,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe2, 0x68, 0x64,
+ 0xe6, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xe9,
+ 0x78, 0xe6, 0x68, 0xe8, 0x64, 0xe1,
+}
+
+var HardwareTablet = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x60, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83,
+ 0x78, 0x00, 0xac, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xb8,
+ 0xe6, 0x64, 0xe8, 0x68, 0xe7, 0xb8, 0xe9, 0xb0, 0xe1,
+}
+
+var HardwareTabletAndroid = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x50, 0xe6, 0x68,
+ 0xa0, 0xb1, 0x70, 0x50, 0x5c, 0xb1, 0x6a, 0x5c, 0x5c, 0xe9, 0xc8, 0xb0, 0x80, 0x51, 0x83, 0xb1,
+ 0x82, 0x8c, 0x8c, 0x8c, 0xe7, 0xb0, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe8,
+ 0x5c, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe3, 0x70, 0xd8, 0xe7, 0x70, 0xe9,
+ 0x7c, 0xe7, 0x90, 0xe9, 0x84, 0xe3, 0x81, 0x8a, 0x74, 0xe7, 0x46, 0xe8, 0x5c, 0xe7, 0xba, 0xe9,
+ 0xc0, 0xe1,
+}
+
+var HardwareTabletMac = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9a, 0x50, 0xe6, 0x62,
+ 0xa0, 0x3d, 0x6e, 0x50, 0x58, 0x3d, 0x6a, 0x58, 0x5a, 0xe9, 0xcc, 0xb0, 0x80, 0xc5, 0x82, 0x3d,
+ 0x82, 0x8a, 0x8a, 0x8a, 0xe7, 0xb8, 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xe8,
+ 0x5a, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0xe2, 0x7e, 0xac, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x9e, 0x70, 0xe6, 0x60, 0xe8, 0x5c, 0xe7, 0xbc, 0xe9,
+ 0xc0, 0xe1,
+}
+
+var HardwareToys = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x80, 0xb0, 0x80,
+ 0xe9, 0x79, 0xe9, 0x84, 0x6a, 0x96, 0x6a, 0x90, 0x96, 0xe9, 0x84, 0x96, 0x96, 0xe6, 0x80, 0xe3,
+ 0x80, 0x80, 0xb0, 0x80, 0x19, 0x86, 0x19, 0x7b, 0x96, 0x6a, 0x96, 0x80, 0x54, 0x19, 0x86, 0x54,
+ 0x80, 0xe7, 0xac, 0xe3, 0x80, 0x80, 0xb0, 0xe9, 0x79, 0x80, 0x6a, 0x19, 0x7b, 0x6a, 0x6a, 0x80,
+ 0xe9, 0x79, 0x54, 0x80, 0x54, 0xe9, 0xac, 0xe3, 0x80, 0x80, 0xb0, 0x19, 0x86, 0x80, 0x96, 0xe9,
+ 0x84, 0x96, 0x96, 0x90, 0x19, 0x7b, 0x96, 0x6a, 0x96, 0xe8, 0x80, 0xe1,
+}
+
+var HardwareTV = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xac, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe1,
+}
+
+var HardwareVideogameAsset = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x68, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x7c, 0x84, 0xe7, 0x74, 0xe9,
+ 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe6, 0x5c, 0xe9, 0x78, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9,
+ 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe3, 0x92, 0x88, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a,
+ 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a,
+ 0x86, 0xe3, 0x90, 0x74, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81,
+ 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var HardwareWatch = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x80, 0xb0, 0x80,
+ 0xe9, 0x7a, 0xa1, 0x7d, 0x61, 0x76, 0xe9, 0x79, 0x75, 0x73, 0x00, 0x90, 0x50, 0xe6, 0x70, 0x20,
+ 0x19, 0x7e, 0x75, 0x8b, 0xa0, 0x61, 0x72, 0x61, 0x76, 0x60, 0xe9, 0x7a, 0x60, 0x80, 0x90, 0x61,
+ 0x82, 0xa1, 0x89, 0x19, 0x86, 0x8d, 0x8c, 0x00, 0x70, 0xb0, 0xe7, 0xa0, 0x20, 0xe9, 0x81, 0x8d,
+ 0x74, 0xa0, 0xa1, 0x8d, 0xa1, 0x89, 0xa0, 0x19, 0x85, 0xa0, 0x80, 0xe3, 0x48, 0x80, 0xb0, 0x80,
+ 0x61, 0x79, 0x61, 0x85, 0x68, 0x98, 0x68, 0x92, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1, 0x7a, 0x98,
+ 0x68, 0x98, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0xe1,
+}
+
+var ImageAddAPhoto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x60, 0xe8, 0x54,
+ 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe7, 0x74, 0xe9, 0x8c, 0xe6, 0x5c, 0xe9, 0x74,
+ 0xe6, 0x50, 0xe8, 0x60, 0xe7, 0x8c, 0xe3, 0x8c, 0x98, 0xe9, 0x74, 0xe7, 0x8c, 0xe8, 0x60, 0xe7,
+ 0x9c, 0x20, 0xa9, 0x83, 0x88, 0xe6, 0xa4, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88,
+ 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe8, 0x78, 0xe7, 0x8c, 0xe3, 0x9c, 0xa4, 0xb0, 0x85, 0x85,
+ 0x80, 0x94, 0x85, 0x7b, 0x94, 0x6c, 0x92, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x6c, 0x7d, 0x84, 0x6c,
+ 0x94, 0x7d, 0x84, 0x94, 0x94, 0x94, 0xe3, 0x99, 0x79, 0x6c, 0xb0, 0x80, 0x8d, 0x83, 0xdd, 0x82,
+ 0x69, 0x86, 0x69, 0x86, 0x69, 0x86, 0x92, 0x69, 0x86, 0x25, 0x7d, 0x69, 0x86, 0x99, 0x79, 0x25,
+ 0x7d, 0x99, 0x79, 0x99, 0x79, 0x99, 0x79, 0x99, 0x79, 0xdd, 0x82, 0x99, 0x79, 0x69, 0x86, 0xe1,
+}
+
+var ImageAddToPhotos = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe6, 0x58,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe3, 0xc0, 0x70, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x7c, 0xa4, 0xe7, 0x70, 0xe9, 0x90, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x70, 0xe9,
+ 0x78, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var ImageAdjust = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf9,
+ 0x74, 0x58, 0x58, 0xf9, 0x74, 0x58, 0x80, 0x91, 0xf9, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x09, 0x77,
+ 0xa8, 0x58, 0x80, 0x09, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60,
+ 0xd1, 0x78, 0x60, 0x60, 0x80, 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0,
+ 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x8c, 0x60, 0xb0, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c, 0x74,
+ 0x8c, 0x92, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x74, 0x8c, 0xb1, 0x82, 0x8c,
+ 0x8c, 0xe1,
+}
+
+var ImageAssistant = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x58, 0xe6, 0x64,
+ 0xa0, 0xcd, 0x6f, 0x58, 0x5c, 0xcd, 0x6d, 0x5c, 0x60, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0x21, 0x8c, 0x8c, 0x8c, 0x74, 0xe7, 0x90, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe2, 0xc1, 0x83, 0xc1, 0x81, 0x00, 0x80, 0x94, 0x20, 0x41, 0x7c, 0xc1, 0x77, 0x00, 0x68,
+ 0x7c, 0x20, 0x41, 0x88, 0x41, 0x7c, 0x00, 0x80, 0x64, 0x20, 0xc1, 0x83, 0x41, 0x88, 0x00, 0x98,
+ 0x7c, 0x20, 0xc1, 0x77, 0xc1, 0x83, 0xe1,
+}
+
+var ImageAssistantPhoto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x84, 0x68, 0x00,
+ 0x88, 0x60, 0xe6, 0x64, 0xe9, 0xc4, 0xe7, 0x88, 0xe8, 0x88, 0xe7, 0x35, 0x8b, 0x20, 0xcd, 0x80,
+ 0x88, 0xe7, 0x9c, 0xe8, 0x68, 0xe1,
+}
+
+var ImageAudiotrack = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x5c, 0xe9, 0x8d,
+ 0x92, 0xb1, 0x11, 0x7f, 0xad, 0x7f, 0x11, 0x7e, 0x75, 0x7f, 0x7a, 0x75, 0x7f, 0x09, 0x7b, 0x80,
+ 0x6e, 0x09, 0x84, 0x6e, 0x92, 0x90, 0x09, 0x84, 0x92, 0x92, 0x92, 0xb0, 0xa1, 0x84, 0x80, 0x69,
+ 0x88, 0x7d, 0x7c, 0xe9, 0x88, 0x70, 0xe7, 0x19, 0x80, 0xe8, 0x68, 0xe7, 0x90, 0xe8, 0x5c, 0xe6,
+ 0x80, 0xe1,
+}
+
+var ImageBlurCircular = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x74, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f,
+ 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x90, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x74, 0x72, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92,
+ 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3,
+ 0x8c, 0x9c, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82,
+ 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x74, 0x74, 0xb0, 0x75,
+ 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f,
+ 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x8c, 0x68, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d,
+ 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80,
+ 0x82, 0x82, 0x82, 0xe3, 0x90, 0x86, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92,
+ 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3,
+ 0x80, 0x7a, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e,
+ 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe3, 0x8c, 0x98, 0xb0, 0x75,
+ 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f,
+ 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x80, 0x70, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75,
+ 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f,
+ 0x7e, 0x7e, 0x7e, 0xe2, 0x80, 0x58, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91,
+ 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58,
+ 0xe3, 0x80, 0xc8, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60,
+ 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x88, 0x72,
+ 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82,
+ 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x80, 0x72, 0xb0, 0xe9, 0x7e, 0x80,
+ 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c,
+ 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var ImageBlurLinear = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x96, 0xb0, 0xa9,
+ 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x90, 0x6e, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19,
+ 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80,
+ 0x84, 0x84, 0x84, 0xe3, 0x80, 0x70, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92,
+ 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe2,
+ 0x5c, 0xa4, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x88, 0x52, 0xb0, 0xa9, 0x81,
+ 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a,
+ 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x80, 0x90, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0x86, 0xe3, 0x90, 0x8e, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19,
+ 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0xa0,
+ 0x7e, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e,
+ 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe2, 0x5c, 0x5c, 0xe9, 0x88, 0xe7,
+ 0xc8, 0xe8, 0x5c, 0xe6, 0x5c, 0xe3, 0xb8, 0x96, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82,
+ 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82,
+ 0x82, 0xe3, 0x80, 0x90, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f,
+ 0x7e, 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe3, 0x70, 0x72,
+ 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x80, 0x90, 0xb0, 0x19, 0x81, 0x80,
+ 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84,
+ 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x80, 0x90, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84,
+ 0x84, 0xe1,
+}
+
+var ImageBlurOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x6c, 0xb0, 0x19,
+ 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80,
+ 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x99, 0x7f, 0xf5, 0x88, 0xb1, 0x21, 0x80, 0x05,
+ 0x80, 0x45, 0x80, 0x0d, 0x80, 0x69, 0x80, 0x0d, 0x80, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86,
+ 0x7a, 0x91, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xb1, 0x80, 0x25, 0x80,
+ 0x05, 0x80, 0x45, 0x80, 0x0d, 0x80, 0x69, 0x80, 0x31, 0x80, 0x51, 0x81, 0x3d, 0x81, 0x61, 0x82,
+ 0x91, 0x82, 0x8d, 0x82, 0xe2, 0x88, 0x5e, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e,
+ 0x92, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82,
+ 0xe3, 0x70, 0x80, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e,
+ 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe3, 0xac, 0x9c, 0xb0,
+ 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x75,
+ 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe3, 0x54, 0x72, 0xb0, 0x19, 0x81, 0x80, 0x84,
+ 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe3, 0xa0, 0xa0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c,
+ 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84,
+ 0xe3, 0x80, 0x70, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c,
+ 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x80, 0x70, 0xb0,
+ 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x70, 0xb6, 0xb0, 0x75, 0x7f, 0x80, 0x7e,
+ 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d,
+ 0x7f, 0x7e, 0x7e, 0x7e, 0xe2, 0x5a, 0x8d, 0x72, 0x20, 0x91, 0x87, 0x91, 0x87, 0xb1, 0xd1, 0x7f,
+ 0xf1, 0x7f, 0xa1, 0x7f, 0xe1, 0x7f, 0x71, 0x7f, 0xe1, 0x7f, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80,
+ 0x7c, 0x84, 0x91, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xb0, 0x80, 0xcd,
+ 0x7f, 0xf1, 0x7f, 0xa1, 0x7f, 0xe5, 0x7f, 0x71, 0x7f, 0x20, 0xa1, 0x85, 0xa1, 0x85, 0xa0, 0x15,
+ 0x7a, 0x4d, 0x81, 0x72, 0x85, 0x82, 0x72, 0x88, 0xb1, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86,
+ 0x86, 0x7d, 0x81, 0x80, 0xb5, 0x82, 0xed, 0x7e, 0xf5, 0x82, 0x81, 0x7d, 0x20, 0xa1, 0x85, 0xa1,
+ 0x85, 0xb1, 0xd1, 0x7f, 0xf1, 0x7f, 0xa1, 0x7f, 0xe1, 0x7f, 0x71, 0x7f, 0xe1, 0x7f, 0xe9, 0x7e,
+ 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x91, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0xb0, 0x80, 0xcd, 0x7f, 0xf1, 0x7f, 0xa1, 0x7f, 0xe5, 0x7f, 0x71, 0x7f, 0x03, 0x75, 0x8d,
+ 0xa6, 0xa0, 0x75, 0x90, 0x8d, 0x6f, 0x60, 0x5a, 0x8d, 0x72, 0xe2, 0x78, 0x94, 0xb0, 0xe9, 0x7e,
+ 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0xac, 0x72, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80,
+ 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e,
+ 0x7e, 0x7e, 0xe3, 0x44, 0x7e, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x74,
+ 0x72, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82,
+ 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x9c, 0xac, 0xb0, 0x75, 0x7f,
+ 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82,
+ 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x70, 0x72, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80,
+ 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c,
+ 0x7c, 0x7c, 0xe3, 0x74, 0x72, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75,
+ 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe1,
+}
+
+var ImageBlurOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x84, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f,
+ 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x90, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x60, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92,
+ 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3,
+ 0x74, 0x82, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82,
+ 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x8c, 0x6e, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f,
+ 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0xbc, 0x96, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d,
+ 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80,
+ 0x82, 0x82, 0x82, 0xe3, 0x64, 0x72, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92,
+ 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3,
+ 0x80, 0x72, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92, 0x8d, 0x7f, 0x7e, 0x7e,
+ 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe2, 0x5c, 0x86, 0xb0, 0x75,
+ 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f,
+ 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x9c, 0x9c, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75,
+ 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f,
+ 0x7e, 0x7e, 0x7e, 0xe3, 0x80, 0x3c, 0xb0, 0x8d, 0x80, 0x80, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x92,
+ 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x75, 0x80, 0x82, 0x82, 0x82, 0xe3,
+ 0x80, 0x8e, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x92, 0x19, 0x7f, 0x7c, 0x7c,
+ 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe3, 0x80, 0x96, 0xb0, 0x59,
+ 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0xa0, 0x82, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x90, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92,
+ 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3,
+ 0x80, 0x60, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84,
+ 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x70, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f,
+ 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x8c, 0xa2, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75,
+ 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f,
+ 0x7e, 0x7e, 0x7e, 0xe3, 0x64, 0x8e, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x92,
+ 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3,
+ 0x80, 0x8e, 0xb0, 0x75, 0x7f, 0x80, 0x7e, 0x75, 0x80, 0x7e, 0x82, 0x92, 0x75, 0x80, 0x82, 0x82,
+ 0x82, 0x82, 0x8d, 0x7f, 0x82, 0x7e, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe3, 0x70, 0x50, 0xb0, 0x59,
+ 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0x80, 0xa2, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0x92, 0xe9, 0x80, 0x84, 0x84, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x90, 0x6e, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92,
+ 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3,
+ 0x80, 0x70, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86,
+ 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe1,
+}
+
+var ImageBrightness1 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x80, 0xd1, 0xa8,
+ 0xa8, 0x00, 0x04, 0xd0, 0x80, 0xa8, 0xa8, 0x00, 0x04, 0x30, 0x80, 0xe1,
+}
+
+var ImageBrightness2 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x58, 0xb1, 0x59,
+ 0x7c, 0x80, 0xf1, 0x78, 0xfd, 0x80, 0x6c, 0xb5, 0x82, 0xf9, 0x85, 0x75, 0x83, 0x94, 0xe9, 0x89,
+ 0x94, 0x4d, 0x91, 0x90, 0xf9, 0x7b, 0xd9, 0x8d, 0x6c, 0x4d, 0x91, 0xb1, 0xf1, 0x82, 0xb5, 0x81,
+ 0x59, 0x86, 0xb5, 0x82, 0x94, 0xb5, 0x82, 0x0d, 0x8b, 0x80, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80,
+ 0x0d, 0x87, 0x58, 0x78, 0x58, 0xe1,
+}
+
+var ImageBrightness3 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x58, 0xb0, 0xe9,
+ 0x7d, 0x80, 0xe5, 0x7b, 0x51, 0x80, 0x74, 0xed, 0x80, 0xa0, 0x1d, 0x7c, 0x79, 0x6f, 0x84, 0x0d,
+ 0x77, 0x84, 0x80, 0xb2, 0x80, 0xf5, 0x88, 0x1d, 0x7a, 0x89, 0x90, 0x64, 0x15, 0x93, 0xe5, 0x81,
+ 0x99, 0x80, 0xe9, 0x83, 0xed, 0x80, 0x8c, 0xed, 0x80, 0x0d, 0x8b, 0x80, 0xa8, 0x0d, 0x77, 0xa8,
+ 0x58, 0x80, 0x0d, 0x85, 0x58, 0x74, 0x58, 0xe1,
+}
+
+var ImageBrightness4 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x61, 0x79, 0xe8,
+ 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61, 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89,
+ 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0, 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96,
+ 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0x01, 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe2,
+ 0x80, 0x98, 0xb1, 0x35, 0x7e, 0x80, 0x85, 0x7c, 0x99, 0x7f, 0x76, 0xe9, 0x7e, 0x21, 0x84, 0x19,
+ 0x7e, 0x8e, 0xf1, 0x79, 0x8e, 0x19, 0x75, 0x90, 0x21, 0x7d, 0x6e, 0x72, 0x19, 0x75, 0xb1, 0x85,
+ 0x81, 0x4d, 0x7f, 0x35, 0x83, 0xe9, 0x7e, 0x8a, 0xe9, 0x7e, 0xa1, 0x86, 0x80, 0x98, 0x61, 0x85,
+ 0x98, 0x98, 0x90, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xe1,
+}
+
+var ImageBrightness5 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xa1, 0x86, 0x01,
+ 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe8, 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61,
+ 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89, 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0,
+ 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96, 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0xe2,
+ 0x80, 0x98, 0xb0, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x92, 0x61, 0x85, 0x68, 0x98,
+ 0x68, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xe1,
+}
+
+var ImageBrightness6 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xa1, 0x86, 0x01,
+ 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe8, 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61,
+ 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89, 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0,
+ 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96, 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0xe2,
+ 0x80, 0x98, 0xe8, 0x68, 0xb0, 0xa1, 0x86, 0x80, 0x98, 0x61, 0x85, 0x98, 0x98, 0x90, 0xa1, 0x7a,
+ 0x98, 0x68, 0x98, 0xe1,
+}
+
+var ImageBrightness7 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x61, 0x79, 0xe8,
+ 0x60, 0xe7, 0xa1, 0x76, 0x01, 0x80, 0x61, 0x69, 0x61, 0x79, 0x60, 0xe6, 0x60, 0xe9, 0x61, 0x89,
+ 0x01, 0x61, 0x69, 0x80, 0x60, 0xa1, 0x86, 0xe8, 0xa0, 0xe7, 0x61, 0x89, 0x01, 0x80, 0xa1, 0x96,
+ 0xa1, 0x86, 0xa0, 0xe6, 0xa0, 0xe9, 0xa1, 0x76, 0x01, 0xa1, 0x96, 0x80, 0xa0, 0x61, 0x79, 0xe2,
+ 0x80, 0x98, 0xb0, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x92, 0x61, 0x85, 0x68, 0x98,
+ 0x68, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0x98, 0xe3, 0x80, 0x58, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe1,
+}
+
+var ImageBrokenImage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x64, 0xe9, 0x35,
+ 0x8d, 0x24, 0x74, 0x74, 0x70, 0x90, 0x70, 0x70, 0x70, 0x90, 0x74, 0x74, 0xe8, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81,
+ 0x88, 0x88, 0xe3, 0x74, 0xcd, 0x8c, 0x20, 0x8c, 0x8c, 0xe8, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0x35,
+ 0x7e, 0x88, 0x78, 0x88, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe8,
+ 0xcd, 0x80, 0x23, 0x8c, 0x8c, 0x90, 0x70, 0x90, 0x90, 0x90, 0x70, 0xe1,
+}
+
+var ImageBrush = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x88, 0xb4, 0xb1,
+ 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x80, 0xa1, 0x82, 0xb1, 0x7d, 0x88, 0x78, 0x88, 0xd9,
+ 0x81, 0x71, 0x82, 0xfd, 0x84, 0x88, 0x90, 0x88, 0x6d, 0x84, 0x80, 0x90, 0x6d, 0x7c, 0x90, 0x70,
+ 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe2, 0x69, 0x91, 0x45, 0x71, 0x20, 0x55, 0x7d,
+ 0x55, 0x7d, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f, 0x2d, 0x7d, 0x80, 0x00, 0x74,
+ 0x81, 0x80, 0x21, 0x81, 0x85, 0x81, 0x85, 0xe9, 0x91, 0x19, 0x6e, 0xb0, 0xcd, 0x80, 0x35, 0x7f,
+ 0xcd, 0x80, 0xf5, 0x7d, 0x80, 0x2d, 0x7d, 0xe1,
+}
+
+var ImageBurstMode = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0x64, 0xe7, 0x88,
+ 0xe9, 0xb8, 0xe6, 0x54, 0xe3, 0x90, 0x80, 0xe7, 0x88, 0xe9, 0xb8, 0xe7, 0x78, 0xe3, 0xc4, 0x80,
+ 0xe6, 0x78, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb0, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0xb0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0xe8, 0x68, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe2, 0x7c, 0x94, 0x22,
+ 0x8a, 0xb5, 0x79, 0x91, 0x83, 0x4d, 0x84, 0x8a, 0x91, 0x79, 0x00, 0xa4, 0x94, 0xe6, 0x7c, 0xe1,
+}
+
+var ImageCamera = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x7a, 0x7a, 0x20,
+ 0x89, 0x89, 0x7d, 0x6f, 0xa0, 0xf1, 0x82, 0x31, 0x6c, 0x7d, 0x81, 0x58, 0x80, 0x58, 0xb0, 0x35,
+ 0x7b, 0x80, 0xd1, 0x76, 0xb1, 0x81, 0x5d, 0x73, 0x85, 0x84, 0x21, 0x55, 0x87, 0xb1, 0x8c, 0x1d,
+ 0x80, 0xcd, 0x7f, 0xe3, 0x49, 0x98, 0x7a, 0xb0, 0x29, 0x7e, 0x29, 0x7a, 0xb5, 0x79, 0x7d, 0x75,
+ 0x05, 0x74, 0x51, 0x73, 0x00, 0xc5, 0x7f, 0x74, 0xe7, 0x51, 0x93, 0xe3, 0x85, 0x80, 0x84, 0xe6,
+ 0xa1, 0x84, 0x21, 0x95, 0x80, 0x82, 0x89, 0x89, 0x81, 0x90, 0xa0, 0xfd, 0x91, 0xf1, 0x89, 0xa8,
+ 0x35, 0x85, 0xa8, 0x80, 0xb0, 0x80, 0xa1, 0x7e, 0xdd, 0x7f, 0x4d, 0x7d, 0x99, 0x7f, 0x78, 0xe3,
+ 0x79, 0x65, 0x88, 0x20, 0x35, 0x78, 0x81, 0x72, 0xa0, 0x05, 0x6e, 0x11, 0x76, 0x58, 0xcd, 0x7a,
+ 0x58, 0x80, 0xb0, 0x80, 0x61, 0x81, 0x25, 0x80, 0xb5, 0x82, 0x69, 0x80, 0x88, 0xe7, 0xfd, 0x8e,
+ 0x20, 0xb1, 0x7d, 0x78, 0xe2, 0xed, 0x6c, 0x8c, 0xb0, 0xd9, 0x81, 0xd9, 0x85, 0x4d, 0x86, 0x85,
+ 0x8a, 0xfd, 0x8b, 0xb1, 0x8c, 0x00, 0x3d, 0x80, 0x8c, 0xe6, 0xed, 0x6c, 0xe3, 0x8d, 0x96, 0x80,
+ 0x20, 0x35, 0x78, 0x85, 0x8d, 0xb1, 0x69, 0x81, 0x51, 0x80, 0xd9, 0x82, 0x7d, 0x80, 0x59, 0x84,
+ 0x7d, 0x80, 0xcd, 0x84, 0x80, 0x31, 0x89, 0x51, 0x7e, 0xa5, 0x8c, 0x7d, 0x7b, 0x01, 0x51, 0x85,
+ 0xcd, 0x82, 0x75, 0x83, 0x8c, 0xe1,
+}
+
+var ImageCameraAlt = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x58, 0x20, 0x59,
+ 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x58, 0xe6, 0x74, 0xe3, 0x8c, 0xbc, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b,
+ 0x6c, 0x6c, 0x92, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b, 0x94,
+ 0x6c, 0x94, 0xe2, 0x99, 0x79, 0x80, 0xd1, 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0xcd, 0x8c, 0x80,
+ 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0x35, 0x73, 0x80, 0xe1,
+}
+
+var ImageCameraFront = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0xa0, 0xe6, 0x64,
+ 0xe9, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0x21, 0x8c, 0x74, 0x74, 0x74, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe9, 0x88, 0xe7, 0x94, 0xe9, 0x78, 0xe6, 0x88, 0xe3, 0x78, 0x50, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0x91, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c,
+ 0x88, 0xb0, 0x05, 0x80, 0x35, 0x82, 0xc9, 0x81, 0x88, 0xfd, 0x83, 0x88, 0xe2, 0x94, 0x50, 0xe6,
+ 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x58, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x6c, 0x58, 0xe7, 0xa8,
+ 0xe9, 0xaa, 0xb0, 0x80, 0xad, 0x7c, 0x55, 0x79, 0x76, 0x6c, 0x76, 0x90, 0x6c, 0xad, 0x81, 0x6c,
+ 0x8a, 0xe8, 0x58, 0xe1,
+}
+
+var ImageCameraRear = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0xa0, 0xe6, 0x64,
+ 0xe9, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0x21, 0x8c, 0x74, 0x74, 0x74, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe9, 0x88, 0xe7, 0x94, 0xe9, 0x78, 0xe6, 0x88, 0xe3, 0x8c, 0x30, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x58, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0xfd, 0x7f, 0x68, 0xa0, 0xc9, 0x7d, 0x68, 0x78,
+ 0x35, 0x72, 0x78, 0x60, 0x92, 0xc9, 0x81, 0x78, 0xfd, 0x83, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe1,
+}
+
+var ImageCameraRoll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x7c, 0xe8, 0x58, 0xb0, 0x80, 0xe9, 0x7e, 0x19,
+ 0x7f, 0x7c, 0x7c, 0x7c, 0xe7, 0x70, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9,
+ 0x84, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xbc, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe7, 0xa0, 0xe8, 0x64, 0xe6, 0x88, 0xe3, 0x78, 0xb4, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x5c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90,
+ 0xa4, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x5c, 0xe7, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90, 0xa4, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3,
+ 0x80, 0x5c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var ImageCenterFocusStrong = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe2, 0x64, 0x8c, 0xe6, 0x5c, 0xe9, 0x90, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe3,
+ 0x80, 0x58, 0xe7, 0x90, 0xe8, 0x5c, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe3, 0xb8, 0x78, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90,
+ 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0x80, 0xc0, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe9, 0x70, 0xe7, 0x78, 0xe9, 0x90, 0xe1,
+}
+
+var ImageCenterFocusWeak = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x8c, 0xe6, 0x5c,
+ 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe7,
+ 0x70, 0xe9, 0x70, 0xe3, 0x80, 0x58, 0xe7, 0x90, 0xe8, 0x5c, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe3, 0xb8, 0x78, 0xe7, 0x70,
+ 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70, 0xe7, 0x78, 0xe9, 0x90, 0xe2, 0x80, 0x70, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe3, 0x80, 0x98, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35,
+ 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x88, 0xe1,
+}
+
+var ImageCollections = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x90, 0xe8, 0x60,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe3, 0x54, 0x70, 0x20, 0x11, 0x84,
+ 0x6d, 0x85, 0x00, 0x90, 0x7c, 0x20, 0x90, 0x94, 0xe6, 0x70, 0x20, 0x8c, 0x70, 0xe2, 0x58, 0x68,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe6, 0x58, 0xe1,
+}
+
+var ImageCollectionsBookmark = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x68, 0xe6, 0x58,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe3, 0xc0, 0x70, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xa8, 0x20, 0x76, 0x7a, 0x00, 0x8c, 0x80, 0xe8, 0x60, 0xe7, 0x94, 0xe9,
+ 0xa0, 0xe1,
+}
+
+var ImageColorLens = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x5c, 0xa0, 0x11,
+ 0x76, 0x5c, 0x5c, 0x11, 0x76, 0x5c, 0x80, 0x90, 0x11, 0x88, 0xa4, 0xa4, 0xa4, 0xb3, 0xa9, 0x81,
+ 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x80, 0x39, 0x7f, 0xb5, 0x7f, 0x85, 0x7e, 0x39, 0x7f, 0xfd,
+ 0x7d, 0x89, 0x7f, 0x79, 0x7f, 0x41, 0x7f, 0xc9, 0x7e, 0x41, 0x7f, 0x05, 0x7e, 0x80, 0x59, 0x7e,
+ 0x59, 0x81, 0x7a, 0x86, 0x7a, 0xe6, 0x90, 0xb1, 0x85, 0x85, 0x80, 0x94, 0x85, 0x7b, 0x94, 0x6c,
+ 0x80, 0x29, 0x77, 0xf1, 0x77, 0x60, 0x5c, 0x60, 0xe2, 0x6a, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a,
+ 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8c, 0x70, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86,
+ 0xe3, 0x94, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a,
+ 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8c, 0x90, 0xb0,
+ 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var ImageColorize = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x91, 0x45, 0x73,
+ 0x20, 0x55, 0x7b, 0x55, 0x7b, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f, 0x2d, 0x7d,
+ 0x80, 0x20, 0xc1, 0x79, 0x41, 0x86, 0x01, 0xd5, 0x7f, 0x62, 0x7a, 0xd5, 0x73, 0x20, 0xd9, 0x82,
+ 0xd9, 0x82, 0x00, 0x5c, 0x81, 0x88, 0xe8, 0xa4, 0xe7, 0x81, 0x89, 0x20, 0xd9, 0x91, 0x29, 0x6e,
+ 0x01, 0x2d, 0x8c, 0x86, 0x9e, 0x2d, 0x80, 0x21, 0x29, 0x7c, 0x29, 0x7c, 0x41, 0x86, 0xc1, 0x79,
+ 0xb0, 0xcd, 0x80, 0x39, 0x7f, 0xcd, 0x80, 0xf5, 0x7d, 0x80, 0x31, 0x7d, 0xe2, 0xd9, 0x75, 0x9c,
+ 0x00, 0x64, 0x29, 0x8a, 0x21, 0x21, 0x90, 0xe1, 0x6f, 0xd9, 0x83, 0xd9, 0x83, 0x00, 0xd9, 0x75,
+ 0x9c, 0xe1,
+}
+
+var ImageCompare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe3, 0x80, 0xbc, 0xe6, 0x64, 0x20, 0x94, 0x68, 0xe9, 0x98, 0xe2, 0x9c, 0x5c, 0xe6, 0x88, 0xe9,
+ 0x88, 0xe7, 0x94, 0xe9, 0xb4, 0x00, 0x88, 0x80, 0xe9, 0xa4, 0xe7, 0x94, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe1,
+}
+
+var ImageControlPoint = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe2, 0x80, 0x58, 0xa0, 0xf9, 0x74, 0x58, 0x58, 0xf9, 0x74,
+ 0x58, 0x80, 0x91, 0xf9, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x09, 0x77, 0xa8, 0x58, 0x80, 0x09, 0x8b,
+ 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80,
+ 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0,
+ 0xe1,
+}
+
+var ImageControlPointDuplicate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x70, 0xe7, 0x78,
+ 0xe9, 0x8c, 0xe7, 0x74, 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x8c, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0x8c,
+ 0xe9, 0x78, 0xe7, 0x74, 0xe2, 0x58, 0x80, 0xb0, 0x80, 0x6d, 0x7a, 0x4d, 0x83, 0x9d, 0x75, 0x05,
+ 0x88, 0x5d, 0x73, 0xe8, 0x0d, 0x6f, 0xa0, 0x09, 0x6d, 0x85, 0x71, 0x50, 0x2d, 0x78, 0x50, 0x80,
+ 0x90, 0x09, 0x85, 0x7d, 0x8e, 0x05, 0x8c, 0xf5, 0x90, 0xe9, 0xb1, 0x7b, 0xa0, 0x4d, 0x6f, 0x65,
+ 0x8a, 0x58, 0x95, 0x85, 0x58, 0x80, 0xe2, 0x8c, 0x5c, 0xb0, 0x11, 0x76, 0x80, 0x5c, 0x11, 0x88,
+ 0x5c, 0xa4, 0x91, 0x11, 0x88, 0xa4, 0xa4, 0xa4, 0xa4, 0xf1, 0x77, 0xa4, 0x5c, 0x80, 0xf1, 0x8f,
+ 0x5c, 0x8c, 0x5c, 0xe3, 0x80, 0xc0, 0xb0, 0x49, 0x78, 0x80, 0x64, 0xb9, 0x79, 0x64, 0x64, 0x92,
+ 0x49, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x49, 0x86, 0x9c, 0x9c, 0xb9, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var ImageCrop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x8c, 0xe7, 0x88,
+ 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x74, 0xe9, 0x88, 0xe7,
+ 0xa0, 0xe9, 0xa0, 0xe3, 0x58, 0x88, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x90, 0xe6, 0x54, 0xe9, 0x88,
+ 0xe7, 0x90, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xe9,
+ 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x78, 0xe6, 0x6c, 0xe1,
+}
+
+var ImageCrop169 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x68, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xa8, 0xe6, 0x64, 0xe8,
+ 0x70, 0xe7, 0xb8, 0xe9, 0xa0, 0xe1,
+}
+
+var ImageCrop32 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x60, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x64, 0xe8,
+ 0x68, 0xe7, 0xb8, 0xe9, 0xb0, 0xe1,
+}
+
+var ImageCrop54 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x64, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb0, 0xe6, 0x64, 0xe8,
+ 0x6c, 0xe7, 0xb8, 0xe9, 0xa8, 0xe1,
+}
+
+var ImageCrop75 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x6c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xa0, 0xe6, 0x64, 0xe8,
+ 0x74, 0xe7, 0xb8, 0xe9, 0x98, 0xe1,
+}
+
+var ImageCropDIN = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x64, 0xe8,
+ 0x64, 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageCropFree = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe9, 0x90,
+ 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90, 0xe8, 0x5c, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe3, 0x88, 0xa8, 0xe6, 0x5c, 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe3, 0xb8, 0x90, 0xe7, 0x70,
+ 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70, 0xe7,
+ 0x78, 0xe9, 0x90, 0xe3, 0x80, 0x40, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88,
+ 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ImageCropLandscape = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x64, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb0, 0xe6, 0x64, 0xe8,
+ 0x6c, 0xe7, 0xb8, 0xe9, 0xa8, 0xe1,
+}
+
+var ImageCropOriginal = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x64, 0xe8,
+ 0x64, 0xe7, 0xb8, 0xe9, 0xb8, 0xe2, 0xf1, 0x83, 0x91, 0x80, 0x21, 0x81, 0x7a, 0x15, 0x87, 0x11,
+ 0x7c, 0x49, 0x7b, 0x00, 0x6a, 0x94, 0xe7, 0xac, 0x20, 0xf1, 0x78, 0x91, 0x76, 0xe1,
+}
+
+var ImageCropPortrait = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8,
+ 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageCropRotate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xf1, 0x76, 0xf9, 0x92,
+ 0xa0, 0x69, 0x70, 0xe1, 0x8f, 0xb5, 0x6b, 0x85, 0x89, 0x56, 0x84, 0xe6, 0x50, 0xb1, 0x05, 0x81,
+ 0x51, 0x8c, 0x51, 0x8b, 0xac, 0xe9, 0x97, 0xac, 0x75, 0x80, 0x80, 0xe1, 0x80, 0xf5, 0x7f, 0x51,
+ 0x81, 0xf1, 0x7f, 0x00, 0x99, 0x79, 0x4d, 0x90, 0x20, 0x55, 0x7d, 0xad, 0x82, 0xe2, 0x19, 0x80,
+ 0x50, 0xb0, 0x8d, 0x7f, 0x80, 0x21, 0x7f, 0x0d, 0x80, 0xb1, 0x7e, 0x11, 0x80, 0x00, 0x69, 0x86,
+ 0xb5, 0x6f, 0x20, 0xa9, 0x82, 0x59, 0x7d, 0xa0, 0x99, 0x8f, 0x21, 0x70, 0x4d, 0x94, 0x7d, 0x76,
+ 0xaa, 0x7c, 0xe7, 0x86, 0xa0, 0xfd, 0x96, 0xb1, 0x71, 0xb1, 0x8c, 0x50, 0x19, 0x80, 0x50, 0xe2,
+ 0x90, 0x88, 0xe7, 0x88, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6,
+ 0x78, 0xe9, 0x88, 0xe7, 0x98, 0xe9, 0x98, 0xe3, 0x60, 0x88, 0xe8, 0x60, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xa0, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe6, 0x70, 0xe1,
+}
+
+var ImageCropSquare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0xe6, 0x68,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x68, 0xe8,
+ 0x68, 0xe7, 0xb0, 0xe9, 0xb0, 0xe1,
+}
+
+var ImageDehaze = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x8e, 0xe9, 0x88,
+ 0xe7, 0xd0, 0xe9, 0x78, 0xe6, 0x58, 0xe3, 0x80, 0x6c, 0xe9, 0x88, 0xe7, 0xd0, 0xe9, 0x78, 0xe6,
+ 0x58, 0xe3, 0x80, 0x6c, 0xe9, 0x88, 0xe7, 0xd0, 0xe9, 0x78, 0xe6, 0x58, 0xe1,
+}
+
+var ImageDetails = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x60, 0x20, 0xa4,
+ 0xc0, 0x00, 0xa4, 0x60, 0xe6, 0x5c, 0xe3, 0xc1, 0x86, 0x88, 0xe7, 0x81, 0x96, 0x01, 0x80, 0x90,
+ 0xc1, 0x74, 0x68, 0xe1,
+}
+
+var ImageEdit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x81, 0x8a, 0xe8,
+ 0xa4, 0xe7, 0x81, 0x87, 0x21, 0x21, 0x96, 0xe1, 0x69, 0x81, 0x78, 0x81, 0x78, 0x00, 0x5c, 0x81,
+ 0x8a, 0xe3, 0x69, 0xa3, 0x99, 0x6b, 0xb0, 0xc9, 0x80, 0x39, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0x80,
+ 0x2d, 0x7d, 0x20, 0x55, 0x7b, 0x55, 0x7b, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f,
+ 0x2d, 0x7d, 0x80, 0x22, 0x59, 0x7c, 0xa9, 0x83, 0x81, 0x87, 0x81, 0x87, 0xa9, 0x83, 0x59, 0x7c,
+ 0xe1,
+}
+
+var ImageExposure = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x94, 0xe9, 0x88,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe2, 0xa0, 0x58, 0xe6, 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58,
+ 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x44, 0x8c, 0xe7, 0x98, 0xe9, 0x88, 0xe6, 0x64, 0xe9, 0x78,
+ 0xe3, 0xbc, 0xbc, 0xe6, 0x60, 0x00, 0xa0, 0x60, 0xe9, 0xc0, 0xe1,
+}
+
+var ImageExposureNeg1 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x7c, 0xe9, 0x88,
+ 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x60, 0xe3, 0xbc, 0x9c, 0xe7, 0x78, 0xe8, 0xc1, 0x76, 0x20, 0x74,
+ 0x0d, 0x82, 0xe9, 0x99, 0x7c, 0x20, 0x69, 0x89, 0x99, 0x7c, 0xe7, 0x99, 0x80, 0xe9, 0xb4, 0xe1,
+}
+
+var ImageExposureNeg2 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x86, 0x95, 0x88,
+ 0x20, 0xbd, 0x85, 0xe1, 0x79, 0xb9, 0xc1, 0x80, 0x35, 0x7f, 0x71, 0x81, 0x71, 0x7e, 0x15, 0x82,
+ 0xa9, 0x7d, 0xa1, 0x80, 0x39, 0x7f, 0x31, 0x81, 0x71, 0x7e, 0xa5, 0x81, 0xad, 0x7d, 0x75, 0x80,
+ 0x39, 0x7f, 0xd1, 0x80, 0x75, 0x7e, 0x11, 0x81, 0xad, 0x7d, 0x45, 0x80, 0x39, 0x7f, 0x65, 0x80,
+ 0x71, 0x7e, 0x65, 0x80, 0xa1, 0x7d, 0x80, 0xf1, 0x7e, 0xd1, 0x7f, 0xf5, 0x7d, 0x75, 0x7f, 0x15,
+ 0x7d, 0xa5, 0x7f, 0x21, 0x7f, 0x21, 0x7f, 0x61, 0x7e, 0x71, 0x7e, 0xc5, 0x7d, 0x51, 0x7f, 0x65,
+ 0x7f, 0x79, 0x7e, 0xed, 0x7e, 0x79, 0x7d, 0x95, 0x7e, 0x7e, 0xad, 0x7f, 0xdd, 0x7d, 0x81, 0x7f,
+ 0x95, 0x7c, 0x81, 0x7f, 0xa1, 0x7e, 0x80, 0x61, 0x7d, 0x35, 0x80, 0x4d, 0x7c, 0xa5, 0x80, 0xed,
+ 0x7e, 0x71, 0x80, 0x05, 0x7e, 0x05, 0x81, 0x45, 0x7d, 0xc1, 0x81, 0x90, 0xb5, 0x7e, 0x9d, 0x81,
+ 0x51, 0x7e, 0x99, 0x82, 0xb0, 0xa5, 0x7f, 0xf1, 0x80, 0x75, 0x7f, 0xf5, 0x81, 0x71, 0x7f, 0x05,
+ 0x83, 0xe7, 0x49, 0x84, 0xbb, 0x05, 0x80, 0x61, 0x7f, 0x19, 0x80, 0xcd, 0x7e, 0x45, 0x80, 0x45,
+ 0x7e, 0x31, 0x80, 0x6d, 0x7f, 0x75, 0x80, 0xed, 0x7e, 0xd1, 0x80, 0x81, 0x7e, 0x5d, 0x80, 0x95,
+ 0x7f, 0xd1, 0x80, 0x45, 0x7f, 0x59, 0x81, 0x05, 0x7f, 0x8d, 0x80, 0xc5, 0x7f, 0x31, 0x81, 0xa9,
+ 0x7f, 0xf1, 0x81, 0xa9, 0x7f, 0x9d, 0x80, 0x80, 0x29, 0x81, 0x19, 0x80, 0xa1, 0x81, 0x51, 0x80,
+ 0x79, 0x80, 0x35, 0x80, 0xe1, 0x80, 0x7d, 0x80, 0x31, 0x81, 0xd9, 0x80, 0x51, 0x80, 0x5d, 0x80,
+ 0x91, 0x80, 0xcd, 0x80, 0xbd, 0x80, 0x4d, 0x81, 0x2d, 0x80, 0x81, 0x80, 0x41, 0x80, 0x0d, 0x81,
+ 0x41, 0x80, 0xa1, 0x81, 0x80, 0x71, 0x80, 0xf1, 0x7f, 0xe1, 0x80, 0xd5, 0x7f, 0x4d, 0x81, 0xe5,
+ 0x7f, 0x71, 0x80, 0xb5, 0x7f, 0xe9, 0x80, 0x6d, 0x7f, 0x69, 0x81, 0xb9, 0x7f, 0x81, 0x80, 0x59,
+ 0x7f, 0x0d, 0x81, 0xe5, 0x7e, 0xa9, 0x81, 0x8d, 0x7f, 0x99, 0x80, 0xf5, 0x7e, 0x4d, 0x81, 0x41,
+ 0x7e, 0x11, 0x82, 0x20, 0xa9, 0x77, 0x1d, 0x89, 0xe8, 0x98, 0xe6, 0xa4, 0xe9, 0x95, 0x7c, 0xe6,
+ 0x19, 0x86, 0xe2, 0x58, 0x7c, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x58, 0xe1,
+}
+
+var ImageExposurePlus1 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x90, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe3, 0xa8, 0xac, 0xe7, 0x78, 0xe8, 0xc1, 0x76, 0x20, 0x74,
+ 0x0d, 0x82, 0xe9, 0x99, 0x7c, 0x20, 0x69, 0x89, 0x99, 0x7c, 0xe7, 0x99, 0x80, 0xe9, 0xb4, 0xe1,
+}
+
+var ImageExposurePlus2 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x88, 0x95, 0x88,
+ 0x20, 0xbd, 0x85, 0xe1, 0x79, 0xb9, 0xc1, 0x80, 0x35, 0x7f, 0x71, 0x81, 0x71, 0x7e, 0x15, 0x82,
+ 0xa9, 0x7d, 0xa1, 0x80, 0x39, 0x7f, 0x31, 0x81, 0x71, 0x7e, 0xa5, 0x81, 0xad, 0x7d, 0x75, 0x80,
+ 0x39, 0x7f, 0xd1, 0x80, 0x75, 0x7e, 0x11, 0x81, 0xad, 0x7d, 0x45, 0x80, 0x39, 0x7f, 0x65, 0x80,
+ 0x71, 0x7e, 0x65, 0x80, 0xa1, 0x7d, 0x80, 0xf1, 0x7e, 0xd1, 0x7f, 0xf5, 0x7d, 0x75, 0x7f, 0x15,
+ 0x7d, 0xa5, 0x7f, 0x21, 0x7f, 0x21, 0x7f, 0x61, 0x7e, 0x71, 0x7e, 0xc5, 0x7d, 0x51, 0x7f, 0x65,
+ 0x7f, 0x79, 0x7e, 0xed, 0x7e, 0x79, 0x7d, 0x95, 0x7e, 0x7e, 0xad, 0x7f, 0xdd, 0x7d, 0x81, 0x7f,
+ 0x95, 0x7c, 0x81, 0x7f, 0xa1, 0x7e, 0x80, 0x61, 0x7d, 0x35, 0x80, 0x4d, 0x7c, 0xa5, 0x80, 0xed,
+ 0x7e, 0x71, 0x80, 0x05, 0x7e, 0x05, 0x81, 0x45, 0x7d, 0xc1, 0x81, 0x90, 0xb5, 0x7e, 0x9d, 0x81,
+ 0x51, 0x7e, 0x99, 0x82, 0xb0, 0xa5, 0x7f, 0xf1, 0x80, 0x75, 0x7f, 0xf5, 0x81, 0x71, 0x7f, 0x05,
+ 0x83, 0xe7, 0x49, 0x84, 0xbb, 0x05, 0x80, 0x61, 0x7f, 0x19, 0x80, 0xcd, 0x7e, 0x45, 0x80, 0x45,
+ 0x7e, 0x31, 0x80, 0x6d, 0x7f, 0x75, 0x80, 0xed, 0x7e, 0xd1, 0x80, 0x81, 0x7e, 0x5d, 0x80, 0x95,
+ 0x7f, 0xd1, 0x80, 0x45, 0x7f, 0x59, 0x81, 0x05, 0x7f, 0x8d, 0x80, 0xc5, 0x7f, 0x31, 0x81, 0xa9,
+ 0x7f, 0xf1, 0x81, 0xa9, 0x7f, 0x9d, 0x80, 0x80, 0x29, 0x81, 0x19, 0x80, 0xa1, 0x81, 0x51, 0x80,
+ 0x79, 0x80, 0x35, 0x80, 0xe1, 0x80, 0x7d, 0x80, 0x31, 0x81, 0xd9, 0x80, 0x51, 0x80, 0x5d, 0x80,
+ 0x91, 0x80, 0xcd, 0x80, 0xbd, 0x80, 0x4d, 0x81, 0x2d, 0x80, 0x81, 0x80, 0x41, 0x80, 0x0d, 0x81,
+ 0x41, 0x80, 0xa1, 0x81, 0x80, 0x71, 0x80, 0xf1, 0x7f, 0xe1, 0x80, 0xd5, 0x7f, 0x4d, 0x81, 0xe5,
+ 0x7f, 0x71, 0x80, 0xb5, 0x7f, 0xe9, 0x80, 0x6d, 0x7f, 0x69, 0x81, 0xb9, 0x7f, 0x81, 0x80, 0x59,
+ 0x7f, 0x0d, 0x81, 0xe5, 0x7e, 0xa9, 0x81, 0x8d, 0x7f, 0x99, 0x80, 0xf5, 0x7e, 0x4d, 0x81, 0x41,
+ 0x7e, 0x11, 0x82, 0x20, 0xa9, 0x77, 0x1d, 0x89, 0xe8, 0x98, 0xe6, 0xa8, 0xe9, 0x95, 0x7c, 0xe6,
+ 0x19, 0x88, 0xe2, 0x70, 0x6c, 0xe7, 0x78, 0xe9, 0x90, 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0x90, 0xe9,
+ 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe1,
+}
+
+var ImageExposureZero = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x49, 0x88, 0x82, 0xb7,
+ 0x80, 0x84, 0xcd, 0x7f, 0xb5, 0x83, 0x69, 0x7f, 0x19, 0x85, 0x9d, 0x7f, 0x69, 0x81, 0x0d, 0x7f,
+ 0x89, 0x82, 0x55, 0x7e, 0x69, 0x83, 0x49, 0x7f, 0xe1, 0x80, 0x69, 0x7e, 0x85, 0x81, 0x69, 0x7d,
+ 0xe9, 0x81, 0xfd, 0x7e, 0x65, 0x80, 0xdd, 0x7d, 0x99, 0x80, 0x9d, 0x7c, 0x99, 0x80, 0xc5, 0x7e,
+ 0x80, 0xa1, 0x7d, 0xcd, 0x7f, 0x9d, 0x7c, 0x69, 0x7f, 0xfd, 0x7e, 0x9d, 0x7f, 0x1d, 0x7e, 0xf9,
+ 0x7e, 0x61, 0x7d, 0x19, 0x7e, 0x45, 0x7f, 0x21, 0x7f, 0xb5, 0x7e, 0xfd, 0x7d, 0x51, 0x7e, 0x99,
+ 0x7c, 0x99, 0x7f, 0x99, 0x7e, 0x69, 0x7f, 0xe9, 0x7c, 0x69, 0x7f, 0xe9, 0x7a, 0xe9, 0xed, 0x7b,
+ 0xb7, 0x80, 0x7c, 0x35, 0x80, 0x4d, 0x7c, 0x99, 0x80, 0xe9, 0x7a, 0x65, 0x80, 0x9d, 0x7e, 0xf5,
+ 0x80, 0x7d, 0x7d, 0xb1, 0x81, 0xa1, 0x7c, 0xb9, 0x80, 0x25, 0x7f, 0x99, 0x81, 0x85, 0x7e, 0x9d,
+ 0x82, 0x21, 0x7e, 0x05, 0x81, 0xa1, 0x7f, 0x29, 0x82, 0x71, 0x7f, 0x65, 0x83, 0x71, 0x7f, 0x41,
+ 0x81, 0x80, 0x65, 0x82, 0x31, 0x80, 0x69, 0x83, 0x91, 0x80, 0x05, 0x81, 0x61, 0x80, 0xe5, 0x81,
+ 0x05, 0x81, 0xa1, 0x82, 0xe1, 0x81, 0xb9, 0x80, 0xdd, 0x80, 0x49, 0x81, 0xfd, 0x81, 0xb1, 0x81,
+ 0x61, 0x83, 0x65, 0x80, 0x65, 0x81, 0x99, 0x80, 0x19, 0x83, 0x99, 0x80, 0x19, 0x85, 0xe8, 0x82,
+ 0xe3, 0xc9, 0x7b, 0x45, 0x7b, 0xb7, 0x80, 0xb5, 0x7e, 0xe9, 0x7f, 0xa1, 0x7d, 0xbd, 0x7f, 0xc5,
+ 0x7c, 0xd1, 0x7f, 0x21, 0x7f, 0x91, 0x7f, 0x6d, 0x7e, 0x35, 0x7f, 0xe1, 0x7d, 0xa9, 0x7f, 0x75,
+ 0x7f, 0x3d, 0x7f, 0x11, 0x7f, 0xb9, 0x7e, 0xd9, 0x7e, 0x81, 0x7f, 0xc5, 0x7f, 0xed, 0x7e, 0xa5,
+ 0x7f, 0x45, 0x7e, 0xa5, 0x7f, 0x59, 0x7f, 0x80, 0xc5, 0x7e, 0x21, 0x80, 0x45, 0x7e, 0x5d, 0x80,
+ 0x7d, 0x7f, 0x3d, 0x80, 0x11, 0x7f, 0xa1, 0x80, 0xb9, 0x7e, 0x29, 0x81, 0xa9, 0x7f, 0x8d, 0x80,
+ 0x65, 0x7f, 0x41, 0x81, 0x35, 0x7f, 0x21, 0x82, 0xd1, 0x7f, 0xe1, 0x80, 0xbd, 0x7f, 0xf5, 0x81,
+ 0xbd, 0x7f, 0x3d, 0x83, 0xe9, 0x59, 0x85, 0xb4, 0x80, 0x45, 0x81, 0x19, 0x80, 0x5d, 0x82, 0x49,
+ 0x80, 0x41, 0x83, 0x31, 0x80, 0xe5, 0x80, 0x75, 0x80, 0xa1, 0x81, 0xcd, 0x80, 0x2d, 0x82, 0x59,
+ 0x80, 0x91, 0x80, 0xc9, 0x80, 0xf5, 0x80, 0x4d, 0x81, 0x39, 0x81, 0x85, 0x80, 0x41, 0x80, 0x19,
+ 0x81, 0x61, 0x80, 0xbd, 0x81, 0x61, 0x80, 0xa9, 0x80, 0x80, 0x3d, 0x81, 0xe1, 0x7f, 0xbd, 0x81,
+ 0xa1, 0x7f, 0x90, 0xed, 0x80, 0x59, 0x7f, 0x45, 0x81, 0xc9, 0x7e, 0xb1, 0x59, 0x80, 0x71, 0x7f,
+ 0x99, 0x80, 0xb9, 0x7e, 0xc5, 0x80, 0xd5, 0x7d, 0x2d, 0x80, 0x1d, 0x7f, 0x45, 0x80, 0x05, 0x7e,
+ 0x45, 0x80, 0xc1, 0x7c, 0xe9, 0xa9, 0x7a, 0xe1,
+}
+
+var ImageFilter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xf1, 0x87, 0x91, 0x7c,
+ 0x21, 0x81, 0x7a, 0x15, 0x87, 0x11, 0x7c, 0x49, 0x7b, 0x00, 0x72, 0x8c, 0xe7, 0xac, 0x20, 0xf1,
+ 0x78, 0x91, 0x76, 0xe2, 0x5c, 0x64, 0xe6, 0x54, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8,
+ 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageFilter1 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xac, 0xa8, 0xe7, 0x88, 0xe8, 0x64, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe9, 0xa0, 0xe2, 0xa4, 0x54, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageFilter2 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe3, 0x70, 0x70,
+ 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9,
+ 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90,
+ 0xe9, 0x88, 0xe7, 0x78, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x90, 0xe7,
+ 0x98, 0xe9, 0x78, 0xe1,
+}
+
+var ImageFilter3 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x54, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8,
+ 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe2, 0x5c, 0x64, 0xe6, 0x54, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x5c, 0xe8, 0x64, 0xe3, 0xb8, 0xa0,
+ 0xe9, 0x7a, 0xb1, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xa9, 0x81, 0x80, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0xe9, 0x7a, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x70,
+ 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x70,
+ 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe1,
+}
+
+var ImageFilter4 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xb0, 0xa8, 0xe7, 0x88, 0xe8, 0x64, 0xe7, 0x78, 0xe9, 0x90, 0xe7, 0x78,
+ 0xe9, 0x70, 0xe7, 0x78, 0xe9, 0x98, 0xe7, 0x90, 0xe9, 0x90, 0xe2, 0xa4, 0x54, 0xe6, 0x6c, 0xb0,
+ 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c,
+ 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageFilter5 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x54, 0xe6, 0x6c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8,
+ 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe2, 0x5c, 0x64, 0xe6, 0x54, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6, 0x5c, 0xe8, 0x64, 0xe3, 0xb8, 0xa0,
+ 0xe9, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x90, 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x98, 0xe7, 0x90, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0x88, 0xe7,
+ 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe1,
+}
+
+var ImageFilter6 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe3, 0x60, 0x78,
+ 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x78, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe1,
+}
+
+var ImageFilter7 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe3, 0x60, 0x78,
+ 0x20, 0x90, 0x60, 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x88, 0xe7, 0x90, 0x20, 0x70, 0xa0, 0xe7, 0x88,
+ 0xe1,
+}
+
+var ImageFilter8 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe3, 0x60, 0x78,
+ 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x7a, 0xb1, 0x80, 0x59,
+ 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe9, 0x7a,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x78, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x86, 0xb1, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86, 0x86, 0x59,
+ 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x86, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe3, 0x80, 0x60, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80, 0x90,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe1,
+}
+
+var ImageFilter9 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe2, 0x8c, 0x64,
+ 0xe7, 0x78, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x88, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var ImageFilter9Plus = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xac, 0x9c, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x84, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x84, 0xe9, 0x84, 0xe7, 0x74, 0xe9, 0x88, 0xe7,
+ 0x8c, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe3, 0x74, 0x74, 0xe9, 0x7c, 0xe7,
+ 0x84, 0xe9, 0x84, 0xe7, 0x7c, 0xe2, 0xa4, 0x54, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xa0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x98, 0xe6,
+ 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0x98, 0xe1,
+}
+
+var ImageFilterBAndW = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0x00, 0x80, 0x7c,
+ 0xe9, 0xa0, 0xe6, 0x64, 0x20, 0x9c, 0x60, 0xe8, 0x64, 0xe7, 0x9c, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageFilterCenterFocus = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x8c, 0xe6, 0x5c,
+ 0xe9, 0x90, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0xe9, 0x78, 0xe7,
+ 0x70, 0xe9, 0x70, 0xe3, 0x80, 0x58, 0xe7, 0x90, 0xe8, 0x5c, 0xe7, 0x70, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe3, 0xb8, 0x78, 0xe7, 0x70,
+ 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70, 0xe7, 0x78, 0xe9, 0x90, 0xe2, 0x80, 0x74, 0xb0, 0xb1,
+ 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x92, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x8c, 0x51, 0x7d,
+ 0x8c, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe1,
+}
+
+var ImageFilterDrama = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x39, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb5, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe9, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe2,
+ 0x9c, 0x98, 0xe6, 0x68, 0xb0, 0x99, 0x7b, 0x80, 0x70, 0x69, 0x7c, 0x70, 0x70, 0x91, 0x99, 0x83,
+ 0x70, 0x90, 0x70, 0x90, 0x99, 0x83, 0x90, 0x90, 0xe7, 0x88, 0xb0, 0x80, 0x7d, 0x7a, 0x45, 0x7c,
+ 0xd9, 0x75, 0x35, 0x77, 0x75, 0x74, 0xa0, 0x35, 0x79, 0xc5, 0x75, 0x69, 0x7c, 0x68, 0x80, 0x68,
+ 0xb0, 0x11, 0x86, 0x80, 0x96, 0xf1, 0x84, 0x96, 0x96, 0xe9, 0x82, 0xe7, 0x86, 0xb0, 0x51, 0x83,
+ 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x90, 0x51, 0x7d, 0x8c, 0x74, 0x8c, 0xe1,
+}
+
+var ImageFilterFrames = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe7, 0x70,
+ 0x21, 0x70, 0x70, 0x70, 0x90, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x60, 0xe8, 0x68, 0xe7, 0x09, 0x89, 0x21, 0x0d, 0x87, 0x72, 0xf5,
+ 0x86, 0x8e, 0xe6, 0xa0, 0xe9, 0xb8, 0xe3, 0x78, 0x50, 0xe6, 0x68, 0xe9, 0xa8, 0xe7, 0xb0, 0xe1,
+}
+
+var ImageFilterHDR = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x68, 0x21, 0x81,
+ 0x78, 0x94, 0xb5, 0x85, 0x99, 0x87, 0x00, 0x7e, 0x90, 0xb0, 0xa1, 0x7c, 0x81, 0x7b, 0x6e, 0x68,
+ 0x6e, 0x68, 0x00, 0x54, 0x98, 0xe7, 0xd8, 0x00, 0x88, 0x68, 0xe1,
+}
+
+var ImageFilterNone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe6, 0x54,
+ 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe8, 0x64, 0xe3, 0xc8, 0x70, 0xe6, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x6c, 0xe8, 0x5c, 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageFilterTiltShift = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x25, 0x70, 0xe8,
+ 0x19, 0x6c, 0xb0, 0xfd, 0x7b, 0x69, 0x80, 0x51, 0x78, 0x84, 0x59, 0x75, 0x6d, 0x84, 0x20, 0xd9,
+ 0x82, 0xd9, 0x82, 0xb0, 0x39, 0x82, 0x4d, 0x7e, 0xe1, 0x84, 0x21, 0x7d, 0xcd, 0x87, 0xc5, 0x7c,
+ 0xe3, 0xa9, 0x8e, 0x61, 0x80, 0xa0, 0xb1, 0x89, 0x19, 0x6e, 0x05, 0x86, 0x81, 0x6c, 0x84, 0x19,
+ 0x6c, 0xe9, 0x0d, 0x84, 0xb0, 0xed, 0x82, 0x61, 0x80, 0x95, 0x85, 0x85, 0x81, 0xcd, 0x87, 0x3d,
+ 0x83, 0x20, 0xd9, 0x82, 0x25, 0x7d, 0xe2, 0xdd, 0x8f, 0x7c, 0xe7, 0x0d, 0x84, 0xb0, 0x99, 0x7f,
+ 0xfd, 0x7b, 0x7c, 0x51, 0x78, 0x95, 0x7b, 0x59, 0x75, 0x20, 0x29, 0x7d, 0xd9, 0x82, 0xb0, 0xb5,
+ 0x81, 0x39, 0x82, 0xe1, 0x82, 0xe1, 0x84, 0x3d, 0x83, 0xcd, 0x87, 0xe3, 0x85, 0x63, 0x35, 0x78,
+ 0x20, 0x29, 0x7d, 0x29, 0x7d, 0xa0, 0x19, 0x6e, 0x51, 0x76, 0x81, 0x6c, 0xfd, 0x79, 0x19, 0x6c,
+ 0x7c, 0xe7, 0x0d, 0x84, 0xb0, 0x61, 0x80, 0x15, 0x7d, 0x85, 0x81, 0x6d, 0x7a, 0x3d, 0x83, 0x35,
+ 0x78, 0xe2, 0x25, 0x70, 0x84, 0xe6, 0x19, 0x6c, 0xb0, 0x69, 0x80, 0x05, 0x84, 0x84, 0xb1, 0x87,
+ 0x6d, 0x84, 0xa9, 0x8a, 0x20, 0xd9, 0x82, 0x29, 0x7d, 0xb0, 0x4d, 0x7e, 0xc9, 0x7d, 0x25, 0x7d,
+ 0x1d, 0x7b, 0xc5, 0x7c, 0x35, 0x78, 0xe2, 0x8c, 0x80, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74,
+ 0x74, 0x74, 0x92, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x8c, 0x51, 0x7d,
+ 0x8c, 0x74, 0xe3, 0xa1, 0x86, 0xcd, 0x89, 0x20, 0xd9, 0x82, 0xd9, 0x82, 0xa0, 0xe9, 0x91, 0xb1,
+ 0x89, 0x81, 0x93, 0x05, 0x86, 0xe9, 0x93, 0x84, 0xe7, 0xf5, 0x7b, 0xb0, 0xa5, 0x7f, 0xe9, 0x82,
+ 0x7d, 0x7e, 0x95, 0x85, 0xc5, 0x7c, 0xcd, 0x87, 0xe2, 0x84, 0xdd, 0x8f, 0xe9, 0x0d, 0x84, 0xb0,
+ 0x05, 0x84, 0x99, 0x7f, 0xb1, 0x87, 0x7c, 0xa9, 0x8a, 0x95, 0x7b, 0x20, 0x29, 0x7d, 0x29, 0x7d,
+ 0xb0, 0xc9, 0x7d, 0xb5, 0x81, 0x21, 0x7b, 0xdd, 0x82, 0x35, 0x78, 0x3d, 0x83, 0xe3, 0x59, 0x71,
+ 0xa1, 0x7f, 0xa0, 0x51, 0x76, 0xe9, 0x91, 0xfd, 0x79, 0x81, 0x93, 0x7c, 0xe9, 0x93, 0xe9, 0xf5,
+ 0x7b, 0xb0, 0x15, 0x7d, 0xa1, 0x7f, 0x6d, 0x7a, 0x7d, 0x7e, 0x35, 0x78, 0xc5, 0x7c, 0x20, 0x29,
+ 0x7d, 0xdd, 0x82, 0xe1,
+}
+
+var ImageFilterVintage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x65, 0x8d, 0xcd, 0x80,
+ 0xbf, 0x71, 0x7f, 0xb1, 0x7f, 0xdd, 0x7e, 0x6d, 0x7f, 0x49, 0x7e, 0x35, 0x7f, 0x95, 0x80, 0xc9,
+ 0x7f, 0x29, 0x81, 0x85, 0x7f, 0xb9, 0x81, 0x35, 0x7f, 0xd9, 0x83, 0xc9, 0x7d, 0xfd, 0x85, 0xc1,
+ 0x79, 0xfd, 0x85, 0x9d, 0x75, 0x69, 0x7c, 0xf1, 0x7d, 0xd9, 0x77, 0xc9, 0x7d, 0x05, 0x74, 0x80,
+ 0x71, 0x7f, 0x51, 0x80, 0xf1, 0x7e, 0xb1, 0x80, 0x71, 0x7e, 0x19, 0x81, 0x19, 0x80, 0x61, 0x7f,
+ 0x2d, 0x80, 0xc1, 0x7e, 0x2d, 0x80, 0x19, 0x7e, 0x80, 0x91, 0x7b, 0x95, 0x7d, 0xb1, 0x77, 0x74,
+ 0xa1, 0x75, 0x6d, 0x7c, 0x11, 0x82, 0x74, 0xf1, 0x85, 0x74, 0x61, 0x8a, 0x80, 0xa9, 0x80, 0x11,
+ 0x80, 0x45, 0x81, 0x29, 0x80, 0xe5, 0x81, 0x85, 0x7f, 0x9d, 0x7f, 0x7e, 0x3d, 0x7f, 0x71, 0x7e,
+ 0xe9, 0x7e, 0x29, 0x7c, 0xc9, 0x7d, 0x99, 0x77, 0xf1, 0x7d, 0x05, 0x74, 0x80, 0x05, 0x80, 0x25,
+ 0x84, 0x29, 0x82, 0x2d, 0x88, 0xfd, 0x85, 0x65, 0x8a, 0x91, 0x80, 0x51, 0x80, 0x25, 0x81, 0x95,
+ 0x80, 0xb9, 0x81, 0xcd, 0x80, 0x6d, 0x7f, 0x39, 0x80, 0xd9, 0x7e, 0x7d, 0x80, 0x49, 0x7e, 0xcd,
+ 0x80, 0x29, 0x7c, 0x39, 0x82, 0x05, 0x7a, 0x41, 0x86, 0x05, 0x7a, 0x65, 0x8a, 0x99, 0x83, 0x11,
+ 0x82, 0x29, 0x88, 0x39, 0x82, 0xfd, 0x8b, 0x80, 0xb7, 0x91, 0x80, 0xb1, 0x7f, 0x11, 0x81, 0x51,
+ 0x7f, 0x91, 0x81, 0xe9, 0x7e, 0xe9, 0x7f, 0xa5, 0x80, 0xd9, 0x7f, 0x45, 0x81, 0xd9, 0x7f, 0xe9,
+ 0x81, 0x80, 0x71, 0x84, 0x6d, 0x82, 0x51, 0x88, 0x8c, 0x61, 0x8a, 0x95, 0x83, 0xed, 0x7d, 0x8c,
+ 0x11, 0x7a, 0x8c, 0xa1, 0x75, 0x80, 0x59, 0x7f, 0xf1, 0x7f, 0xbd, 0x7e, 0xd9, 0x7f, 0x1d, 0x7e,
+ 0x7d, 0x80, 0x65, 0x80, 0x82, 0xc5, 0x80, 0x91, 0x81, 0x19, 0x81, 0xd9, 0x83, 0x39, 0x82, 0x69,
+ 0x88, 0x11, 0x82, 0xfd, 0x8b, 0x80, 0xfd, 0x7f, 0xd9, 0x7b, 0xd9, 0x7d, 0xd1, 0x77, 0x74, 0x99,
+ 0x75, 0xe2, 0x80, 0x90, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x92, 0x95, 0x83,
+ 0x70, 0x90, 0x70, 0x90, 0x95, 0x83, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70, 0x90, 0xe1,
+}
+
+var ImageFlare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x7c, 0xe6, 0x54,
+ 0xe9, 0x88, 0xe7, 0x98, 0xe9, 0x78, 0xe3, 0x59, 0x84, 0x85, 0x79, 0x23, 0xc5, 0x7b, 0xc5, 0x7b,
+ 0x2d, 0x7d, 0xd5, 0x82, 0x3d, 0x84, 0x3d, 0x84, 0xd5, 0x82, 0x2d, 0x7d, 0xe2, 0x84, 0x54, 0xe7,
+ 0x78, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0x54, 0xe3, 0xbd, 0x8a, 0x19, 0x8c, 0x23, 0x2d, 0x7d, 0x2d,
+ 0x7d, 0xc5, 0x7b, 0x3d, 0x84, 0xd5, 0x82, 0xd5, 0x82, 0x3d, 0x84, 0xc5, 0x7b, 0xe2, 0x94, 0x7c,
+ 0xe9, 0x88, 0xe7, 0x98, 0xe9, 0x78, 0xe6, 0x94, 0xe3, 0x6c, 0x78, 0xb0, 0xb1, 0x7c, 0x80, 0x74,
+ 0xb1, 0x82, 0x74, 0x8c, 0x92, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x51,
+ 0x7d, 0x74, 0x74, 0x74, 0xe3, 0xa9, 0x85, 0x7d, 0x8e, 0x23, 0x3d, 0x84, 0x3d, 0x84, 0xd5, 0x82,
+ 0x2d, 0x7d, 0xc5, 0x7b, 0xc5, 0x7b, 0x2d, 0x7d, 0xd5, 0x82, 0xe2, 0x45, 0x73, 0xe9, 0x89, 0x23,
+ 0xd5, 0x82, 0xd5, 0x82, 0x3d, 0x84, 0xc5, 0x7b, 0x2d, 0x7d, 0x2d, 0x7d, 0xc5, 0x7b, 0x3d, 0x84,
+ 0xe2, 0x7c, 0xac, 0xe7, 0x88, 0xe8, 0x94, 0xe7, 0x78, 0xe9, 0x98, 0xe1,
+}
+
+var ImageFlashAuto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x58, 0xe9, 0xb0,
+ 0xe7, 0x8c, 0xe9, 0xa4, 0x20, 0x9c, 0x50, 0xe7, 0x70, 0x20, 0x90, 0x5c, 0xe6, 0x5c, 0xe3, 0xc0,
+ 0x80, 0xe7, 0x78, 0x20, 0x99, 0x79, 0xa4, 0xe7, 0xcd, 0x83, 0x20, 0x69, 0x81, 0x78, 0xe7, 0x69,
+ 0x86, 0x20, 0x69, 0x81, 0x88, 0xe7, 0xcd, 0x83, 0x00, 0x9c, 0x58, 0xe3, 0xb5, 0x7b, 0x4d, 0x8b,
+ 0x00, 0x98, 0x60, 0x20, 0x4d, 0x82, 0x4d, 0x87, 0xe7, 0x69, 0x7b, 0xe1,
+}
+
+var ImageFlashOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8d, 0x6e, 0x5c, 0x00,
+ 0x58, 0x8d, 0x70, 0x20, 0x94, 0x94, 0xe8, 0x84, 0xe7, 0x8c, 0xe9, 0xa4, 0x20, 0x2d, 0x87, 0xb5,
+ 0x73, 0x02, 0x75, 0x8b, 0xa0, 0x9c, 0x75, 0x8d, 0x8d, 0x6e, 0x5c, 0xe2, 0x94, 0x78, 0xe7, 0x70,
+ 0x20, 0x90, 0x60, 0xe6, 0x6c, 0xe9, 0x5d, 0x84, 0x20, 0xed, 0x90, 0xed, 0x90, 0x00, 0x94, 0x78,
+ 0xe1,
+}
+
+var ImageFlashOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x58, 0xe9, 0xac,
+ 0xe7, 0x8c, 0xe9, 0xa4, 0x20, 0x9c, 0x50, 0xe7, 0x70, 0x20, 0x90, 0x60, 0xe1,
+}
+
+var ImageFlip = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0xa4, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0x50, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe2, 0x5c, 0x64, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe8, 0x64, 0xe7, 0x90, 0xe8, 0x5c, 0xe7, 0x70, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe3, 0xc0, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x7c, 0xac, 0xe7, 0x88, 0xe8, 0x54, 0xe7, 0x78, 0xe9,
+ 0xd8, 0xe3, 0xa0, 0x68, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x70, 0x50, 0xe7,
+ 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x90, 0xa0, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe3, 0x80, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe1,
+}
+
+var ImageGradient = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x74, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe7, 0x78, 0xe3, 0x78, 0x88, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe3, 0x90, 0x80,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe3, 0x88, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe3,
+ 0x60, 0x80, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe2, 0x9c, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d,
+ 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x98, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x88, 0x64, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe8, 0x64, 0xe7, 0xb8, 0xe9, 0x98, 0xe1,
+}
+
+var ImageGrain = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x80, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x80, 0xa0, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0xb0, 0x60, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x70, 0xa0, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x90, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x70, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0x70, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ImageGridOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x60, 0xe9, 0xe9,
+ 0x82, 0x20, 0x88, 0x88, 0xe8, 0x60, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x19, 0x79, 0x20, 0x88, 0x88,
+ 0xe6, 0x88, 0xe9, 0xe9, 0x82, 0x20, 0x88, 0x88, 0xe8, 0x78, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x19,
+ 0x79, 0x20, 0x88, 0x88, 0xe6, 0xa0, 0xe9, 0xe9, 0x82, 0x20, 0x88, 0x88, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x19, 0x71, 0x20, 0x88, 0x88, 0xe6, 0x70, 0xe3,
+ 0xa0, 0x80, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x70, 0xe8, 0x60, 0xe2, 0x8d, 0x6a, 0x8d, 0x6a, 0x00,
+ 0x50, 0x19, 0x6d, 0x20, 0x88, 0x88, 0xe8, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xe9, 0x9e, 0x22, 0x88, 0x88, 0x8d, 0x82, 0x75, 0x7d, 0x19, 0x55, 0x19, 0x55, 0xe2,
+ 0x78, 0x19, 0x81, 0x00, 0xe9, 0x7e, 0x88, 0xe6, 0x78, 0xe9, 0x19, 0x7d, 0xe3, 0x68, 0x68, 0x00,
+ 0xe9, 0x72, 0x70, 0xe6, 0x60, 0xe9, 0x19, 0x7d, 0xe2, 0x70, 0xa0, 0xe6, 0x60, 0xe9, 0x70, 0xe7,
+ 0x90, 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe6, 0x60, 0xe9, 0x70, 0xe7, 0xe9, 0x86, 0x00, 0x70, 0x19,
+ 0x7d, 0xe8, 0x88, 0xe3, 0x98, 0x98, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0xe9, 0x86, 0x00, 0x88, 0x19,
+ 0x89, 0xe8, 0xa0, 0xe3, 0x88, 0x80, 0xe9, 0x19, 0x7d, 0x00, 0xe9, 0x8a, 0xa0, 0xe6, 0x90, 0xe1,
+}
+
+var ImageGridOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x70, 0xa0, 0xe6, 0x60, 0xe9,
+ 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe6, 0x60, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90,
+ 0xe3, 0x80, 0x68, 0xe6, 0x60, 0xe8, 0x60, 0xe7, 0x90, 0xe9, 0x90, 0xe3, 0x98, 0xb0, 0xe7, 0x70,
+ 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0x90, 0xe9,
+ 0x90, 0xe3, 0x80, 0x68, 0xe7, 0x70, 0xe8, 0x60, 0xe7, 0x90, 0xe9, 0x90, 0xe3, 0x98, 0xb0, 0xe7,
+ 0x70, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0x90,
+ 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe7, 0x70, 0xe8, 0x60, 0xe7, 0x90, 0xe9, 0x90, 0xe1,
+}
+
+var ImageHDROff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x96, 0x8c, 0xe9, 0x78,
+ 0xe7, 0x4d, 0x82, 0x20, 0xb5, 0x81, 0x88, 0xe7, 0x86, 0x20, 0x35, 0x7e, 0xcd, 0x7b, 0xb0, 0x19,
+ 0x81, 0x81, 0x7f, 0xcd, 0x81, 0x81, 0x7e, 0xcd, 0x81, 0x35, 0x7d, 0xe9, 0x7c, 0xb0, 0x80, 0x4d,
+ 0x7e, 0xb5, 0x7e, 0x7a, 0x7a, 0x7a, 0xe7, 0x72, 0xe9, 0xcd, 0x89, 0x20, 0x35, 0x82, 0x35, 0x82,
+ 0xe7, 0xcd, 0x80, 0xe3, 0x80, 0x6e, 0xe7, 0x88, 0xe9, 0x84, 0xe7, 0x78, 0xe9, 0x7c, 0xe3, 0x6e,
+ 0x80, 0xe9, 0xcd, 0x80, 0x20, 0x86, 0x86, 0xe8, 0x7a, 0xb0, 0x80, 0x4d, 0x7e, 0xb5, 0x7e, 0x7a,
+ 0x7a, 0x7a, 0xe7, 0x35, 0x7c, 0x20, 0x86, 0x86, 0xe7, 0xcd, 0x80, 0xe3, 0x72, 0x7c, 0x01, 0xe9,
+ 0x6c, 0xe9, 0x6c, 0xcd, 0x6a, 0x5e, 0x20, 0x96, 0x96, 0xe6, 0x6a, 0xe9, 0x88, 0xe6, 0x62, 0xe9,
+ 0x78, 0xe6, 0x5c, 0xe9, 0x98, 0xe7, 0x86, 0xe9, 0x76, 0xe7, 0x88, 0xe9, 0x8a, 0xe7, 0x86, 0xe9,
+ 0x35, 0x76, 0x20, 0x86, 0x86, 0xe8, 0x8c, 0xe7, 0xcd, 0x86, 0x00, 0xa2, 0x35, 0x95, 0x20, 0x19,
+ 0x82, 0xe9, 0x7d, 0x00, 0x76, 0x76, 0xe1,
+}
+
+var ImageHDROn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x7e, 0xe9, 0x7c,
+ 0xb0, 0x80, 0x4d, 0x7e, 0xb5, 0x7e, 0x7a, 0x7a, 0x7a, 0xe7, 0x72, 0xe9, 0x98, 0xe7, 0x86, 0xe9,
+ 0x78, 0xe7, 0x4d, 0x82, 0x20, 0xb5, 0x81, 0x88, 0xe7, 0x86, 0x20, 0x35, 0x7e, 0xcd, 0x7b, 0xb0,
+ 0x82, 0x81, 0x7f, 0xcd, 0x81, 0x69, 0x7e, 0xcd, 0x81, 0x35, 0x7d, 0xe3, 0x7a, 0x80, 0xe7, 0x78,
+ 0xe9, 0x7c, 0xe7, 0x88, 0xe9, 0x84, 0xe3, 0x4c, 0x7e, 0xe6, 0x62, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x98, 0xe7, 0x86, 0xe9, 0x76, 0xe7, 0x88, 0xe9, 0x8a, 0xe7, 0x86, 0xe8, 0x74, 0xe7, 0x7a, 0xe9,
+ 0x88, 0xe3, 0x9a, 0x78, 0xe7, 0x72, 0xe9, 0x98, 0xe7, 0x8e, 0xb0, 0xb5, 0x81, 0x80, 0x86, 0xb5,
+ 0x7e, 0x86, 0x7a, 0xe9, 0x74, 0xb0, 0x80, 0x4d, 0x7e, 0xb5, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0x80,
+ 0x92, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe1,
+}
+
+var ImageHDRStrong = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x68, 0xb0, 0x61,
+ 0x79, 0x80, 0x68, 0x61, 0x85, 0x68, 0x98, 0x92, 0x61, 0x85, 0x98, 0x98, 0x98, 0x98, 0xa1, 0x7a,
+ 0x98, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x68, 0xe3, 0x50, 0x88, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x95,
+ 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70, 0x6d, 0x7c,
+ 0x70, 0x70, 0x70, 0xe3, 0x80, 0x98, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92,
+ 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe1,
+}
+
+var ImageHDRWeak = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x70, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe3, 0xb0, 0x78, 0xb0, 0x61, 0x79, 0x80, 0x68, 0x61,
+ 0x85, 0x68, 0x98, 0x92, 0x61, 0x85, 0x98, 0x98, 0x98, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0xa1, 0x7a,
+ 0x68, 0x68, 0x68, 0xe3, 0x80, 0xa8, 0xb0, 0x95, 0x7b, 0x80, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x92,
+ 0x95, 0x83, 0x70, 0x90, 0x70, 0x90, 0x95, 0x83, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70, 0x90, 0xe1,
+}
+
+var ImageHealing = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x75, 0x8b, 0x0d, 0x80,
+ 0x20, 0xf5, 0x87, 0x0d, 0x78, 0xb0, 0xc9, 0x80, 0x39, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0x80, 0x2d,
+ 0x7d, 0x20, 0x55, 0x77, 0x55, 0x77, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f, 0x2d,
+ 0x7d, 0x80, 0x20, 0x0d, 0x78, 0xf5, 0x87, 0x00, 0x70, 0x99, 0x6c, 0xb1, 0x9d, 0x7f, 0x9d, 0x7f,
+ 0x19, 0x7f, 0x69, 0x7f, 0x99, 0x7e, 0x69, 0x7f, 0x7d, 0x7f, 0x80, 0xfd, 0x7e, 0x35, 0x80, 0x99,
+ 0x7e, 0x99, 0x80, 0x00, 0x81, 0x6c, 0x45, 0x75, 0xb0, 0x39, 0x7f, 0xc9, 0x80, 0x39, 0x7f, 0x0d,
+ 0x82, 0x80, 0xd5, 0x82, 0x20, 0xf5, 0x87, 0xf5, 0x87, 0x00, 0x81, 0x6c, 0x90, 0xb0, 0x39, 0x7f,
+ 0xc9, 0x80, 0x39, 0x7f, 0x0d, 0x82, 0x80, 0xd5, 0x82, 0x20, 0xad, 0x88, 0xad, 0x88, 0xb0, 0xc9,
+ 0x80, 0xc9, 0x80, 0x0d, 0x82, 0xc9, 0x80, 0xd5, 0x82, 0x80, 0x21, 0xf5, 0x87, 0x0d, 0x78, 0xf5,
+ 0x87, 0xf5, 0x87, 0xb1, 0x65, 0x80, 0x65, 0x80, 0xe9, 0x80, 0x99, 0x80, 0x69, 0x81, 0x99, 0x80,
+ 0x85, 0x80, 0x80, 0x05, 0x81, 0xcd, 0x7f, 0x69, 0x81, 0x69, 0x7f, 0x20, 0xad, 0x88, 0x55, 0x77,
+ 0xb0, 0xc9, 0x80, 0x39, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0x80, 0x2d, 0x7d, 0x20, 0x0d, 0x78, 0x0d,
+ 0x78, 0xe2, 0x80, 0x74, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f,
+ 0x84, 0x7c, 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x95, 0x76,
+ 0xed, 0x83, 0x23, 0xc1, 0x78, 0xc1, 0x78, 0x45, 0x87, 0xbd, 0x78, 0x41, 0x87, 0x41, 0x87, 0xbd,
+ 0x78, 0x45, 0x87, 0xe2, 0x78, 0x84, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x92,
+ 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe3,
+ 0x88, 0x88, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x92, 0xe9, 0x80, 0x7c, 0x84,
+ 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe3, 0x88, 0x70, 0xb0, 0x19,
+ 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x55, 0x85, 0xad, 0x92, 0x23, 0xc1, 0x78, 0xc1,
+ 0x78, 0x45, 0x87, 0xbd, 0x78, 0x41, 0x87, 0x41, 0x87, 0xbd, 0x78, 0x45, 0x87, 0xe1,
+}
+
+var ImageImage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x9c, 0xe8, 0x64,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe2, 0x72, 0x86, 0x20, 0x8a, 0x05,
+ 0x86, 0x00, 0x8a, 0x80, 0x20, 0x92, 0x98, 0xe6, 0x64, 0x20, 0x8e, 0x6e, 0xe1,
+}
+
+var ImageImageAspectRatio = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x78, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x60, 0x70, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x90, 0x80, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe2, 0xa0, 0x60, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x60, 0xe8, 0x68, 0xe7, 0xc0, 0xe9,
+ 0xb0, 0xe1,
+}
+
+var ImageISO = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x4a, 0x92, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe7, 0x86, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x86, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7a, 0xe9,
+ 0x78, 0xe7, 0x78, 0xe9, 0x7a, 0xe3, 0xb6, 0xae, 0xe6, 0x64, 0x20, 0xb8, 0x48, 0xe9, 0xb8, 0xe3,
+ 0x78, 0x78, 0xe9, 0x7a, 0xe6, 0x80, 0xe9, 0x86, 0xe7, 0x94, 0xe1,
+}
+
+var ImageLandscape = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x68, 0x21, 0x81,
+ 0x78, 0x94, 0xb5, 0x85, 0x99, 0x87, 0x00, 0x7e, 0x90, 0xb0, 0xa1, 0x7c, 0x81, 0x7b, 0x6e, 0x68,
+ 0x6e, 0x68, 0x00, 0x54, 0x98, 0xe7, 0xd8, 0x00, 0x88, 0x68, 0xe1,
+}
+
+var ImageLeakAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x5c, 0xe6, 0x5c,
+ 0xe9, 0x8c, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe3, 0xa0, 0x80, 0xe7, 0x78,
+ 0xb0, 0x80, 0xf1, 0x89, 0xf1, 0x77, 0xa4, 0x5c, 0xa4, 0xe9, 0x88, 0xb0, 0x29, 0x8c, 0x80, 0xac,
+ 0x29, 0x76, 0xac, 0x54, 0xe3, 0x70, 0x80, 0xe7, 0x78, 0xb0, 0x80, 0x85, 0x85, 0x85, 0x7b, 0x94,
+ 0x6c, 0x94, 0xe9, 0x88, 0xb0, 0xbd, 0x87, 0x80, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0xe3, 0x80, 0xc8,
+ 0xe7, 0x88, 0xb0, 0x80, 0x11, 0x76, 0x11, 0x88, 0x5c, 0xa4, 0x5c, 0xe9, 0x78, 0xb0, 0xd9, 0x73,
+ 0x80, 0x54, 0xd9, 0x89, 0x54, 0xac, 0xe3, 0xa0, 0x80, 0xe7, 0x8c, 0xe9, 0x74, 0xb0, 0xb1, 0x7c,
+ 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe3, 0x70, 0x80, 0xe7, 0x88, 0xb0, 0x80, 0x7d, 0x7a, 0x7d,
+ 0x84, 0x6c, 0x94, 0x6c, 0xe9, 0x78, 0xb0, 0x45, 0x78, 0x80, 0x64, 0x45, 0x86, 0x64, 0x9c, 0xe1,
+}
+
+var ImageLeakRemove = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x5c, 0xe7, 0x78,
+ 0xb0, 0x80, 0xbd, 0x80, 0xe9, 0x7f, 0x71, 0x81, 0xc5, 0x7f, 0x21, 0x82, 0x20, 0x31, 0x83, 0x31,
+ 0x83, 0xa0, 0xa1, 0x7b, 0xad, 0x71, 0x78, 0xe1, 0x6f, 0x78, 0x5c, 0xe2, 0x5c, 0x8d, 0x70, 0x20,
+ 0xb1, 0x85, 0xb1, 0x85, 0xa0, 0x11, 0x72, 0x59, 0x77, 0x1d, 0x70, 0x70, 0x5c, 0x70, 0xe9, 0x88,
+ 0xb0, 0x39, 0x83, 0x80, 0x2d, 0x86, 0xe9, 0x7e, 0x89, 0x88, 0x15, 0x7d, 0x20, 0xd9, 0x82, 0xd9,
+ 0x82, 0xa0, 0x4d, 0x76, 0x79, 0x7e, 0x51, 0x72, 0x80, 0x5c, 0x80, 0xe9, 0x88, 0xb0, 0x71, 0x85,
+ 0x80, 0x65, 0x8a, 0x09, 0x7e, 0x39, 0x8e, 0xc5, 0x7a, 0x20, 0x05, 0x85, 0x05, 0x85, 0xa0, 0xf9,
+ 0x7d, 0x9d, 0x87, 0x78, 0x91, 0x8c, 0x78, 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0xb1, 0x7b, 0x89, 0x81,
+ 0xb5, 0x77, 0x11, 0x84, 0x9d, 0x74, 0x20, 0xd9, 0x82, 0xd9, 0x82, 0xa0, 0x19, 0x85, 0xd5, 0x8b,
+ 0x88, 0xc9, 0x8e, 0x88, 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0xe5, 0x7d, 0xa9, 0x80, 0xf1, 0x7b, 0xc9,
+ 0x81, 0x51, 0x7a, 0x03, 0x75, 0x8f, 0xa4, 0xa4, 0x75, 0x8f, 0x8d, 0x70, 0x5c, 0x5c, 0x8d, 0x70,
+ 0xe2, 0x88, 0x5c, 0xe7, 0x78, 0xb0, 0x80, 0x86, 0x41, 0x7f, 0xd5, 0x85, 0xf5, 0x7d, 0x51, 0x88,
+ 0x20, 0xf1, 0x82, 0xf1, 0x82, 0xa0, 0xd9, 0x82, 0xf5, 0x75, 0x88, 0x21, 0x72, 0x88, 0x5c, 0xe3,
+ 0xe1, 0x8b, 0x3d, 0x9a, 0xb0, 0xb1, 0x80, 0xd9, 0x7f, 0x69, 0x81, 0xc5, 0x7f, 0x21, 0x82, 0xc5,
+ 0x7f, 0xe9, 0x78, 0xb0, 0x21, 0x7e, 0x80, 0x55, 0x7c, 0x61, 0x80, 0xb1, 0x7a, 0x0d, 0x81, 0x20,
+ 0x31, 0x83, 0x31, 0x83, 0xe3, 0xe1, 0x76, 0xe1, 0x76, 0x20, 0xf1, 0x82, 0xf1, 0x82, 0xa0, 0x2d,
+ 0x8c, 0xc1, 0x80, 0x9e, 0x80, 0xa4, 0x80, 0xe9, 0x78, 0xb0, 0xe1, 0x7b, 0x80, 0x0d, 0x78, 0x29,
+ 0x81, 0xc1, 0x74, 0x21, 0x83, 0xe1,
+}
+
+var ImageLens = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe1,
+}
+
+var ImageLinkedCamera = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0xa9, 0x6e, 0xb0,
+ 0x29, 0x85, 0x80, 0x59, 0x89, 0x31, 0x84, 0x59, 0x89, 0x59, 0x89, 0xe6, 0xa8, 0xb0, 0x80, 0x61,
+ 0x79, 0xa1, 0x7a, 0x68, 0x68, 0x68, 0xe9, 0xa9, 0x82, 0xe2, 0x90, 0x68, 0xb0, 0x39, 0x82, 0x80,
+ 0x88, 0xc9, 0x81, 0x88, 0x88, 0xe7, 0xa9, 0x82, 0xb0, 0x80, 0x51, 0x7c, 0x05, 0x7d, 0x59, 0x79,
+ 0x59, 0x79, 0x59, 0x79, 0xe8, 0x68, 0xe2, 0x99, 0x79, 0x88, 0xd1, 0x69, 0x86, 0x69, 0x86, 0x00,
+ 0x04, 0xcd, 0x8c, 0x80, 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0x35, 0x73, 0x80, 0xe1, 0xc0, 0x94,
+ 0x74, 0xb0, 0x80, 0xc9, 0x7d, 0x39, 0x7e, 0x78, 0x78, 0x78, 0xe8, 0x60, 0xe6, 0x74, 0x20, 0x59,
+ 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x74, 0xe6, 0x94, 0xe2, 0x80, 0x9c, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85,
+ 0x7b, 0x6c, 0x6c, 0x92, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b,
+ 0x94, 0x6c, 0x94, 0xe1,
+}
+
+var ImageLooks = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x78, 0xb0, 0x49,
+ 0x78, 0x80, 0x64, 0x49, 0x86, 0x64, 0x9c, 0xe7, 0x88, 0xb0, 0x80, 0x7d, 0x7a, 0x7d, 0x84, 0x6c,
+ 0x94, 0x6c, 0x90, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xe7, 0x88, 0xb0, 0x80, 0x49, 0x78, 0xb9, 0x79,
+ 0x64, 0x64, 0x64, 0xe3, 0x80, 0x70, 0xa0, 0xe1, 0x73, 0x68, 0x54, 0xe1, 0x7d, 0x54, 0x94, 0xe7,
+ 0x88, 0xb0, 0x80, 0x15, 0x76, 0x15, 0x88, 0x5c, 0xa4, 0x5c, 0x90, 0xa4, 0x15, 0x88, 0xa4, 0xa4,
+ 0xe7, 0x88, 0xb0, 0x80, 0xe1, 0x73, 0x21, 0x76, 0x54, 0x54, 0x54, 0xe1,
+}
+
+var ImageLooks3 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x8e, 0x5c, 0xe7,
+ 0x48, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0x9e, 0xb1, 0x80,
+ 0xa9, 0x81, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xa9, 0x81, 0x80, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe9,
+ 0x86, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x86, 0xe1,
+}
+
+var ImageLooks4 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0xb8, 0xe7, 0x78, 0xe9,
+ 0x70, 0xe7, 0x70, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x88, 0xe9,
+ 0xa8, 0xe1,
+}
+
+var ImageLooks5 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0x98, 0xe7, 0x70, 0xe9,
+ 0x88, 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x88, 0xb0, 0x80,
+ 0x35, 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe7, 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x78, 0xe7,
+ 0x70, 0xe8, 0x6c, 0xe7, 0x98, 0xe9, 0x88, 0xe1,
+}
+
+var ImageLooks6 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x8c, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe2, 0x9c, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0x98, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x88, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x88, 0xb0, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88,
+ 0x78, 0x88, 0xe7, 0x78, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe8, 0x74, 0xb0,
+ 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var ImageLooksOne = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x88, 0x94, 0xe7, 0x78, 0xe8,
+ 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0xa8, 0xe1,
+}
+
+var ImageLooksTwo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x70, 0xa0, 0xb0, 0x80, 0x35,
+ 0x82, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x88, 0xe6, 0x74,
+ 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x88,
+ 0xe1,
+}
+
+var ImageLoupe = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x6c, 0xe7, 0x78,
+ 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x70, 0xe9, 0x70, 0xe2, 0x80, 0x58, 0xa0, 0xf9, 0x74, 0x58, 0x58, 0xf9, 0x74,
+ 0x58, 0x80, 0x90, 0xf9, 0x88, 0xa8, 0xa8, 0xa8, 0xe7, 0xa0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x80, 0xb0, 0x80, 0xf9, 0x74, 0x09, 0x77, 0x58, 0x58, 0x58, 0xe3, 0x80,
+ 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60, 0xd1, 0x78, 0x60, 0x60, 0x80, 0x31, 0x77, 0x60, 0x80, 0x60,
+ 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0, 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ImageMonochromePhotos = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0xe9, 0x99,
+ 0x83, 0xb0, 0x81, 0x83, 0x80, 0x69, 0x86, 0xe9, 0x82, 0x69, 0x86, 0x69, 0x86, 0x90, 0x19, 0x7d,
+ 0x69, 0x86, 0x99, 0x79, 0x69, 0x86, 0xe8, 0x98, 0xb0, 0x81, 0x85, 0x80, 0x94, 0x81, 0x7b, 0x94,
+ 0x6c, 0x90, 0x81, 0x7b, 0x6c, 0x6c, 0x6c, 0xe3, 0x99, 0x79, 0x94, 0xb0, 0x80, 0x81, 0x83, 0xe9,
+ 0x82, 0x69, 0x86, 0x69, 0x86, 0x69, 0x86, 0xe8, 0x99, 0x7b, 0xb0, 0x81, 0x7c, 0x80, 0x99, 0x79,
+ 0xe9, 0x82, 0x99, 0x79, 0x69, 0x86, 0xe2, 0x80, 0x70, 0xe9, 0x99, 0x83, 0xb0, 0x81, 0x83, 0x80,
+ 0x69, 0x86, 0xe9, 0x82, 0x69, 0x86, 0x69, 0x86, 0x90, 0x19, 0x7d, 0x69, 0x86, 0x99, 0x79, 0x69,
+ 0x86, 0xe8, 0x98, 0xb0, 0x81, 0x85, 0x80, 0x94, 0x81, 0x7b, 0x94, 0x6c, 0x90, 0x81, 0x7b, 0x6c,
+ 0x6c, 0x6c, 0xe3, 0x99, 0x79, 0x94, 0xb0, 0x80, 0x81, 0x83, 0xe9, 0x82, 0x69, 0x86, 0x69, 0x86,
+ 0x69, 0x86, 0xe8, 0x99, 0x7b, 0xb0, 0x81, 0x7c, 0x80, 0x99, 0x79, 0xe9, 0x82, 0x99, 0x79, 0x69,
+ 0x86, 0xe2, 0xa0, 0x64, 0xe7, 0xb5, 0x79, 0x00, 0x8c, 0x5c, 0xe6, 0x74, 0x20, 0x4d, 0x7c, 0x88,
+ 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x6c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6,
+ 0x80, 0xe9, 0x7c, 0xb0, 0x81, 0x7a, 0x80, 0x6c, 0x81, 0x7b, 0x6c, 0x6c, 0x90, 0x81, 0x84, 0x6c,
+ 0x94, 0x6c, 0xe9, 0x7c, 0xe7, 0xa0, 0xe9, 0xb0, 0xe3, 0x74, 0x68, 0xb0, 0x80, 0x81, 0x7a, 0x81,
+ 0x7b, 0x6c, 0x6c, 0x6c, 0xe9, 0x99, 0x83, 0xb0, 0x81, 0x83, 0x80, 0x69, 0x86, 0xe9, 0x82, 0x69,
+ 0x86, 0x69, 0x86, 0x90, 0x19, 0x7d, 0x69, 0x86, 0x99, 0x79, 0x69, 0x86, 0xe8, 0x98, 0xb0, 0x81,
+ 0x85, 0x80, 0x94, 0x81, 0x7b, 0x94, 0x6c, 0xe3, 0x99, 0x6f, 0x80, 0xb0, 0x80, 0x81, 0x83, 0xe9,
+ 0x82, 0x69, 0x86, 0x69, 0x86, 0x69, 0x86, 0xe8, 0x99, 0x7b, 0xb0, 0x81, 0x7c, 0x80, 0x99, 0x79,
+ 0xe9, 0x82, 0x99, 0x79, 0x69, 0x86, 0xe3, 0x80, 0x80, 0xb0, 0x80, 0x81, 0x83, 0xe9, 0x82, 0x69,
+ 0x86, 0x69, 0x86, 0x69, 0x86, 0xe8, 0x99, 0x7b, 0xb0, 0x81, 0x7c, 0x80, 0x99, 0x79, 0xe9, 0x82,
+ 0x99, 0x79, 0x69, 0x86, 0xe1,
+}
+
+var ImageMovieCreation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0x20, 0x88,
+ 0x90, 0xe7, 0x74, 0x20, 0x78, 0x70, 0xe7, 0x78, 0x20, 0x88, 0x90, 0xe7, 0x74, 0x20, 0x78, 0x70,
+ 0xe7, 0x78, 0x20, 0x88, 0x90, 0xe7, 0x74, 0x20, 0x78, 0x70, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60,
+ 0xe7, 0x70, 0xe1,
+}
+
+var ImageMovieFilter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x60, 0x20, 0x88,
+ 0x8c, 0xe7, 0x74, 0x20, 0x78, 0x74, 0xe7, 0x78, 0x20, 0x88, 0x8c, 0xe7, 0x74, 0x20, 0x78, 0x74,
+ 0xe7, 0x78, 0x20, 0x88, 0x8c, 0xe7, 0x74, 0x20, 0x78, 0x74, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60,
+ 0xe7, 0x70, 0xe2, 0x81, 0x7e, 0x81, 0x86, 0x00, 0x78, 0x98, 0x20, 0x81, 0x7d, 0x81, 0x7a, 0x00,
+ 0x68, 0x88, 0x20, 0x81, 0x85, 0x81, 0x7d, 0x00, 0x78, 0x78, 0x20, 0x81, 0x82, 0x81, 0x85, 0x00,
+ 0x88, 0x88, 0x20, 0x81, 0x7a, 0x81, 0x82, 0xe3, 0x61, 0x8b, 0x61, 0x79, 0x00, 0x90, 0x88, 0x20,
+ 0x21, 0x7e, 0xe1, 0x7b, 0x00, 0x84, 0x7c, 0x20, 0x21, 0x84, 0x21, 0x7e, 0x00, 0x90, 0x70, 0x20,
+ 0xe1, 0x81, 0x21, 0x84, 0x00, 0x9c, 0x7c, 0x20, 0xe1, 0x7b, 0xe1, 0x81, 0xe1,
+}
+
+var ImageMusicNote = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x5c, 0xe9, 0x1d,
+ 0x95, 0xb1, 0xd1, 0x7e, 0x51, 0x7f, 0x75, 0x7d, 0xe5, 0x7e, 0x78, 0xe5, 0x7e, 0x95, 0x7b, 0x80,
+ 0x70, 0x95, 0x83, 0x70, 0x90, 0x91, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c, 0x90, 0x70,
+ 0xe8, 0x6c, 0xe7, 0x90, 0xe8, 0x5c, 0xe6, 0x80, 0xe1,
+}
+
+var ImageNature = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x3d, 0x88, 0xb1,
+ 0xf5, 0x86, 0x31, 0x7f, 0x59, 0x8c, 0x49, 0x79, 0x59, 0x8c, 0x1d, 0x72, 0x80, 0x45, 0x78, 0xbd,
+ 0x79, 0x64, 0x64, 0x64, 0x90, 0x64, 0x45, 0x86, 0x64, 0x9c, 0xb0, 0x80, 0xf1, 0x86, 0x0d, 0x85,
+ 0xad, 0x8c, 0xa9, 0x8b, 0xcd, 0x8d, 0xe8, 0xa0, 0xe6, 0x64, 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78,
+ 0xe6, 0x84, 0xe9, 0x3d, 0x78, 0xe1,
+}
+
+var ImageNaturePeople = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x59, 0x94, 0x59, 0x7a,
+ 0xb0, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0x90, 0x64, 0x45, 0x86, 0x64, 0x9c, 0xb0,
+ 0x80, 0xf1, 0x86, 0x0d, 0x85, 0xad, 0x8c, 0xa9, 0x8b, 0xcd, 0x8d, 0xe8, 0xa0, 0xe6, 0x68, 0xe9,
+ 0x74, 0xe7, 0x84, 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x5c,
+ 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xe7, 0x84, 0xe9, 0x94, 0xe7,
+ 0xc0, 0xe9, 0x78, 0xe7, 0x74, 0xe9, 0x3d, 0x78, 0xb0, 0xf5, 0x86, 0x31, 0x7f, 0x59, 0x8c, 0x45,
+ 0x79, 0x59, 0x8c, 0x19, 0x72, 0xe2, 0x62, 0x7c, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86,
+ 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86,
+ 0x86, 0xe1,
+}
+
+var ImageNavigateBefore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd5, 0x86, 0xd5, 0x76,
+ 0x01, 0x88, 0x68, 0x70, 0x80, 0x21, 0x98, 0x98, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0xa9, 0x7d, 0x80,
+ 0xe1,
+}
+
+var ImageNavigateNext = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x68, 0x20, 0x2d,
+ 0x7d, 0xd5, 0x82, 0x00, 0x59, 0x82, 0x80, 0x20, 0xd5, 0x76, 0x2d, 0x89, 0x00, 0x78, 0x98, 0x20,
+ 0x98, 0x68, 0xe1,
+}
+
+var ImagePalette = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x5c, 0xa0, 0x11,
+ 0x76, 0x5c, 0x5c, 0x11, 0x76, 0x5c, 0x80, 0x90, 0x11, 0x88, 0xa4, 0xa4, 0xa4, 0xb3, 0xa9, 0x81,
+ 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x80, 0x39, 0x7f, 0xb5, 0x7f, 0x85, 0x7e, 0x39, 0x7f, 0xfd,
+ 0x7d, 0x89, 0x7f, 0x79, 0x7f, 0x41, 0x7f, 0xc9, 0x7e, 0x41, 0x7f, 0x05, 0x7e, 0x80, 0x59, 0x7e,
+ 0x59, 0x81, 0x7a, 0x86, 0x7a, 0xe6, 0x90, 0xb1, 0x85, 0x85, 0x80, 0x94, 0x85, 0x7b, 0x94, 0x6c,
+ 0x80, 0x29, 0x77, 0xf1, 0x77, 0x60, 0x5c, 0x60, 0xe2, 0x6a, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a,
+ 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8c, 0x70, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86,
+ 0xe3, 0x94, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a,
+ 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8c, 0x90, 0xb0,
+ 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var ImagePanorama = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x98, 0xe8, 0x68,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe2, 0x72, 0x82, 0x20, 0x8a, 0x05,
+ 0x86, 0x00, 0x8a, 0x7c, 0x20, 0x92, 0x98, 0xe6, 0x64, 0x20, 0x8e, 0x6e, 0xe1,
+}
+
+var ImagePanoramaFishEye = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x31, 0x77, 0x80, 0x60,
+ 0xd1, 0x78, 0x60, 0x60, 0x80, 0x31, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x31, 0x87, 0xa0, 0xa0,
+ 0xd1, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ImagePanoramaHorizontal = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x19, 0x75, 0xe9,
+ 0xd1, 0x95, 0xb1, 0xd1, 0x7a, 0x75, 0x7e, 0x71, 0x75, 0xb1, 0x7d, 0x60, 0xb1, 0x7d, 0x91, 0x7a,
+ 0x80, 0x35, 0x75, 0xc9, 0x80, 0x60, 0x51, 0x82, 0xe8, 0x19, 0x75, 0xb1, 0x31, 0x85, 0x8d, 0x81,
+ 0x91, 0x8a, 0x51, 0x82, 0xa0, 0x51, 0x82, 0x71, 0x85, 0x80, 0xcd, 0x8a, 0x39, 0x7f, 0xa0, 0xb1,
+ 0x7d, 0xe2, 0xdd, 0x92, 0x60, 0xb3, 0xd1, 0x7f, 0x80, 0x99, 0x7f, 0x0d, 0x80, 0x61, 0x7f, 0x21,
+ 0x80, 0x21, 0x7a, 0x31, 0x82, 0xf1, 0x73, 0x4d, 0x83, 0xc5, 0x6d, 0x4d, 0x83, 0xd1, 0x79, 0x80,
+ 0xa5, 0x73, 0xe9, 0x7e, 0xc5, 0x6d, 0xb5, 0x7c, 0xc9, 0x7f, 0xed, 0x7f, 0x91, 0x7f, 0xe1, 0x7f,
+ 0x61, 0x7f, 0xe1, 0x7f, 0xa0, 0x79, 0x6c, 0x60, 0x58, 0x79, 0x70, 0x58, 0x41, 0x71, 0xe9, 0x81,
+ 0x9d, 0xb5, 0x05, 0x80, 0xc9, 0x80, 0x79, 0x80, 0x41, 0x81, 0x21, 0x81, 0x41, 0x81, 0x31, 0x80,
+ 0x80, 0x69, 0x80, 0xf5, 0x7f, 0xa1, 0x80, 0xe1, 0x7f, 0xe1, 0x85, 0xd1, 0x7d, 0x11, 0x8c, 0xb5,
+ 0x7c, 0x3d, 0x92, 0xb5, 0x7c, 0x31, 0x86, 0x80, 0x5d, 0x8c, 0x19, 0x81, 0x3d, 0x92, 0x4d, 0x83,
+ 0x39, 0x80, 0x15, 0x80, 0x71, 0x80, 0x21, 0x80, 0xa1, 0x80, 0x21, 0x80, 0xa9, 0x80, 0x80, 0x25,
+ 0x81, 0x89, 0x7f, 0x21, 0x81, 0xc1, 0x7e, 0xe8, 0x41, 0x71, 0xa0, 0xa8, 0x79, 0x70, 0x85, 0x93,
+ 0x60, 0xdd, 0x92, 0x60, 0xe1,
+}
+
+var ImagePanoramaVertical = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe1, 0x8f, 0x3d, 0x92,
+ 0xb2, 0xd1, 0x7d, 0x21, 0x7a, 0xb5, 0x7c, 0xf1, 0x73, 0xb5, 0x7c, 0xc5, 0x6d, 0x80, 0xd1, 0x79,
+ 0x19, 0x81, 0xa5, 0x73, 0x4d, 0x83, 0xc5, 0x6d, 0x15, 0x80, 0xc9, 0x7f, 0x21, 0x80, 0x91, 0x7f,
+ 0x21, 0x80, 0x61, 0x7f, 0xa0, 0xa0, 0x79, 0x6c, 0x89, 0x8f, 0x58, 0xc1, 0x8e, 0x58, 0xe6, 0x41,
+ 0x71, 0xa0, 0x79, 0x70, 0x05, 0x6c, 0x60, 0x79, 0x6c, 0x60, 0x21, 0x6d, 0xb4, 0x80, 0x31, 0x80,
+ 0x0d, 0x80, 0x69, 0x80, 0x21, 0x80, 0xa1, 0x80, 0x31, 0x82, 0xe1, 0x85, 0x4d, 0x83, 0x11, 0x8c,
+ 0x4d, 0x83, 0x3d, 0x92, 0x80, 0x31, 0x86, 0xe9, 0x7e, 0x5d, 0x8c, 0xb5, 0x7c, 0x3d, 0x92, 0xed,
+ 0x7f, 0x3d, 0x80, 0xe1, 0x7f, 0x71, 0x80, 0xe1, 0x7f, 0xa5, 0x80, 0x80, 0xa9, 0x80, 0x79, 0x80,
+ 0x21, 0x81, 0x41, 0x81, 0x21, 0x81, 0xe7, 0x81, 0x9d, 0xb1, 0xc9, 0x80, 0x80, 0x41, 0x81, 0x85,
+ 0x7f, 0x41, 0x81, 0xdd, 0x7e, 0x80, 0xd1, 0x7f, 0xf5, 0x7f, 0x99, 0x7f, 0xe1, 0x7f, 0x61, 0x7f,
+ 0xe2, 0x19, 0x75, 0xa0, 0xb1, 0x8d, 0x81, 0xd1, 0x7a, 0x51, 0x82, 0x71, 0x75, 0x51, 0x82, 0x60,
+ 0x80, 0x91, 0x7a, 0x39, 0x7f, 0x35, 0x75, 0xb1, 0x7d, 0x60, 0xe7, 0xd1, 0x95, 0xb1, 0x75, 0x7e,
+ 0x31, 0x85, 0xb1, 0x7d, 0x91, 0x8a, 0xb1, 0x7d, 0xa0, 0x80, 0x71, 0x85, 0xc9, 0x80, 0xcd, 0x8a,
+ 0x51, 0x82, 0xa0, 0xe6, 0x19, 0x75, 0xe1,
+}
+
+var ImagePanoramaWideAngle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x68, 0xb0, 0xe9,
+ 0x84, 0x80, 0x6d, 0x89, 0x65, 0x80, 0x95, 0x8e, 0x45, 0x81, 0xa0, 0x85, 0x8f, 0xd9, 0x78, 0xa0,
+ 0x75, 0x7c, 0xa0, 0x80, 0xb0, 0x80, 0x8d, 0x83, 0x85, 0x7f, 0x29, 0x87, 0x95, 0x7e, 0xbd, 0x8a,
+ 0xa0, 0x6d, 0x89, 0x9d, 0x8b, 0xe9, 0x84, 0x98, 0x80, 0x98, 0x90, 0x95, 0x76, 0x9d, 0x7f, 0x6d,
+ 0x71, 0xbd, 0x7e, 0xa0, 0x7d, 0x70, 0x29, 0x87, 0x60, 0x8d, 0x83, 0x60, 0x80, 0xb0, 0x80, 0x75,
+ 0x7c, 0x7d, 0x80, 0xd9, 0x78, 0x6d, 0x81, 0x45, 0x75, 0xa0, 0x95, 0x76, 0x65, 0x74, 0x19, 0x7b,
+ 0x68, 0x80, 0x68, 0xe3, 0x80, 0x78, 0xb0, 0x8d, 0x7a, 0x80, 0x8d, 0x75, 0x7d, 0x80, 0x19, 0x70,
+ 0x71, 0x81, 0x21, 0x29, 0x7e, 0x55, 0x80, 0x81, 0x7f, 0xcd, 0x81, 0xa0, 0x95, 0x6c, 0xb5, 0x77,
+ 0x58, 0xd9, 0x7b, 0x58, 0x80, 0x90, 0x95, 0x80, 0x4d, 0x88, 0xbd, 0x81, 0x71, 0x8c, 0x21, 0x81,
+ 0x80, 0xcd, 0x81, 0xd9, 0x81, 0x55, 0x80, 0xa0, 0x8d, 0x75, 0x85, 0x8f, 0x8d, 0x7a, 0xa0, 0x80,
+ 0xa0, 0x90, 0x75, 0x8a, 0x85, 0x7f, 0xe9, 0x8f, 0x91, 0x7e, 0x21, 0xd9, 0x81, 0xad, 0x7f, 0x81,
+ 0x80, 0x35, 0x7e, 0xa0, 0x6d, 0x93, 0x4d, 0x88, 0xa8, 0x29, 0x84, 0xa8, 0x80, 0x90, 0x6d, 0x7f,
+ 0xb5, 0x77, 0x45, 0x7e, 0x91, 0x73, 0x21, 0x81, 0x7f, 0x35, 0x7e, 0x29, 0x7e, 0xad, 0x7f, 0xa0,
+ 0x75, 0x8a, 0x7d, 0x70, 0x75, 0x85, 0x60, 0x80, 0x60, 0xe1,
+}
+
+var ImagePhoto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x9c, 0xe8, 0x64,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe2, 0x72, 0x86, 0x20, 0x8a, 0x05,
+ 0x86, 0x00, 0x8a, 0x80, 0x20, 0x92, 0x98, 0xe6, 0x64, 0x20, 0x8e, 0x6e, 0xe1,
+}
+
+var ImagePhotoAlbum = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x58, 0xe6, 0x68,
+ 0xa0, 0xcd, 0x71, 0x58, 0x60, 0xcd, 0x6d, 0x60, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0x60, 0xe7, 0x94, 0xe9,
+ 0xa0, 0x21, 0x76, 0x7a, 0x76, 0x86, 0xe8, 0x60, 0xe3, 0x80, 0xbc, 0x22, 0x8c, 0x4d, 0x78, 0x4d,
+ 0x84, 0x29, 0x85, 0x8c, 0x45, 0x78, 0x00, 0x98, 0x9c, 0xe6, 0x68, 0xe1,
+}
+
+var ImagePhotoCamera = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x58, 0x20, 0x59,
+ 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x58, 0xe6, 0x74, 0xe3, 0x8c, 0xbc, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b,
+ 0x6c, 0x6c, 0x92, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b, 0x94,
+ 0x6c, 0x94, 0xe2, 0x99, 0x79, 0x80, 0xd1, 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0xcd, 0x8c, 0x80,
+ 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0x35, 0x73, 0x80, 0xe1,
+}
+
+var ImagePhotoFilter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x0d, 0x8e, 0x78, 0xe9,
+ 0xa4, 0xe6, 0x64, 0xe8, 0x64, 0xe7, 0xa4, 0xe8, 0x5c, 0xe6, 0x0d, 0x72, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x78, 0xe7, 0x78, 0xe2,
+ 0x94, 0x78, 0x20, 0xe1, 0x81, 0xe1, 0x7b, 0x00, 0xa0, 0x6c, 0x20, 0xe1, 0x7b, 0x21, 0x7e, 0x00,
+ 0x94, 0x60, 0x20, 0x21, 0x7e, 0x21, 0x84, 0x00, 0x88, 0x6c, 0x20, 0x21, 0x84, 0xe1, 0x81, 0xe3,
+ 0x81, 0x78, 0x81, 0x81, 0x00, 0x80, 0x70, 0x20, 0x81, 0x7d, 0x81, 0x85, 0x00, 0x70, 0x80, 0x20,
+ 0x81, 0x85, 0x81, 0x82, 0x00, 0x80, 0x90, 0x20, 0x81, 0x82, 0x81, 0x7a, 0x00, 0x90, 0x80, 0xe1,
+}
+
+var ImagePhotoLibrary = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x90, 0xe8, 0x60,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe3, 0x54, 0x70, 0x20, 0x11, 0x84,
+ 0x6d, 0x85, 0x00, 0x90, 0x7c, 0x20, 0x90, 0x94, 0xe6, 0x70, 0x20, 0x8c, 0x70, 0xe2, 0x58, 0x68,
+ 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe6, 0x58, 0xe1,
+}
+
+var ImagePhotoSizeSelectActual = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xa0, 0x58, 0x5c, 0x54, 0x60, 0x54, 0x64, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x84, 0x80, 0x88, 0x7c, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0x7c,
+ 0x7c, 0x78, 0x78, 0x78, 0xe2, 0x64, 0x94, 0x21, 0x8e, 0x6e, 0x8a, 0x05, 0x86, 0x00, 0x8a, 0x7c,
+ 0x20, 0x92, 0x98, 0xe6, 0x64, 0xe1,
+}
+
+var ImagePhotoSizeSelectLarge = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x8c, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe3, 0x80, 0x70, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe9,
+ 0x78, 0xe3, 0x88, 0xa0, 0xe7, 0x78, 0xe9, 0x88, 0xb0, 0x84, 0x80, 0x88, 0x7c, 0x88, 0x78, 0xe2,
+ 0x84, 0x5c, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe8, 0x5c, 0xe3, 0xa0, 0x90, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe7, 0x78, 0xe8, 0x6c, 0xe3, 0x80, 0x70, 0xe9, 0x88, 0xe7, 0x88, 0xb0, 0x80, 0x7c, 0x7c,
+ 0x78, 0x78, 0x78, 0xe2, 0x54, 0x6c, 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x54, 0xe8, 0x6c, 0xe3, 0xc0,
+ 0x70, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0x78, 0xe8, 0x5c, 0xe3, 0x80, 0xc0, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe2, 0x5c, 0x5c, 0xa0, 0x58, 0x5c, 0x54, 0x60, 0x54, 0x64, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe3, 0x98, 0x80, 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x74, 0xe8, 0x5c, 0xe2, 0x64, 0x5c,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe6, 0x64, 0xe8, 0x5c, 0xe3, 0x70, 0xa0, 0xe9, 0xa0, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xe8, 0x7c, 0xe6, 0x54, 0xe3, 0x88, 0xa0, 0x22,
+ 0x8a, 0x95, 0x79, 0x95, 0x83, 0x4d, 0x84, 0x8a, 0x91, 0x79, 0x00, 0x84, 0x9c, 0xe6, 0x5c, 0xe1,
+}
+
+var ImagePhotoSizeSelectSmall = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xac, 0x8c, 0xe7, 0x78,
+ 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9,
+ 0x78, 0xe3, 0x80, 0xa0, 0xe7, 0x78, 0xe9, 0x88, 0xb0, 0x84, 0x80, 0x88, 0x7c, 0x88, 0x78, 0xe2,
+ 0x8c, 0x5c, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0xa0, 0x90, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe7, 0x88, 0xe8, 0x6c, 0xe3, 0x78, 0x70, 0xe9, 0x88, 0xe7, 0x88, 0xb0, 0x80, 0x7c, 0x7c,
+ 0x78, 0x78, 0x78, 0xe2, 0x5c, 0xa4, 0xe7, 0xa0, 0xe9, 0x68, 0xe6, 0x54, 0xe9, 0x90, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe2, 0x5c, 0x6c, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe8, 0x6c, 0xe3, 0xb0, 0xb0, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x90, 0x40,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x80, 0xc0, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe2, 0x5c, 0x5c, 0xa0, 0x58, 0x5c, 0x54, 0x60, 0x54, 0x64, 0xe7, 0x88, 0xe8,
+ 0x5c, 0xe3, 0x80, 0xa0, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0xa0, 0x60, 0xe6,
+ 0x74, 0xe9, 0x88, 0xe7, 0x88, 0xe8, 0x5c, 0xe2, 0x6c, 0x5c, 0xe6, 0x64, 0xe9, 0x88, 0xe7, 0x88,
+ 0xe8, 0x5c, 0xe1,
+}
+
+var ImagePictureAsPDF = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x70,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x7e, 0x76, 0xb0, 0x80, 0xa9,
+ 0x81, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe7, 0x7c, 0xe9, 0x88, 0xe7, 0x7a, 0xe8, 0x6c, 0xe7, 0x8a,
+ 0xb0, 0xa9, 0x81, 0x80, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe9, 0x84, 0xe3, 0x94, 0x88, 0xb0, 0x80,
+ 0xa9, 0x81, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe7, 0x76, 0xe8, 0x6c, 0xe7, 0x8a, 0xb0, 0xa9, 0x81,
+ 0x80, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe9, 0x8c, 0xe3, 0x90, 0x74, 0xe7, 0x7a, 0xe9, 0x84, 0xe7,
+ 0x86, 0xe9, 0x86, 0xe7, 0x7a, 0xe9, 0x88, 0xe7, 0x7a, 0xe8, 0x6c, 0xe7, 0x8c, 0xe9, 0x86, 0xe3,
+ 0x52, 0x84, 0xe7, 0x84, 0xe9, 0x7c, 0xe7, 0x7c, 0xe9, 0x84, 0xe2, 0x60, 0x68, 0xe6, 0x58, 0xe9,
+ 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x60,
+ 0xe8, 0x68, 0xe3, 0xa8, 0x96, 0xe7, 0x84, 0xe9, 0x74, 0xe7, 0x7c, 0xe9, 0x8c, 0xe1,
+}
+
+var ImagePortrait = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x81, 0x80, 0xb1,
+ 0x7d, 0x82, 0x80, 0x81, 0x84, 0xfd, 0x7d, 0x81, 0x84, 0x81, 0x7b, 0x80, 0x85, 0x7d, 0xfd, 0x7d,
+ 0x81, 0x7b, 0x81, 0x7b, 0x81, 0x7b, 0x90, 0x81, 0x7b, 0x05, 0x82, 0x81, 0x7b, 0x81, 0x84, 0xb0,
+ 0x80, 0x7d, 0x82, 0x05, 0x82, 0x81, 0x84, 0x81, 0x84, 0x81, 0x84, 0xe3, 0x92, 0x90, 0xb0, 0x80,
+ 0x7a, 0x74, 0x81, 0x7b, 0x6e, 0x81, 0x7b, 0x90, 0x6e, 0x81, 0x81, 0x6e, 0x81, 0x84, 0xe8, 0x94,
+ 0xe7, 0xa4, 0xe9, 0x81, 0x7e, 0xe2, 0x9c, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x64, 0xe8, 0x64, 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageRemoveRedEye = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x62, 0xa0, 0x6c,
+ 0x62, 0x75, 0x6d, 0x39, 0x77, 0x54, 0x80, 0xb0, 0x75, 0x83, 0xc9, 0x88, 0x98, 0x9e, 0xac, 0x9e,
+ 0x90, 0x8d, 0x92, 0xc9, 0x79, 0xac, 0x62, 0xa0, 0x8d, 0x92, 0x39, 0x77, 0x05, 0x8a, 0x62, 0x80,
+ 0x62, 0xe3, 0x80, 0xb2, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b, 0x6c, 0x6c, 0x92, 0x7d, 0x84,
+ 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b, 0x94, 0x6c, 0x94, 0xe3, 0x80, 0x60,
+ 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x92, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x51, 0x7d, 0x8c, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x74, 0xe1,
+}
+
+var ImageRotate90DegreesCCW = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb1, 0x76, 0xd5, 0x74,
+ 0x00, 0xb9, 0x69, 0xcd, 0x81, 0x20, 0xf9, 0x8c, 0xf9, 0x8c, 0x01, 0xa9, 0x83, 0xcd, 0x81, 0xb1,
+ 0x76, 0xd5, 0x74, 0xe2, 0x61, 0x6f, 0xcd, 0x81, 0x20, 0x51, 0x87, 0xb1, 0x78, 0x00, 0x7c, 0xcd,
+ 0x81, 0x21, 0xb1, 0x78, 0x51, 0x87, 0xb1, 0x78, 0xb1, 0x78, 0xe3, 0x5d, 0x9f, 0x79, 0x73, 0xa0,
+ 0x35, 0x8b, 0xc5, 0x71, 0x9d, 0x86, 0x60, 0x84, 0x60, 0xe8, 0x85, 0x69, 0x01, 0x85, 0x79, 0x64,
+ 0x84, 0x7d, 0x7a, 0xe8, 0x68, 0xb3, 0x95, 0x83, 0x80, 0x2d, 0x87, 0x61, 0x81, 0xe9, 0x89, 0x19,
+ 0x84, 0x79, 0x85, 0x79, 0x85, 0x79, 0x85, 0x55, 0x8e, 0x80, 0xcd, 0x93, 0x45, 0x7d, 0xbd, 0x82,
+ 0xb1, 0x79, 0x19, 0x84, 0x19, 0x76, 0x19, 0x84, 0x11, 0x7e, 0x80, 0x21, 0x7c, 0x99, 0x7f, 0x55,
+ 0x7a, 0xcd, 0x7e, 0x20, 0x05, 0x7d, 0xfd, 0x82, 0xa0, 0x09, 0x7c, 0x41, 0x93, 0x05, 0x7f, 0xa8,
+ 0x84, 0xa8, 0xb1, 0x9d, 0x84, 0x80, 0x35, 0x89, 0x3d, 0x7e, 0xbd, 0x8c, 0xbd, 0x7a, 0x09, 0x87,
+ 0xf9, 0x78, 0x09, 0x87, 0x91, 0x6d, 0x80, 0x8d, 0x66, 0xe1,
+}
+
+var ImageRotateLeft = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x39, 0x76, 0x0d, 0x79,
+ 0x20, 0x2d, 0x7d, 0x2d, 0x7d, 0xb0, 0x35, 0x7e, 0x51, 0x82, 0x19, 0x7d, 0x8a, 0xc1, 0x7c, 0xc9,
+ 0x87, 0xe7, 0x0d, 0x84, 0xb0, 0x4d, 0x80, 0x41, 0x7e, 0xf9, 0x80, 0x91, 0x7c, 0x0d, 0x82, 0x0d,
+ 0x7b, 0xe2, 0x31, 0x74, 0x84, 0xe6, 0x25, 0x70, 0xb0, 0x59, 0x80, 0xc9, 0x82, 0x75, 0x81, 0x75,
+ 0x85, 0x41, 0x83, 0xc9, 0x87, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0xb0, 0xf1, 0x7e, 0x7d, 0x7e, 0x41,
+ 0x7e, 0xcd, 0x7c, 0xf5, 0x7d, 0x0d, 0x7b, 0xe3, 0x05, 0x82, 0xa1, 0x8a, 0xb0, 0x51, 0x82, 0xd1,
+ 0x81, 0x05, 0x85, 0xe1, 0x82, 0xcd, 0x87, 0x39, 0x83, 0xe9, 0xf5, 0x7b, 0xb0, 0x41, 0x7e, 0xb5,
+ 0x7f, 0x91, 0x7c, 0x05, 0x7f, 0x11, 0x7b, 0xf5, 0x7d, 0x20, 0x21, 0x7d, 0xe1, 0x82, 0xe2, 0x84,
+ 0x25, 0x70, 0xe8, 0x54, 0x20, 0xe9, 0x76, 0x19, 0x89, 0x00, 0x84, 0x78, 0xe9, 0x31, 0x78, 0xb0,
+ 0xad, 0x85, 0xf5, 0x80, 0x94, 0xe1, 0x85, 0x94, 0xd1, 0x8b, 0x90, 0xad, 0x7b, 0xe1, 0x8a, 0x6c,
+ 0xd1, 0x8b, 0xe9, 0x0d, 0x84, 0xb0, 0xe5, 0x87, 0x05, 0x7f, 0x9c, 0x4d, 0x78, 0x9c, 0x25, 0x70,
+ 0x80, 0xe5, 0x89, 0x21, 0x71, 0x84, 0x25, 0x70, 0xe1,
+}
+
+var ImageRotateRight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x87, 0x19, 0x73,
+ 0x00, 0x7c, 0x54, 0xe9, 0x25, 0x86, 0xa0, 0x1d, 0x76, 0x21, 0x71, 0x60, 0xd9, 0x77, 0x60, 0x80,
+ 0x90, 0x1d, 0x86, 0xe1, 0x8e, 0x9c, 0xdd, 0x8f, 0xe9, 0xf5, 0x7b, 0xb0, 0x55, 0x7a, 0x0d, 0x7f,
+ 0x6c, 0x21, 0x7a, 0x6c, 0x31, 0x74, 0x90, 0x55, 0x84, 0x21, 0x75, 0x94, 0x31, 0x74, 0xe8, 0x78,
+ 0x20, 0x19, 0x89, 0x19, 0x77, 0xe2, 0xdd, 0x8f, 0x7c, 0xb0, 0xa9, 0x7f, 0x39, 0x7d, 0x8d, 0x7e,
+ 0x8d, 0x7a, 0xc1, 0x7c, 0x39, 0x78, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0xb0, 0x11, 0x81, 0x85, 0x81,
+ 0xc1, 0x81, 0x35, 0x83, 0x0d, 0x82, 0xf5, 0x84, 0xe7, 0x0d, 0x84, 0xe2, 0x84, 0xd1, 0x8b, 0xe9,
+ 0x0d, 0x84, 0xb0, 0xc9, 0x82, 0xa9, 0x7f, 0x7d, 0x85, 0x95, 0x7e, 0xcd, 0x87, 0xc9, 0x7c, 0x20,
+ 0x21, 0x7d, 0x21, 0x7d, 0xb0, 0x81, 0x7e, 0x11, 0x81, 0xd1, 0x7c, 0xbd, 0x81, 0x11, 0x7b, 0x0d,
+ 0x82, 0xe3, 0xc9, 0x87, 0x25, 0x7b, 0x20, 0xd5, 0x82, 0xd5, 0x82, 0xb0, 0xcd, 0x81, 0xb1, 0x7d,
+ 0xe9, 0x82, 0x76, 0x41, 0x83, 0x39, 0x78, 0xe7, 0xf5, 0x7b, 0xb0, 0xb5, 0x7f, 0xc1, 0x81, 0x09,
+ 0x7f, 0x71, 0x83, 0xf5, 0x7d, 0xf5, 0x84, 0xe1,
+}
+
+var ImageSlideshow = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x70, 0xe9, 0xa0,
+ 0x21, 0x94, 0x70, 0x6c, 0x70, 0xe2, 0x9c, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35,
+ 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x64, 0xe8, 0x64, 0xe7, 0xb8, 0xe9, 0xb8, 0xe1,
+}
+
+var ImageStraighten = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x68, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xa8, 0xe6, 0x5c, 0xe8,
+ 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9,
+ 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe7, 0x88, 0xe9,
+ 0x70, 0xe7, 0x88, 0xe9, 0xa0, 0xe1,
+}
+
+var ImageStyle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x11, 0x6d, 0x51, 0x8f,
+ 0x20, 0xb1, 0x82, 0x1d, 0x81, 0xe8, 0x61, 0x7e, 0x00, 0xe9, 0x6a, 0x15, 0x8a, 0xb0, 0x29, 0x7f,
+ 0x09, 0x82, 0x21, 0x80, 0x61, 0x84, 0x29, 0x82, 0x3d, 0x85, 0xe3, 0xce, 0x95, 0x78, 0x00, 0x25,
+ 0x8a, 0xf5, 0x6f, 0xb1, 0x61, 0x7f, 0x81, 0x7e, 0xed, 0x7d, 0x91, 0x7d, 0x65, 0x7c, 0x8d, 0x7d,
+ 0x79, 0x7f, 0xfd, 0x7f, 0xf1, 0x7e, 0x19, 0x80, 0x69, 0x7e, 0x4d, 0x80, 0x00, 0x35, 0x76, 0xe9,
+ 0x73, 0xb1, 0x81, 0x7e, 0xa1, 0x80, 0x95, 0x7d, 0x11, 0x82, 0x8d, 0x7d, 0x99, 0x83, 0xfd, 0x7f,
+ 0x8d, 0x80, 0x15, 0x80, 0x15, 0x81, 0x4d, 0x80, 0x9d, 0x81, 0x20, 0xe9, 0x89, 0xf1, 0x97, 0xb1,
+ 0xa1, 0x80, 0x85, 0x81, 0x19, 0x82, 0x71, 0x82, 0xa9, 0x83, 0x75, 0x82, 0x85, 0x80, 0x80, 0x0d,
+ 0x81, 0xe9, 0x7f, 0x8d, 0x81, 0xb5, 0x7f, 0x20, 0xbd, 0x8e, 0xe9, 0x79, 0xb0, 0x09, 0x82, 0x29,
+ 0x7f, 0x05, 0x83, 0xd1, 0x7c, 0x2d, 0x82, 0xcd, 0x7a, 0xe2, 0xc1, 0x77, 0x81, 0x79, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x92, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0x84, 0xe9, 0x80,
+ 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe3, 0x78, 0xac, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xe9, 0x82, 0x20, 0x19, 0x79, 0x51, 0x6f, 0xe8, 0x81, 0x8f, 0xe1,
+}
+
+var ImageSwitchCamera = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x58, 0xe6, 0x74, 0x20, 0x59, 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x8c, 0x8e, 0xe9, 0x76, 0xe6, 0x74, 0xe9, 0x8a, 0x21,
+ 0x72, 0x72, 0x8e, 0x72, 0xe9, 0x8a, 0xe7, 0x98, 0xe9, 0x76, 0x21, 0x8e, 0x8e, 0x72, 0x8e, 0xe1,
+}
+
+var ImageSwitchVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x76, 0xe9, 0x72,
+ 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x5c, 0xb0, 0xe5, 0x7e, 0x80, 0x7c,
+ 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0xb0, 0xb0, 0x80, 0x19, 0x81, 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe7,
+ 0xb8, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0x20, 0x90, 0x90, 0xe8,
+ 0x66, 0x20, 0x70, 0x90, 0xe2, 0x84, 0x8e, 0xe9, 0x76, 0xe6, 0x6c, 0xe9, 0x8a, 0x21, 0x72, 0x72,
+ 0x8e, 0x72, 0xe9, 0x8a, 0xe7, 0x98, 0xe9, 0x76, 0x21, 0x8e, 0x8e, 0x72, 0x8e, 0xe1,
+}
+
+var ImageTagFaces = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2,
+ 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80,
+ 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x8e, 0x5c, 0xb0,
+ 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x64, 0x80, 0xb0, 0xa9, 0x81, 0x80, 0x86,
+ 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0xe3, 0x8e, 0x9a, 0xb0, 0xa9, 0x84, 0x80, 0x9d, 0x88, 0x19, 0x7d, 0x35,
+ 0x8a, 0x72, 0xe6, 0xcd, 0x75, 0xb0, 0x99, 0x81, 0x19, 0x84, 0x8d, 0x85, 0x8e, 0x35, 0x8a, 0x8e,
+ 0xe1,
+}
+
+var ImageTexture = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x8f, 0x29, 0x6e,
+ 0x00, 0x29, 0x6e, 0x05, 0x8f, 0xb0, 0x31, 0x80, 0xb1, 0x80, 0x89, 0x80, 0x51, 0x81, 0x05, 0x81,
+ 0xcd, 0x81, 0x20, 0x05, 0x80, 0x05, 0x80, 0xb0, 0x7d, 0x80, 0x7d, 0x80, 0x1d, 0x81, 0xd9, 0x80,
+ 0xcd, 0x81, 0x05, 0x81, 0x00, 0xd9, 0x91, 0xfd, 0x70, 0xb0, 0xa1, 0x7f, 0xa1, 0x7e, 0x8d, 0x7e,
+ 0x8d, 0x7d, 0x2d, 0x7d, 0x2d, 0x7d, 0xe2, 0xc5, 0x7f, 0x5c, 0x00, 0x5c, 0xc5, 0x7f, 0xe9, 0xa9,
+ 0x85, 0x00, 0x6d, 0x85, 0x5c, 0xe7, 0x59, 0x7a, 0xe2, 0x64, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x05, 0x84, 0x00, 0x05, 0x76, 0x5c, 0xe6, 0x64, 0xe3, 0xb8, 0xc8,
+ 0xb0, 0x19, 0x81, 0x80, 0x19, 0x82, 0x8d, 0x7f, 0xd1, 0x82, 0xd5, 0x7e, 0xa0, 0x8d, 0x91, 0x19,
+ 0x90, 0xa4, 0x19, 0x8f, 0xa4, 0x9c, 0xe9, 0xfd, 0x7b, 0x00, 0xfd, 0x89, 0xa4, 0xe6, 0x9c, 0xe3,
+ 0x95, 0x6c, 0x80, 0xe7, 0xa9, 0x85, 0x00, 0xa4, 0x3d, 0x80, 0xe9, 0x59, 0x7a, 0x00, 0x95, 0x7a,
+ 0xa4, 0xe1,
+}
+
+var ImageTimeLapse = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7d, 0x88, 0x85, 0x77,
+ 0xa0, 0x25, 0x86, 0x2d, 0x75, 0x11, 0x83, 0x68, 0x80, 0x68, 0xe9, 0x98, 0x20, 0x85, 0x77, 0x7d,
+ 0x88, 0xb1, 0xb1, 0x84, 0xb1, 0x84, 0x49, 0x8c, 0xb1, 0x84, 0xf9, 0x90, 0x80, 0xb1, 0x84, 0x51,
+ 0x7b, 0xb1, 0x84, 0xb5, 0x73, 0x05, 0x80, 0x05, 0x6f, 0xe2, 0x80, 0x58, 0xa0, 0xf5, 0x74, 0x58,
+ 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58,
+ 0xb0, 0x80, 0xf5, 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x29, 0x77, 0x80,
+ 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0,
+ 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ImageTimer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x54, 0xe6, 0x74,
+ 0xe9, 0x88, 0xe7, 0x98, 0xe8, 0x54, 0xe3, 0x70, 0xb4, 0xe7, 0x88, 0xe8, 0x70, 0xe7, 0x78, 0xe9,
+ 0x98, 0xe3, 0x0d, 0x90, 0xc5, 0x72, 0x20, 0xd9, 0x82, 0x29, 0x7d, 0xb0, 0x25, 0x7f, 0xf9, 0x7e,
+ 0x35, 0x7e, 0x09, 0x7e, 0x2d, 0x7d, 0x2d, 0x7d, 0x20, 0x29, 0x7d, 0xd9, 0x82, 0xa1, 0x29, 0x88,
+ 0x7d, 0x71, 0x3d, 0x84, 0x60, 0xfd, 0x7f, 0x60, 0x0d, 0x76, 0x60, 0x5c, 0x11, 0x78, 0x5c, 0x84,
+ 0x90, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0x80, 0xa4, 0xf1, 0x8b, 0xa4, 0x84, 0xb0, 0x80, 0xc1,
+ 0x7b, 0x85, 0x7e, 0xd9, 0x77, 0x0d, 0x7c, 0xc5, 0x74, 0xe2, 0x80, 0xa0, 0xb0, 0x45, 0x78, 0x80,
+ 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c,
+ 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var ImageTimer10 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x50, 0x71, 0x77, 0xe9,
+ 0x61, 0x83, 0x20, 0x8c, 0x7c, 0xe8, 0x98, 0xe7, 0x88, 0xe8, 0x68, 0xe7, 0x7d, 0x7f, 0x00, 0x50,
+ 0x71, 0x77, 0xe3, 0x91, 0xaf, 0x4d, 0x8d, 0xbe, 0xb5, 0x7f, 0x71, 0x7f, 0x4d, 0x7f, 0xf1, 0x7e,
+ 0xbd, 0x7e, 0x85, 0x7e, 0x75, 0x7f, 0x95, 0x7f, 0xc5, 0x7e, 0x3d, 0x7f, 0xfd, 0x7d, 0xf1, 0x7e,
+ 0x35, 0x7f, 0xb5, 0x7f, 0x4d, 0x7e, 0x75, 0x7f, 0x4d, 0x7d, 0x41, 0x7f, 0x4d, 0x7f, 0xd9, 0x7f,
+ 0xbd, 0x7e, 0xb5, 0x7f, 0x45, 0x7e, 0x8d, 0x7f, 0x8d, 0x7f, 0xd9, 0x7f, 0x2d, 0x7f, 0xb1, 0x7f,
+ 0xe9, 0x7e, 0x81, 0x7f, 0xbd, 0x7f, 0xd1, 0x7f, 0x8d, 0x7f, 0x9d, 0x7f, 0x71, 0x7f, 0x65, 0x7f,
+ 0xe5, 0x7f, 0xc9, 0x7f, 0xd9, 0x7f, 0x85, 0x7f, 0xd9, 0x7f, 0x3d, 0x7f, 0x80, 0xb5, 0x7f, 0x11,
+ 0x80, 0x71, 0x7f, 0x31, 0x80, 0x2d, 0x7f, 0x21, 0x80, 0xbd, 0x7f, 0x4d, 0x80, 0x85, 0x7f, 0x8d,
+ 0x80, 0x51, 0x7f, 0x3d, 0x80, 0xcd, 0x7f, 0x8d, 0x80, 0xa5, 0x7f, 0xe9, 0x80, 0x85, 0x7f, 0x5d,
+ 0x80, 0xe1, 0x7f, 0xcd, 0x80, 0xd1, 0x7f, 0x49, 0x81, 0xd1, 0x7f, 0x81, 0x80, 0x80, 0xf1, 0x80,
+ 0x11, 0x80, 0x51, 0x81, 0x39, 0x80, 0x61, 0x80, 0x25, 0x80, 0xb5, 0x80, 0x59, 0x80, 0xf5, 0x80,
+ 0x99, 0x80, 0x45, 0x80, 0x41, 0x80, 0x75, 0x80, 0x89, 0x80, 0x95, 0x80, 0xd9, 0x80, 0x21, 0x80,
+ 0x51, 0x80, 0x31, 0x80, 0xa5, 0x80, 0x31, 0x80, 0xf9, 0x80, 0xe7, 0xe9, 0x83, 0xbc, 0x80, 0x39,
+ 0x7f, 0xd9, 0x7f, 0x7d, 0x7e, 0x89, 0x7f, 0xd1, 0x7d, 0xb1, 0x7f, 0x55, 0x7f, 0x3d, 0x7f, 0xbd,
+ 0x7e, 0xa1, 0x7e, 0x3d, 0x7e, 0x69, 0x7f, 0x81, 0x7f, 0xad, 0x7e, 0x1d, 0x7f, 0xd1, 0x7d, 0xd5,
+ 0x7e, 0x21, 0x7f, 0xb5, 0x7f, 0x29, 0x7e, 0x91, 0x7f, 0x15, 0x7d, 0x91, 0x7f, 0xf9, 0x7e, 0x80,
+ 0x0d, 0x7e, 0x25, 0x80, 0x39, 0x7d, 0x6d, 0x80, 0x2d, 0x7f, 0x49, 0x80, 0x79, 0x7e, 0xad, 0x80,
+ 0xe1, 0x7d, 0x29, 0x81, 0x6d, 0x7f, 0x7d, 0x80, 0xf9, 0x7e, 0x0d, 0x81, 0xa9, 0x7e, 0xb1, 0x81,
+ 0xb1, 0x7f, 0xa5, 0x80, 0x89, 0x7f, 0x51, 0x81, 0x89, 0x7f, 0x05, 0x82, 0x80, 0xbd, 0x80, 0x29,
+ 0x80, 0x61, 0x81, 0x75, 0x80, 0xf1, 0x81, 0x4d, 0x80, 0x91, 0x80, 0xbd, 0x80, 0x0d, 0x81, 0x49,
+ 0x81, 0x75, 0x81, 0x8d, 0x80, 0x69, 0x80, 0x35, 0x81, 0xc5, 0x80, 0xf9, 0x81, 0x0d, 0x81, 0xc5,
+ 0x80, 0x4d, 0x80, 0x9d, 0x81, 0x89, 0x80, 0x8d, 0x82, 0xb9, 0x80, 0xc9, 0x80, 0x29, 0x80, 0x69,
+ 0x81, 0x55, 0x80, 0xe9, 0x81, 0x85, 0x80, 0x90, 0xdd, 0x80, 0x61, 0x80, 0x21, 0x81, 0x99, 0x80,
+ 0xb7, 0x45, 0x80, 0x35, 0x80, 0x75, 0x80, 0x71, 0x80, 0x8d, 0x80, 0xad, 0x80, 0x19, 0x80, 0x3d,
+ 0x80, 0x25, 0x80, 0x81, 0x80, 0x25, 0x80, 0xc9, 0x80, 0x80, 0xa1, 0x80, 0xbd, 0x7f, 0x25, 0x81,
+ 0x35, 0x7f, 0x89, 0x81, 0x75, 0x7f, 0x65, 0x80, 0xb1, 0x7e, 0x99, 0x80, 0xa9, 0x7d, 0x99, 0x80,
+ 0x91, 0x7f, 0x80, 0x21, 0x7f, 0xf5, 0x7f, 0xb9, 0x7e, 0xd9, 0x7f, 0x95, 0x7f, 0xe9, 0x7f, 0x35,
+ 0x7f, 0xbd, 0x7f, 0xe1, 0x7e, 0x85, 0x7f, 0xad, 0x7f, 0xc9, 0x7f, 0x69, 0x7f, 0x7d, 0x7f, 0x31,
+ 0x7f, 0x21, 0x7f, 0xcd, 0x7f, 0xa5, 0x7f, 0xa9, 0x7f, 0x31, 0x7f, 0xa1, 0x7f, 0xa9, 0x7e, 0xe7,
+ 0x35, 0x7c, 0xb8, 0x80, 0xb9, 0x80, 0x29, 0x80, 0x6d, 0x81, 0x79, 0x80, 0x1d, 0x82, 0x51, 0x80,
+ 0xb1, 0x80, 0xc9, 0x80, 0x51, 0x81, 0x69, 0x81, 0xd9, 0x81, 0xa1, 0x80, 0x8d, 0x80, 0x65, 0x81,
+ 0xfd, 0x80, 0x4d, 0x82, 0x51, 0x81, 0xe9, 0x80, 0x59, 0x80, 0xf9, 0x81, 0x85, 0x80, 0x29, 0x83,
+ 0x85, 0x80, 0x11, 0x81, 0x80, 0x05, 0x82, 0xe1, 0x7f, 0xe5, 0x82, 0xa1, 0x7f, 0xe1, 0x80, 0xbd,
+ 0x7f, 0x9d, 0x81, 0x61, 0x7f, 0x39, 0x82, 0xed, 0x7e, 0xa1, 0x80, 0x89, 0x7f, 0x19, 0x81, 0xfd,
+ 0x7e, 0x6d, 0x81, 0x59, 0x7e, 0x55, 0x80, 0x5d, 0x7f, 0x81, 0x80, 0xa9, 0x7e, 0x81, 0x80, 0xe1,
+ 0x7d, 0xfd, 0x7f, 0x35, 0x7f, 0xd9, 0x7f, 0x8d, 0x7e, 0x91, 0x7f, 0xf9, 0x7d, 0xe2, 0xa5, 0x83,
+ 0x19, 0x76, 0xb5, 0x51, 0x7f, 0x31, 0x7f, 0x81, 0x7e, 0x9d, 0x7e, 0x8d, 0x7d, 0x41, 0x7e, 0x0d,
+ 0x7f, 0xa5, 0x7f, 0xfd, 0x7d, 0x75, 0x7f, 0xcd, 0x7c, 0x75, 0x7f, 0xd9, 0x7e, 0x80, 0xc9, 0x7d,
+ 0x31, 0x80, 0xd1, 0x7c, 0x8d, 0x80, 0x0d, 0x7f, 0x5d, 0x80, 0x39, 0x7e, 0xf1, 0x80, 0x8d, 0x7d,
+ 0xc1, 0x81, 0x51, 0x7f, 0xd1, 0x80, 0xcd, 0x7e, 0xe1, 0x81, 0x71, 0x7e, 0x2d, 0x83, 0xa1, 0x7f,
+ 0x51, 0x81, 0x75, 0x7f, 0xe9, 0x82, 0x75, 0x7f, 0xc9, 0x84, 0xe9, 0xd5, 0x83, 0xb7, 0x80, 0xe1,
+ 0x81, 0x31, 0x80, 0x7d, 0x83, 0x91, 0x80, 0xcd, 0x84, 0x61, 0x80, 0x51, 0x81, 0xe9, 0x80, 0x61,
+ 0x82, 0x99, 0x81, 0x35, 0x83, 0xb1, 0x80, 0xd1, 0x80, 0x85, 0x81, 0x6d, 0x81, 0x75, 0x82, 0xcd,
+ 0x81, 0xf5, 0x80, 0x61, 0x80, 0x05, 0x82, 0x8d, 0x80, 0x31, 0x83, 0x8d, 0x80, 0x2d, 0x81, 0x80,
+ 0x3d, 0x82, 0xd1, 0x7f, 0x31, 0x83, 0x75, 0x7f, 0xf5, 0x80, 0xa1, 0x7f, 0xc5, 0x81, 0x0d, 0x7f,
+ 0x71, 0x82, 0x35, 0x7e, 0xad, 0x80, 0x31, 0x7f, 0x35, 0x81, 0x1d, 0x7e, 0x91, 0x81, 0xcd, 0x7c,
+ 0x61, 0x80, 0xb1, 0x7e, 0x8d, 0x80, 0x19, 0x7d, 0x8d, 0x80, 0x35, 0x7b, 0xe9, 0x2d, 0x7c, 0xb1,
+ 0x80, 0x21, 0x7e, 0xd1, 0x7f, 0x89, 0x7c, 0x75, 0x7f, 0x39, 0x7b, 0xa1, 0x7f, 0xb5, 0x7e, 0x19,
+ 0x7f, 0xa5, 0x7d, 0x69, 0x7e, 0xd5, 0x7c, 0xe3, 0x2d, 0x7e, 0x5d, 0x8c, 0xb1, 0x80, 0x35, 0x81,
+ 0xed, 0x7f, 0x35, 0x82, 0xc5, 0x7f, 0x0d, 0x83, 0xd9, 0x7f, 0xd9, 0x80, 0x99, 0x7f, 0x85, 0x81,
+ 0x45, 0x7f, 0x0d, 0x82, 0x90, 0x49, 0x7f, 0xe9, 0x80, 0xd1, 0x7e, 0x25, 0x81, 0xb1, 0x89, 0x7f,
+ 0x3d, 0x80, 0xfd, 0x7e, 0x5d, 0x80, 0x61, 0x7e, 0x5d, 0x80, 0x65, 0x7f, 0x80, 0xd9, 0x7e, 0xe1,
+ 0x7f, 0x61, 0x7e, 0xa5, 0x7f, 0x90, 0x21, 0x7f, 0x61, 0x7f, 0xcd, 0x7e, 0xdd, 0x7e, 0xb1, 0xad,
+ 0x7f, 0x7d, 0x7f, 0x6d, 0x7f, 0xcd, 0x7e, 0x41, 0x7f, 0xf5, 0x7d, 0xd5, 0x7f, 0x29, 0x7f, 0xbd,
+ 0x7f, 0x29, 0x7e, 0xbd, 0x7f, 0xf5, 0x7c, 0xe9, 0xfd, 0x7a, 0xb7, 0x80, 0xcd, 0x7e, 0x15, 0x80,
+ 0xc9, 0x7d, 0x41, 0x80, 0xf5, 0x7c, 0x2d, 0x80, 0x31, 0x7f, 0x6d, 0x80, 0x85, 0x7e, 0xbd, 0x80,
+ 0x05, 0x7e, 0x55, 0x80, 0x7d, 0x7f, 0xbd, 0x80, 0x21, 0x7f, 0x35, 0x81, 0xe9, 0x7e, 0x79, 0x80,
+ 0xc9, 0x7f, 0x05, 0x81, 0xad, 0x7f, 0xa1, 0x81, 0xad, 0x7f, 0x9d, 0x80, 0x80, 0x29, 0x81, 0x1d,
+ 0x80, 0xa1, 0x81, 0x55, 0x80, 0x79, 0x80, 0x39, 0x80, 0xe1, 0x80, 0x99, 0x80, 0x35, 0x81, 0x19,
+ 0x81, 0x55, 0x80, 0x85, 0x80, 0x91, 0x80, 0x2d, 0x81, 0xbd, 0x80, 0xfd, 0x81, 0x2d, 0x80, 0xd1,
+ 0x80, 0x41, 0x80, 0xd9, 0x81, 0x41, 0x80, 0x0d, 0x83, 0xe9, 0x05, 0x85, 0xe1,
+}
+
+var ImageTimer3 = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x39, 0x7f, 0xf5, 0x81,
+ 0xbd, 0xb1, 0x7f, 0x85, 0x7f, 0x45, 0x7f, 0x15, 0x7f, 0xc5, 0x7e, 0xb1, 0x7e, 0x7d, 0x7f, 0x9d,
+ 0x7f, 0xe1, 0x7e, 0x4d, 0x7f, 0x29, 0x7e, 0x0d, 0x7f, 0x9d, 0x80, 0xbd, 0x7f, 0x25, 0x81, 0x65,
+ 0x7f, 0x99, 0x81, 0x7e, 0x75, 0x80, 0x99, 0x7f, 0xd9, 0x80, 0x31, 0x7f, 0x25, 0x81, 0xbd, 0x7e,
+ 0x4d, 0x80, 0x8d, 0x7f, 0x89, 0x80, 0x15, 0x7f, 0xb1, 0x80, 0x99, 0x7e, 0x29, 0x80, 0x85, 0x7f,
+ 0x3d, 0x80, 0x09, 0x7f, 0x3d, 0x80, 0x8d, 0x7e, 0x80, 0xe5, 0x7e, 0xd1, 0x7f, 0xed, 0x7d, 0x75,
+ 0x7f, 0x11, 0x7d, 0xa1, 0x7f, 0x29, 0x7f, 0x1d, 0x7f, 0x75, 0x7e, 0x71, 0x7e, 0xe5, 0x7d, 0x55,
+ 0x7f, 0x71, 0x7f, 0x89, 0x7e, 0x05, 0x7f, 0x99, 0x7d, 0xb9, 0x7e, 0x11, 0x7f, 0xb5, 0x7f, 0x09,
+ 0x7e, 0x91, 0x7f, 0xe9, 0x7c, 0x91, 0x7f, 0xe9, 0x7e, 0x80, 0xe5, 0x7d, 0x29, 0x80, 0xf5, 0x7c,
+ 0x7d, 0x80, 0x11, 0x7f, 0x55, 0x80, 0x45, 0x7e, 0xc9, 0x80, 0x99, 0x7d, 0x5d, 0x81, 0x55, 0x7f,
+ 0x95, 0x80, 0xd1, 0x7e, 0x45, 0x81, 0x71, 0x7e, 0x11, 0x82, 0xa1, 0x7f, 0xcd, 0x80, 0x71, 0x7f,
+ 0xa9, 0x81, 0x71, 0x7f, 0x99, 0x82, 0xe7, 0xf5, 0x83, 0xb9, 0x80, 0x7d, 0x7f, 0x19, 0x80, 0x09,
+ 0x7f, 0x49, 0x80, 0xa1, 0x7e, 0x31, 0x80, 0x99, 0x7f, 0x71, 0x80, 0x3d, 0x7f, 0xc5, 0x80, 0xf5,
+ 0x7e, 0x55, 0x80, 0xb5, 0x7f, 0xb5, 0x80, 0x7d, 0x7f, 0x29, 0x81, 0x55, 0x7f, 0x75, 0x80, 0xd9,
+ 0x7f, 0xf1, 0x80, 0xc5, 0x7f, 0x75, 0x81, 0xc5, 0x7f, 0x39, 0x81, 0x80, 0x21, 0x82, 0x51, 0x80,
+ 0xb9, 0x82, 0xf1, 0x80, 0x99, 0x80, 0xa1, 0x80, 0xe5, 0x80, 0x81, 0x81, 0xe5, 0x80, 0xa1, 0x82,
+ 0x80, 0x8d, 0x80, 0xed, 0x7f, 0x0d, 0x81, 0xc5, 0x7f, 0x7d, 0x81, 0xd9, 0x7f, 0x75, 0x80, 0x99,
+ 0x7f, 0xd5, 0x80, 0x41, 0x7f, 0x25, 0x81, 0xa9, 0x7f, 0x51, 0x80, 0x3d, 0x7f, 0x91, 0x80, 0xbd,
+ 0x7e, 0xbd, 0x80, 0x81, 0x7f, 0x31, 0x80, 0xe9, 0x7e, 0x45, 0x80, 0x39, 0x7e, 0x45, 0x80, 0xe7,
+ 0xa9, 0x7d, 0xe9, 0x21, 0x83, 0xe7, 0x59, 0x82, 0xb9, 0xad, 0x80, 0x80, 0x49, 0x81, 0x15, 0x80,
+ 0xd1, 0x81, 0x3d, 0x80, 0x8d, 0x80, 0x29, 0x80, 0x82, 0x65, 0x80, 0x61, 0x81, 0xb5, 0x80, 0x61,
+ 0x80, 0x51, 0x80, 0xad, 0x80, 0xbd, 0x80, 0xe1, 0x80, 0x39, 0x81, 0x35, 0x80, 0x7d, 0x80, 0x51,
+ 0x80, 0x11, 0x81, 0x51, 0x80, 0xc1, 0x81, 0x80, 0x3d, 0x81, 0xa9, 0x7f, 0x31, 0x82, 0xf1, 0x7e,
+ 0xd9, 0x82, 0x4d, 0x7f, 0xa9, 0x80, 0x55, 0x7e, 0xfd, 0x80, 0x19, 0x7d, 0xfd, 0x80, 0x69, 0x7f,
+ 0x80, 0xe1, 0x7e, 0xed, 0x7f, 0x69, 0x7e, 0xc1, 0x7f, 0x85, 0x7f, 0xd5, 0x7f, 0x1d, 0x7f, 0x99,
+ 0x7f, 0xc9, 0x7e, 0x49, 0x7f, 0xa9, 0x7f, 0xb1, 0x7f, 0x69, 0x7f, 0x51, 0x7f, 0x39, 0x7f, 0xe1,
+ 0x7e, 0xd1, 0x7f, 0x91, 0x7f, 0xb9, 0x7f, 0x19, 0x7f, 0xb9, 0x7f, 0x91, 0x7e, 0xe6, 0x61, 0x70,
+ 0xb1, 0x80, 0x19, 0x81, 0x35, 0x80, 0x11, 0x82, 0xa5, 0x80, 0xe9, 0x82, 0x6d, 0x80, 0xd9, 0x80,
+ 0xfd, 0x80, 0x8d, 0x81, 0xb5, 0x81, 0x19, 0x82, 0x90, 0x8d, 0x81, 0xfd, 0x80, 0x7d, 0x82, 0x45,
+ 0x81, 0xb6, 0xf1, 0x80, 0x49, 0x80, 0xf1, 0x81, 0x6d, 0x80, 0xf5, 0x82, 0x6d, 0x80, 0x21, 0x81,
+ 0x80, 0x31, 0x82, 0xd9, 0x7f, 0x29, 0x83, 0x8d, 0x7f, 0xf9, 0x80, 0xb1, 0x7f, 0xd1, 0x81, 0x3d,
+ 0x7f, 0x89, 0x82, 0xa9, 0x7e, 0xb5, 0x80, 0x69, 0x7f, 0x45, 0x81, 0xb1, 0x7e, 0xad, 0x81, 0xd1,
+ 0x7d, 0x69, 0x80, 0x21, 0x7f, 0x99, 0x80, 0x25, 0x7e, 0x99, 0x80, 0x09, 0x7d, 0x80, 0x69, 0x7f,
+ 0xf1, 0x7f, 0xd9, 0x7e, 0xc9, 0x7f, 0x4d, 0x7e, 0xd9, 0x7f, 0x71, 0x7f, 0xa1, 0x7f, 0xed, 0x7e,
+ 0x51, 0x7f, 0x71, 0x7e, 0xe3, 0x89, 0x92, 0xc9, 0x82, 0xbe, 0xb5, 0x7f, 0x71, 0x7f, 0x4d, 0x7f,
+ 0xf1, 0x7e, 0xbd, 0x7e, 0x85, 0x7e, 0x75, 0x7f, 0x95, 0x7f, 0xc5, 0x7e, 0x3d, 0x7f, 0xfd, 0x7d,
+ 0xf1, 0x7e, 0x35, 0x7f, 0xb5, 0x7f, 0x4d, 0x7e, 0x75, 0x7f, 0x4d, 0x7d, 0x41, 0x7f, 0x4d, 0x7f,
+ 0xd9, 0x7f, 0xbd, 0x7e, 0xb5, 0x7f, 0x45, 0x7e, 0x8d, 0x7f, 0x8d, 0x7f, 0xd9, 0x7f, 0x2d, 0x7f,
+ 0xb1, 0x7f, 0xe9, 0x7e, 0x81, 0x7f, 0xbd, 0x7f, 0xd1, 0x7f, 0x8d, 0x7f, 0x9d, 0x7f, 0x71, 0x7f,
+ 0x65, 0x7f, 0xe5, 0x7f, 0xc9, 0x7f, 0xd9, 0x7f, 0x85, 0x7f, 0xd9, 0x7f, 0x3d, 0x7f, 0x80, 0xb5,
+ 0x7f, 0x11, 0x80, 0x71, 0x7f, 0x31, 0x80, 0x2d, 0x7f, 0x21, 0x80, 0xbd, 0x7f, 0x4d, 0x80, 0x85,
+ 0x7f, 0x8d, 0x80, 0x51, 0x7f, 0x3d, 0x80, 0xcd, 0x7f, 0x8d, 0x80, 0xa5, 0x7f, 0xe9, 0x80, 0x85,
+ 0x7f, 0x5d, 0x80, 0xe1, 0x7f, 0xcd, 0x80, 0xd1, 0x7f, 0x49, 0x81, 0xd1, 0x7f, 0x81, 0x80, 0x80,
+ 0xf1, 0x80, 0x11, 0x80, 0x51, 0x81, 0x39, 0x80, 0x61, 0x80, 0x25, 0x80, 0xb5, 0x80, 0x59, 0x80,
+ 0xf5, 0x80, 0x99, 0x80, 0x45, 0x80, 0x41, 0x80, 0x75, 0x80, 0x89, 0x80, 0x95, 0x80, 0xd9, 0x80,
+ 0x21, 0x80, 0x51, 0x80, 0x31, 0x80, 0xa5, 0x80, 0x31, 0x80, 0xf9, 0x80, 0xe7, 0xe9, 0x83, 0xbc,
+ 0x80, 0x39, 0x7f, 0xd9, 0x7f, 0x7d, 0x7e, 0x89, 0x7f, 0xd1, 0x7d, 0xb1, 0x7f, 0x55, 0x7f, 0x3d,
+ 0x7f, 0xbd, 0x7e, 0xa1, 0x7e, 0x3d, 0x7e, 0x69, 0x7f, 0x81, 0x7f, 0xad, 0x7e, 0x1d, 0x7f, 0xd1,
+ 0x7d, 0xd5, 0x7e, 0x21, 0x7f, 0xb5, 0x7f, 0x29, 0x7e, 0x91, 0x7f, 0x11, 0x7d, 0x91, 0x7f, 0xf9,
+ 0x7e, 0x80, 0x0d, 0x7e, 0x25, 0x80, 0x39, 0x7d, 0x6d, 0x80, 0x2d, 0x7f, 0x49, 0x80, 0x79, 0x7e,
+ 0xad, 0x80, 0xe1, 0x7d, 0x29, 0x81, 0x6d, 0x7f, 0x7d, 0x80, 0xf9, 0x7e, 0x0d, 0x81, 0xa9, 0x7e,
+ 0xb1, 0x81, 0xb1, 0x7f, 0xa5, 0x80, 0x89, 0x7f, 0x51, 0x81, 0x89, 0x7f, 0x05, 0x82, 0x80, 0xbd,
+ 0x80, 0x29, 0x80, 0x61, 0x81, 0x75, 0x80, 0xf1, 0x81, 0x4d, 0x80, 0x91, 0x80, 0xbd, 0x80, 0x0d,
+ 0x81, 0x49, 0x81, 0x75, 0x81, 0x8d, 0x80, 0x69, 0x80, 0x35, 0x81, 0xc5, 0x80, 0xf9, 0x81, 0x0d,
+ 0x81, 0xc5, 0x80, 0x4d, 0x80, 0x9d, 0x81, 0x89, 0x80, 0x8d, 0x82, 0xb9, 0x80, 0xc9, 0x80, 0x29,
+ 0x80, 0x69, 0x81, 0x55, 0x80, 0xe9, 0x81, 0x85, 0x80, 0x90, 0xdd, 0x80, 0x61, 0x80, 0x21, 0x81,
+ 0x99, 0x80, 0xb7, 0x45, 0x80, 0x35, 0x80, 0x75, 0x80, 0x71, 0x80, 0x8d, 0x80, 0xad, 0x80, 0x19,
+ 0x80, 0x3d, 0x80, 0x25, 0x80, 0x81, 0x80, 0x25, 0x80, 0xc9, 0x80, 0x80, 0xa1, 0x80, 0xbd, 0x7f,
+ 0x25, 0x81, 0x35, 0x7f, 0x89, 0x81, 0x75, 0x7f, 0x65, 0x80, 0xb1, 0x7e, 0x99, 0x80, 0xa9, 0x7d,
+ 0x99, 0x80, 0x91, 0x7f, 0x80, 0x21, 0x7f, 0xf5, 0x7f, 0xb9, 0x7e, 0xd9, 0x7f, 0x95, 0x7f, 0xe9,
+ 0x7f, 0x35, 0x7f, 0xbd, 0x7f, 0xe1, 0x7e, 0x85, 0x7f, 0xad, 0x7f, 0xc9, 0x7f, 0x69, 0x7f, 0x7d,
+ 0x7f, 0x31, 0x7f, 0x21, 0x7f, 0xcd, 0x7f, 0xa5, 0x7f, 0xa9, 0x7f, 0x31, 0x7f, 0xa1, 0x7f, 0xa9,
+ 0x7e, 0xe7, 0x35, 0x7c, 0xb8, 0x80, 0xb9, 0x80, 0x29, 0x80, 0x6d, 0x81, 0x79, 0x80, 0x1d, 0x82,
+ 0x51, 0x80, 0xb1, 0x80, 0xc9, 0x80, 0x51, 0x81, 0x69, 0x81, 0xd9, 0x81, 0xa1, 0x80, 0x8d, 0x80,
+ 0x65, 0x81, 0xfd, 0x80, 0x4d, 0x82, 0x51, 0x81, 0xe9, 0x80, 0x59, 0x80, 0xf9, 0x81, 0x85, 0x80,
+ 0x29, 0x83, 0x85, 0x80, 0x11, 0x81, 0x80, 0x05, 0x82, 0xe1, 0x7f, 0xe5, 0x82, 0xa1, 0x7f, 0xe1,
+ 0x80, 0xbd, 0x7f, 0x9d, 0x81, 0x61, 0x7f, 0x39, 0x82, 0xed, 0x7e, 0xa1, 0x80, 0x89, 0x7f, 0x19,
+ 0x81, 0xfd, 0x7e, 0x6d, 0x81, 0x59, 0x7e, 0x55, 0x80, 0x5d, 0x7f, 0x81, 0x80, 0xa9, 0x7e, 0x81,
+ 0x80, 0xe1, 0x7d, 0x80, 0x35, 0x7f, 0xd9, 0x7f, 0x8d, 0x7e, 0x91, 0x7f, 0xf9, 0x7d, 0xe1,
+}
+
+var ImageTimerOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x11, 0x8e, 0x19, 0x71,
+ 0x20, 0x29, 0x7d, 0xd9, 0x82, 0xa0, 0x29, 0x88, 0x7d, 0x71, 0x3d, 0x84, 0x60, 0xfd, 0x7f, 0x60,
+ 0xb0, 0x59, 0x7c, 0x80, 0xf5, 0x78, 0x19, 0x81, 0x1d, 0x76, 0xf9, 0x82, 0x20, 0xe9, 0x82, 0xe9,
+ 0x82, 0xa0, 0x11, 0x7b, 0xb1, 0x74, 0x75, 0x7d, 0x68, 0x80, 0x68, 0xb1, 0xbd, 0x87, 0x80, 0x9c,
+ 0x45, 0x86, 0x9c, 0x9c, 0x80, 0x8d, 0x82, 0x51, 0x7f, 0xf1, 0x84, 0x21, 0x7e, 0xfd, 0x86, 0x20,
+ 0xe9, 0x82, 0xe9, 0x82, 0xa0, 0xe9, 0x90, 0x11, 0x89, 0xa4, 0xa9, 0x85, 0xa4, 0x84, 0xb0, 0x80,
+ 0xc1, 0x7b, 0x85, 0x7e, 0xd9, 0x77, 0x0d, 0x7c, 0xc5, 0x74, 0x21, 0xd9, 0x82, 0x29, 0x7d, 0x2d,
+ 0x7d, 0x31, 0x7d, 0xe2, 0x8c, 0x54, 0xe6, 0x74, 0xe9, 0x88, 0xe7, 0x98, 0xe8, 0x54, 0xe3, 0x70,
+ 0xe1, 0x90, 0x20, 0x88, 0x88, 0xe8, 0x70, 0xe7, 0x78, 0xe9, 0xe1, 0x82, 0xe2, 0x0d, 0x6e, 0x60,
+ 0x20, 0x75, 0x7d, 0x8d, 0x82, 0x00, 0x62, 0x0d, 0x78, 0xa0, 0x1d, 0x6f, 0xe9, 0x7a, 0x5c, 0x51,
+ 0x7e, 0x5c, 0x84, 0xb1, 0x80, 0xf1, 0x89, 0x0d, 0x88, 0xa4, 0xfd, 0x91, 0xa4, 0xb1, 0x83, 0x80,
+ 0x19, 0x87, 0xe5, 0x7e, 0xf5, 0x89, 0x7a, 0x22, 0x8a, 0x8a, 0x8d, 0x82, 0x75, 0x7d, 0x99, 0x70,
+ 0x99, 0x70, 0x00, 0x0d, 0x6e, 0x60, 0xe2, 0x80, 0xa0, 0xb1, 0x45, 0x78, 0x80, 0x64, 0xbd, 0x79,
+ 0x64, 0x64, 0x80, 0x71, 0x7d, 0xb5, 0x80, 0x09, 0x7b, 0xe9, 0x81, 0xf5, 0x78, 0x20, 0x21, 0x93,
+ 0x21, 0x93, 0xa0, 0xf9, 0x84, 0x4d, 0x8f, 0x91, 0x82, 0xa0, 0x80, 0xa0, 0xe1,
+}
+
+var ImageTonality = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x7c, 0xdd, 0xa3, 0xa0, 0x1d, 0x76, 0xe1,
+ 0x8e, 0x60, 0x29, 0x88, 0x60, 0x80, 0x80, 0x1d, 0x76, 0x21, 0x71, 0x7c, 0x25, 0x70, 0xe9, 0xb9,
+ 0x9f, 0xe3, 0x88, 0x49, 0x60, 0xb0, 0x11, 0x82, 0x45, 0x80, 0x88, 0xe9, 0x80, 0xbd, 0x85, 0xdd,
+ 0x81, 0xe6, 0x84, 0xe8, 0x25, 0x70, 0xe2, 0x84, 0x6c, 0xe7, 0x7d, 0x8a, 0xb0, 0x81, 0x80, 0xa1,
+ 0x80, 0xf5, 0x80, 0x4d, 0x81, 0x5d, 0x81, 0x84, 0xe6, 0x84, 0xe9, 0x7c, 0xe3, 0x80, 0x8c, 0xe7,
+ 0x7d, 0x8d, 0xb0, 0x2d, 0x80, 0xa9, 0x80, 0x4d, 0x80, 0x51, 0x81, 0x65, 0x80, 0x84, 0xe6, 0x84,
+ 0xe9, 0x7c, 0xe3, 0x80, 0xdd, 0x93, 0xe8, 0x9c, 0xe7, 0xbd, 0x85, 0xb0, 0x45, 0x7e, 0xf5, 0x80,
+ 0x51, 0x7c, 0x99, 0x81, 0x45, 0x7a, 0xdd, 0x81, 0xe2, 0x7d, 0x8c, 0x94, 0xe6, 0x84, 0xe9, 0x7c,
+ 0xe7, 0xd9, 0x8b, 0xb0, 0x99, 0x7f, 0xb5, 0x80, 0x25, 0x7f, 0x61, 0x81, 0xa5, 0x7e, 0x84, 0xe3,
+ 0x86, 0x74, 0xe6, 0x84, 0xe9, 0x7c, 0xe7, 0xdd, 0x8d, 0xb0, 0xed, 0x7f, 0xb1, 0x80, 0xc9, 0x7f,
+ 0x59, 0x81, 0xa1, 0x7f, 0x84, 0xe1,
+}
+
+var ImageTransform = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x98, 0xe9, 0x78,
+ 0xe6, 0x70, 0xe8, 0x60, 0xe7, 0x88, 0x21, 0x74, 0x74, 0x74, 0x8c, 0xe7, 0x88, 0xe9, 0x88, 0xe6,
+ 0x58, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xa0, 0xe9, 0x88, 0xe7, 0x78, 0x21, 0x8c, 0x8c, 0x8c, 0x74, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x90, 0xe2, 0x78, 0x70, 0xe7, 0x98, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x78, 0xe9, 0x88, 0xe1,
+}
+
+var ImageTune = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x94, 0xe9, 0x88,
+ 0xe7, 0x98, 0xe9, 0x78, 0xe6, 0x5c, 0xe3, 0x80, 0x50, 0xe9, 0x88, 0xe7, 0xa8, 0xe9, 0x78, 0xe6,
+ 0x5c, 0xe3, 0xa8, 0xc0, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0x78, 0xe6, 0x84, 0xe9, 0x78, 0xe7, 0x78,
+ 0xe9, 0x98, 0xe7, 0x88, 0xe2, 0x6c, 0x74, 0xe9, 0x88, 0xe6, 0x5c, 0xe9, 0x88, 0xe7, 0x90, 0xe9,
+ 0x88, 0xe7, 0x88, 0xe8, 0x74, 0xe7, 0x78, 0xe3, 0xb8, 0x90, 0xe9, 0x78, 0xe6, 0x7c, 0xe9, 0x88,
+ 0xe7, 0xa8, 0xe3, 0x68, 0x70, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xe8,
+ 0x5c, 0xe7, 0x78, 0xe9, 0x98, 0xe1,
+}
+
+var ImageViewComfy = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x74, 0xe7, 0x90,
+ 0xe9, 0x70, 0xe6, 0x5c, 0xe9, 0x90, 0xe3, 0x80, 0x94, 0xe7, 0x90, 0xe9, 0x70, 0xe6, 0x5c, 0xe9,
+ 0x90, 0xe3, 0x94, 0x80, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x90, 0xe3, 0x94, 0x80, 0xe7,
+ 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x90, 0xe2, 0x70, 0x74, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70,
+ 0xe9, 0x90, 0xe3, 0x94, 0x70, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe3, 0x94, 0xa4,
+ 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x90, 0xe2, 0x5c, 0x9c, 0xe7, 0x90, 0xe9, 0x70, 0xe6,
+ 0x5c, 0xe9, 0x90, 0xe3, 0x94, 0x80, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x90, 0xe3, 0x94,
+ 0x80, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x90, 0xe3, 0x94, 0x80, 0xe7, 0x90, 0xe9, 0x70,
+ 0xe7, 0x70, 0xe9, 0x90, 0xe3, 0x80, 0x48, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe1,
+}
+
+var ImageViewCompact = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x9c, 0xe7, 0x98,
+ 0xe8, 0x80, 0xe6, 0x5c, 0xe9, 0x9c, 0xe3, 0x9c, 0x80, 0xe7, 0xb0, 0xe8, 0x80, 0xe6, 0x78, 0xe9,
+ 0x9c, 0xe2, 0x5c, 0x64, 0xe9, 0x98, 0xe7, 0xcc, 0xe8, 0x64, 0xe6, 0x5c, 0xe1,
+}
+
+var ImageVignette = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x5c, 0xbc, 0xb0, 0x29, 0x77,
+ 0x80, 0x60, 0xa1, 0x7a, 0x60, 0x68, 0x92, 0x29, 0x87, 0x68, 0xa0, 0x68, 0xa0, 0x61, 0x85, 0xa0,
+ 0x98, 0xd9, 0x78, 0x98, 0x60, 0x98, 0xe1,
+}
+
+var ImageWBAuto = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x75, 0x4d, 0x81,
+ 0xe7, 0x99, 0x84, 0x00, 0x70, 0x74, 0x20, 0xb5, 0x7d, 0x4d, 0x87, 0xe2, 0xa8, 0x6c, 0x20, 0x99,
+ 0x7d, 0x95, 0x8c, 0x00, 0x99, 0x8e, 0x6c, 0xe7, 0xcd, 0x7c, 0x20, 0x05, 0x7d, 0x95, 0x8c, 0x00,
+ 0x8c, 0x6c, 0xe7, 0x7d, 0x7e, 0xa1, 0x8d, 0x81, 0x59, 0x72, 0x0d, 0x7d, 0x60, 0x70, 0x60, 0x29,
+ 0x6f, 0x60, 0x50, 0x29, 0x77, 0x50, 0x80, 0x90, 0x29, 0x87, 0xa0, 0xa0, 0xa0, 0xb0, 0x45, 0x86,
+ 0x80, 0xb1, 0x8b, 0x65, 0x7c, 0x51, 0x8e, 0x25, 0x77, 0x20, 0x31, 0x80, 0xdd, 0x80, 0xe6, 0x94,
+ 0x20, 0x86, 0xcd, 0x73, 0x00, 0xa0, 0x90, 0xe7, 0x81, 0x83, 0x20, 0x19, 0x84, 0x5c, 0xe6, 0xa8,
+ 0xe2, 0x99, 0x7c, 0x90, 0x20, 0x99, 0x7e, 0x78, 0xe7, 0x99, 0x79, 0x20, 0x99, 0x7e, 0x88, 0xe6,
+ 0x99, 0x6f, 0x00, 0x6c, 0x6c, 0xe7, 0x88, 0x20, 0x69, 0x86, 0xa4, 0xe7, 0x35, 0x7c, 0xe1,
+}
+
+var ImageWBCloudy = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x8e, 0x11, 0x7c,
+ 0xa0, 0x59, 0x8d, 0x31, 0x75, 0x49, 0x87, 0x60, 0x80, 0x60, 0xb0, 0x39, 0x7a, 0x80, 0x35, 0x75,
+ 0x49, 0x83, 0xb5, 0x72, 0x11, 0x88, 0xa0, 0xb1, 0x6c, 0xb9, 0x78, 0x50, 0xd1, 0x7d, 0x50, 0x88,
+ 0xb0, 0x80, 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0xe7, 0xb4, 0xb1, 0x85, 0x85, 0x80, 0x94,
+ 0x85, 0x7b, 0x94, 0x6c, 0x80, 0xb9, 0x7a, 0xe5, 0x7b, 0x71, 0x76, 0xb5, 0x76, 0x11, 0x76, 0xe1,
+}
+
+var ImageWBIncandescent = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x6f, 0x11, 0x8d,
+ 0x23, 0xd5, 0x82, 0xd5, 0x82, 0x99, 0x83, 0x69, 0x7c, 0x2d, 0x7d, 0x2d, 0x7d, 0x69, 0x7c, 0x99,
+ 0x83, 0xe2, 0x7c, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x9e, 0xe7, 0x78, 0xe9, 0xe9, 0x85, 0xe2, 0x60,
+ 0x7a, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x78, 0xe3, 0xac, 0xa1, 0x77, 0xe8, 0x56, 0xe6,
+ 0x74, 0xe9, 0xa1, 0x89, 0xb1, 0x6d, 0x7c, 0x15, 0x82, 0x74, 0xf1, 0x85, 0x74, 0x61, 0x8a, 0x80,
+ 0xa1, 0x86, 0x61, 0x85, 0x98, 0x98, 0x98, 0x90, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0xb0, 0x80, 0x91,
+ 0x7b, 0x95, 0x7d, 0xb1, 0x77, 0x74, 0xa1, 0x75, 0xe2, 0xa0, 0x7a, 0xe9, 0x88, 0xe7, 0x8c, 0xe9,
+ 0x78, 0xe7, 0x74, 0xe3, 0x7d, 0x7a, 0x51, 0x8f, 0x23, 0x99, 0x83, 0x99, 0x83, 0xd5, 0x82, 0x2d,
+ 0x7d, 0x69, 0x7c, 0x69, 0x7c, 0x2d, 0x7d, 0xd5, 0x82, 0xe1,
+}
+
+var ImageWBIridescent = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x8a, 0xe7, 0xb8,
+ 0xe8, 0x72, 0xe6, 0x64, 0xe9, 0x98, 0xe2, 0x7c, 0x19, 0x69, 0xe8, 0x5e, 0xe7, 0x88, 0xe8, 0x19,
+ 0x69, 0xe7, 0x78, 0xe3, 0x11, 0x90, 0x8a, 0x23, 0x69, 0x7c, 0x99, 0x83, 0xd5, 0x82, 0xd5, 0x82,
+ 0x99, 0x83, 0x69, 0x7c, 0x2d, 0x7d, 0x2d, 0x7d, 0xe2, 0x84, 0xe9, 0x94, 0xe8, 0x9e, 0xe7, 0x78,
+ 0xe9, 0xe9, 0x85, 0xe7, 0x88, 0xe3, 0xe9, 0x8e, 0x2d, 0x78, 0x23, 0x69, 0x7c, 0x69, 0x7c, 0x2d,
+ 0x7d, 0xd5, 0x82, 0x99, 0x83, 0x99, 0x83, 0xd5, 0x82, 0x2d, 0x7d, 0xe2, 0x19, 0x6f, 0xf1, 0x70,
+ 0x21, 0x99, 0x83, 0x99, 0x83, 0xd5, 0x82, 0x2d, 0x7d, 0x01, 0xf1, 0x71, 0x19, 0x6e, 0x19, 0x6f,
+ 0xf1, 0x70, 0xe2, 0xf1, 0x71, 0xe9, 0x8f, 0x23, 0x99, 0x83, 0x69, 0x7c, 0x2d, 0x7d, 0x2d, 0x7d,
+ 0x69, 0x7c, 0x99, 0x83, 0xd5, 0x82, 0xd5, 0x82, 0xe1,
+}
+
+var ImageWBSunny = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x85, 0x75, 0xb1, 0x71,
+ 0x01, 0xf1, 0x71, 0x19, 0x6e, 0x19, 0x6f, 0xf1, 0x70, 0x21, 0x99, 0x83, 0x99, 0x83, 0xd1, 0x82,
+ 0x2d, 0x7d, 0xe2, 0x60, 0x7a, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x78, 0xe2, 0x84, 0x19,
+ 0x69, 0xe7, 0x78, 0xe8, 0x5e, 0xe7, 0x88, 0xe8, 0x19, 0x69, 0xe3, 0xe9, 0x8e, 0xd5, 0x87, 0x00,
+ 0x11, 0x8e, 0x19, 0x6e, 0x22, 0x69, 0x7c, 0x99, 0x83, 0xd5, 0x82, 0xd5, 0x82, 0x99, 0x83, 0x69,
+ 0x7c, 0xe3, 0x99, 0x79, 0x61, 0x9b, 0x23, 0x99, 0x83, 0x99, 0x83, 0xd5, 0x82, 0x2d, 0x7d, 0x69,
+ 0x7c, 0x69, 0x7c, 0x2d, 0x7d, 0xd5, 0x82, 0xe2, 0xa0, 0x7a, 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x78,
+ 0xe7, 0x74, 0xe2, 0x80, 0x66, 0xb0, 0x61, 0x79, 0x80, 0x68, 0x61, 0x85, 0x68, 0x98, 0x92, 0x61,
+ 0x85, 0x98, 0x98, 0x98, 0x98, 0xa1, 0x7a, 0x98, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x68, 0xe3, 0x7c,
+ 0xe9, 0xa1, 0xe7, 0x88, 0xe8, 0x9e, 0xe7, 0x78, 0xe9, 0xe9, 0x85, 0xe2, 0x19, 0x6f, 0x11, 0x8d,
+ 0x23, 0xd5, 0x82, 0xd5, 0x82, 0x99, 0x83, 0x69, 0x7c, 0x2d, 0x7d, 0x2d, 0x7d, 0x69, 0x7c, 0x99,
+ 0x83, 0xe1,
+}
+
+var MapsAddLocation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb1, 0x49,
+ 0x78, 0x80, 0x64, 0x49, 0x86, 0x64, 0x9c, 0x80, 0x81, 0x8a, 0x9c, 0xb4, 0x9c, 0xb4, 0x90, 0x9c,
+ 0x81, 0x70, 0x9c, 0x4c, 0xb0, 0x80, 0x49, 0x78, 0xb9, 0x79, 0x64, 0x64, 0x64, 0xe3, 0x90, 0xa0,
+ 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x74, 0xe9, 0x78, 0xe7, 0x8c, 0xe9, 0x74,
+ 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe1,
+}
+
+var MapsBeenhere = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x54, 0xe6, 0x64,
+ 0xa0, 0xcd, 0x6f, 0x54, 0x05, 0x6e, 0xcd, 0x6b, 0x05, 0x6e, 0x5c, 0x00, 0x5c, 0xe1, 0x87, 0xb0,
+ 0x80, 0x61, 0x81, 0xb5, 0x80, 0x99, 0x82, 0xc5, 0x81, 0x51, 0x83, 0x00, 0xfd, 0x7f, 0xac, 0x20,
+ 0x3d, 0x90, 0x31, 0x75, 0xb0, 0x11, 0x81, 0x49, 0x7f, 0xc5, 0x81, 0x11, 0x7e, 0xc5, 0x81, 0xb1,
+ 0x7c, 0x00, 0xa4, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x78, 0x90,
+ 0x00, 0x64, 0x7c, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x78, 0x59, 0x82, 0x20, 0x2d, 0x8f, 0xd5,
+ 0x70, 0x01, 0x9c, 0x6c, 0x78, 0x90, 0xe1,
+}
+
+var MapsDirections = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x93, 0x99, 0x7e,
+ 0x20, 0x5c, 0x5c, 0xb0, 0x39, 0x7f, 0x39, 0x7f, 0xf5, 0x7d, 0x39, 0x7f, 0x31, 0x7d, 0x80, 0x20,
+ 0x5c, 0xa4, 0xb0, 0x39, 0x7f, 0xc9, 0x80, 0x39, 0x7f, 0x0d, 0x82, 0x80, 0xd5, 0x82, 0x20, 0xa4,
+ 0xfd, 0x91, 0xe9, 0x05, 0x80, 0xb0, 0xc9, 0x80, 0xc9, 0x80, 0x0d, 0x82, 0xc9, 0x80, 0xd5, 0x82,
+ 0x80, 0x20, 0xa4, 0x5c, 0xb0, 0xc9, 0x80, 0x35, 0x7f, 0xc9, 0x80, 0xf5, 0x7d, 0xfd, 0x7f, 0x2d,
+ 0x7d, 0xe2, 0x88, 0x8a, 0xe9, 0x76, 0xe7, 0x70, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x70, 0xb0, 0x80,
+ 0xe5, 0x7e, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xe7, 0x94, 0xe9, 0x76, 0x21, 0x8e, 0x8e, 0x72, 0x8e,
+ 0xe1,
+}
+
+var MapsDirectionsBike = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x66, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe2, 0x64, 0x80, 0xa0, 0x81, 0x6c, 0x80, 0x50, 0x81,
+ 0x84, 0x50, 0x94, 0x92, 0x81, 0x84, 0x94, 0x94, 0x94, 0x94, 0x81, 0x7b, 0x94, 0x6c, 0x81, 0x7b,
+ 0x6c, 0x6c, 0x6c, 0xe3, 0x80, 0xa2, 0xb0, 0x19, 0x7c, 0x80, 0x72, 0xe9, 0x7c, 0x72, 0x72, 0x92,
+ 0x19, 0x83, 0x72, 0x8e, 0x72, 0x8e, 0x19, 0x83, 0x8e, 0x8e, 0xe9, 0x7c, 0x8e, 0x72, 0x8e, 0xe3,
+ 0x99, 0x8b, 0x19, 0x6c, 0x21, 0xb5, 0x84, 0x35, 0x7b, 0x81, 0x81, 0x81, 0x81, 0xb0, 0x81, 0x82,
+ 0x99, 0x82, 0x8c, 0x19, 0x84, 0x19, 0x8a, 0x19, 0x84, 0xe9, 0x78, 0xb0, 0x7a, 0x80, 0x81, 0x7a,
+ 0xe9, 0x7e, 0xb5, 0x78, 0x19, 0x7d, 0x20, 0x19, 0x7c, 0x35, 0x7c, 0xb0, 0x69, 0x7f, 0x4d, 0x7f,
+ 0x69, 0x7e, 0xcd, 0x7e, 0x4d, 0x7d, 0xcd, 0x7e, 0x90, 0xe9, 0x7d, 0x69, 0x80, 0x35, 0x7d, 0x35,
+ 0x81, 0x20, 0x81, 0x7a, 0x81, 0x85, 0xb1, 0x4d, 0x7f, 0xb5, 0x80, 0xcd, 0x7e, 0xb5, 0x81, 0xcd,
+ 0x7e, 0xcd, 0x82, 0x80, 0x19, 0x81, 0x81, 0x80, 0x19, 0x82, 0x35, 0x81, 0xe9, 0x82, 0x00, 0x7c,
+ 0x88, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x81, 0x81, 0x20, 0x99, 0x7b, 0x99, 0x7b, 0xe2, 0x9c, 0x80,
+ 0xb0, 0x81, 0x7a, 0x80, 0x6c, 0x81, 0x84, 0x6c, 0x94, 0x92, 0x81, 0x84, 0x94, 0x94, 0x94, 0x94,
+ 0x81, 0x7b, 0x94, 0x6c, 0x81, 0x7b, 0x6c, 0x6c, 0x6c, 0xe3, 0x80, 0xa2, 0xb0, 0x19, 0x7c, 0x80,
+ 0x72, 0xe9, 0x7c, 0x72, 0x72, 0x92, 0x19, 0x83, 0x72, 0x8e, 0x72, 0x8e, 0x19, 0x83, 0x8e, 0x8e,
+ 0xe9, 0x7c, 0x8e, 0x72, 0x8e, 0xe1,
+}
+
+var MapsDirectionsBoat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0xa4, 0xb1, 0x39,
+ 0x7d, 0x80, 0x71, 0x7a, 0x11, 0x7f, 0x70, 0x59, 0x7d, 0x21, 0x7b, 0x6d, 0x83, 0xe1, 0x74, 0x6d,
+ 0x83, 0x60, 0x80, 0xa0, 0x91, 0x75, 0x11, 0x91, 0xc9, 0x72, 0xa4, 0x60, 0xa4, 0xe6, 0x58, 0xe9,
+ 0x88, 0xe7, 0x88, 0xb1, 0xc1, 0x82, 0x80, 0x7d, 0x85, 0x51, 0x7f, 0x90, 0x05, 0x7e, 0x0d, 0x85,
+ 0x99, 0x82, 0xf5, 0x8a, 0x99, 0x82, 0xa0, 0x80, 0xa0, 0x85, 0x8a, 0x4d, 0x95, 0x41, 0x8d, 0xac,
+ 0xa0, 0xac, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe2, 0xe5, 0x6f, 0x9c, 0xe6, 0x60, 0xb1, 0x35,
+ 0x83, 0x80, 0x0d, 0x86, 0x3d, 0x7e, 0x90, 0x78, 0xf5, 0x81, 0x3d, 0x82, 0xcd, 0x84, 0x88, 0x90,
+ 0x88, 0x90, 0x0d, 0x86, 0x3d, 0x7e, 0x90, 0x78, 0xb0, 0xf5, 0x81, 0x3d, 0x82, 0xcd, 0x84, 0x88,
+ 0x90, 0x88, 0xe7, 0x1d, 0x80, 0x20, 0xcd, 0x83, 0xa1, 0x72, 0xb1, 0x2d, 0x80, 0x7d, 0x7f, 0x21,
+ 0x80, 0xf1, 0x7e, 0xe1, 0x7f, 0x75, 0x7e, 0xc1, 0x7f, 0x85, 0x7f, 0x51, 0x7f, 0x29, 0x7f, 0xcd,
+ 0x7e, 0x05, 0x7f, 0x00, 0xa0, 0x3d, 0x7d, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe7, 0x74, 0xe8, 0x54, 0xe6, 0x74, 0xe9, 0x8c, 0xe7, 0x74, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0x3d, 0x89, 0x20, 0x71, 0x7d, 0xd9, 0x80, 0xb0, 0x7d, 0x7f,
+ 0x29, 0x80, 0x0d, 0x7f, 0x85, 0x80, 0xcd, 0x7e, 0xfd, 0x80, 0x90, 0xb5, 0x7f, 0x0d, 0x81, 0xe1,
+ 0x7f, 0x8d, 0x81, 0x00, 0xe5, 0x6f, 0x9c, 0xe2, 0x68, 0x68, 0xe7, 0xb0, 0xe9, 0xf1, 0x87, 0x00,
+ 0x80, 0x70, 0x20, 0x68, 0xf1, 0x83, 0xe8, 0x68, 0xe1,
+}
+
+var MapsDirectionsBus = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x90, 0xb0, 0x80,
+ 0xc5, 0x81, 0xc9, 0x80, 0x59, 0x83, 0x84, 0x71, 0x84, 0xe8, 0xa0, 0xb0, 0x80, 0x19, 0x81, 0xe9,
+ 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9,
+ 0x7c, 0xe7, 0xa0, 0xe9, 0x84, 0xb0, 0x80, 0x19, 0x81, 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84,
+ 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x71, 0x7c, 0xb0, 0x39, 0x81, 0xe9,
+ 0x7e, 0x84, 0x55, 0x7d, 0x84, 0x91, 0x7b, 0xe8, 0x68, 0xb0, 0x80, 0x72, 0xd9, 0x78, 0x70, 0x60,
+ 0x70, 0x80, 0x60, 0x5a, 0x60, 0x68, 0xe9, 0xa8, 0xe3, 0x8e, 0x84, 0xb0, 0x59, 0x7e, 0x80, 0x7a,
+ 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xa4, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86,
+ 0xe3, 0x86, 0x68, 0xe6, 0x68, 0xe8, 0x68, 0xe7, 0xb0, 0xe9, 0x94, 0xe1,
+}
+
+var MapsDirectionsCar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd9, 0x8d, 0x05, 0x74,
+ 0xa0, 0x71, 0x8d, 0xd9, 0x72, 0x51, 0x8c, 0x64, 0x96, 0x64, 0xe6, 0x6a, 0xb0, 0xb1, 0x7e, 0x80,
+ 0x91, 0x7d, 0xd9, 0x80, 0x29, 0x7d, 0x05, 0x82, 0x00, 0x5c, 0x80, 0xe9, 0xa0, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0xe9, 0x7c, 0xe7, 0xb0, 0xe9, 0x84, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84,
+ 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x80, 0x20, 0xd9, 0x7b,
+ 0x05, 0x74, 0xe2, 0x6a, 0x90, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59,
+ 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xac,
+ 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a,
+ 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe2, 0x64, 0x7c, 0x20, 0x86, 0x6e,
+ 0xe7, 0xac, 0x20, 0x86, 0x92, 0xe6, 0x64, 0xe1,
+}
+
+var MapsDirectionsRailway = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x8e, 0xb0, 0x80,
+ 0xe1, 0x83, 0x25, 0x83, 0x8e, 0x8e, 0x8e, 0x20, 0x7a, 0x86, 0xe9, 0x82, 0xe7, 0xb0, 0xe9, 0x7e,
+ 0x20, 0x7a, 0x7a, 0xb0, 0xe1, 0x83, 0x80, 0x8e, 0xe1, 0x7c, 0x8e, 0x72, 0xe8, 0x64, 0xb0, 0x80,
+ 0x72, 0xd9, 0x78, 0x70, 0x60, 0x70, 0x80, 0x60, 0x56, 0x60, 0x64, 0xe9, 0xaa, 0xe3, 0xa0, 0x86,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88,
+ 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x98, 0x64, 0xe6, 0x68, 0xe8, 0x64,
+ 0xe7, 0xb0, 0xe9, 0x94, 0xe1,
+}
+
+var MapsDirectionsRun = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x86, 0x62, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0xcd, 0x78, 0xcd, 0x9b, 0x21, 0xe9, 0x81, 0x35,
+ 0x77, 0x4d, 0x84, 0x88, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0xe9, 0x84, 0x21, 0xe9, 0x7b, 0xe9, 0x7b,
+ 0x35, 0x81, 0x74, 0xa0, 0xb5, 0x85, 0x7c, 0x99, 0x89, 0x80, 0x9c, 0x80, 0xe9, 0x78, 0xb0, 0x4d,
+ 0x7c, 0x80, 0x19, 0x79, 0x7c, 0x4d, 0x77, 0x19, 0x7b, 0x20, 0x19, 0x7e, 0xcd, 0x7c, 0xb1, 0x4d,
+ 0x7f, 0xcd, 0x7e, 0x7c, 0x19, 0x7e, 0x99, 0x7c, 0x19, 0x7e, 0x81, 0x7f, 0x80, 0x7e, 0x19, 0x80,
+ 0x81, 0x7e, 0x4d, 0x80, 0x00, 0x68, 0x99, 0x76, 0xe8, 0x80, 0xe7, 0x88, 0xe9, 0x4d, 0x79, 0x20,
+ 0x81, 0x83, 0x99, 0x7e, 0x00, 0x69, 0x78, 0x90, 0x21, 0x35, 0x76, 0x19, 0x7e, 0x35, 0x7f, 0xe9,
+ 0x83, 0x90, 0x9c, 0xb5, 0x82, 0x9c, 0xcd, 0x82, 0xe1,
+}
+
+var MapsDirectionsSubway = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0x29,
+ 0x77, 0x58, 0x60, 0x5a, 0x60, 0x68, 0xe9, 0xa6, 0xb0, 0x80, 0xe1, 0x83, 0x25, 0x83, 0x8e, 0x8e,
+ 0x8e, 0x20, 0x7a, 0x86, 0xe9, 0x82, 0xe7, 0xb0, 0xe9, 0x7e, 0x20, 0x7a, 0x7a, 0xb0, 0xe1, 0x83,
+ 0x80, 0x8e, 0xe1, 0x7c, 0x8e, 0x72, 0xe8, 0x68, 0xb0, 0x80, 0x72, 0xd9, 0x78, 0x70, 0x60, 0x70,
+ 0xe3, 0x6e, 0xbc, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a,
+ 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8e, 0x68, 0xe6,
+ 0x68, 0xe8, 0x68, 0xe7, 0x94, 0xe9, 0x94, 0xe3, 0x96, 0x98, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9,
+ 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0x86, 0xe3, 0x86, 0x68, 0xe6, 0x84, 0xe8, 0x68, 0xe7, 0x94, 0xe9, 0x94, 0xe1,
+}
+
+var MapsDirectionsTransit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0x29,
+ 0x77, 0x58, 0x60, 0x5a, 0x60, 0x68, 0xe9, 0xa6, 0xb0, 0x80, 0xe1, 0x83, 0x25, 0x83, 0x8e, 0x8e,
+ 0x8e, 0x20, 0x7a, 0x86, 0xe9, 0x82, 0xe7, 0xb0, 0xe9, 0x7e, 0x20, 0x7a, 0x7a, 0xb0, 0xe1, 0x83,
+ 0x80, 0x8e, 0xe1, 0x7c, 0x8e, 0x72, 0xe8, 0x68, 0xb0, 0x80, 0x72, 0xd9, 0x78, 0x70, 0x60, 0x70,
+ 0xe3, 0x6e, 0xbc, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a,
+ 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8e, 0x68, 0xe6,
+ 0x68, 0xe8, 0x68, 0xe7, 0x94, 0xe9, 0x94, 0xe3, 0x96, 0x98, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9,
+ 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0x86, 0xe3, 0x86, 0x68, 0xe6, 0x84, 0xe8, 0x68, 0xe7, 0x94, 0xe9, 0x94, 0xe1,
+}
+
+var MapsDirectionsWalk = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x86, 0x62, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x81, 0x78, 0xcd, 0x86, 0x00, 0x6c, 0xa8, 0xe7,
+ 0x35, 0x84, 0x21, 0x81, 0x83, 0x60, 0x4d, 0x84, 0x88, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0xe9, 0x84,
+ 0x21, 0xe9, 0x7b, 0xe9, 0x7b, 0x35, 0x81, 0x74, 0xa0, 0xb5, 0x85, 0x7c, 0x99, 0x89, 0x80, 0x9c,
+ 0x80, 0xe9, 0x78, 0xb0, 0x4d, 0x7c, 0x80, 0x19, 0x79, 0x7c, 0x4d, 0x77, 0x19, 0x7b, 0x20, 0x19,
+ 0x7e, 0xcd, 0x7c, 0xb1, 0x4d, 0x7f, 0xcd, 0x7e, 0x7c, 0x19, 0x7e, 0x99, 0x7c, 0x19, 0x7e, 0x81,
+ 0x7f, 0x80, 0x7e, 0x19, 0x80, 0x81, 0x7e, 0x4d, 0x80, 0x00, 0x68, 0x99, 0x76, 0xe8, 0x80, 0xe7,
+ 0x88, 0xe9, 0x4d, 0x79, 0x20, 0x81, 0x83, 0x81, 0x7e, 0xe1,
+}
+
+var MapsEditLocation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb1, 0x49,
+ 0x78, 0x80, 0x64, 0x49, 0x86, 0x64, 0x9c, 0x80, 0x81, 0x8a, 0x9c, 0xb4, 0x9c, 0xb4, 0x90, 0x9c,
+ 0x81, 0x70, 0x9c, 0x4c, 0xb0, 0x80, 0x49, 0x78, 0xb9, 0x79, 0x64, 0x64, 0x64, 0xe3, 0xe1, 0x7c,
+ 0xa8, 0xe6, 0x74, 0xe9, 0x21, 0x7d, 0x22, 0xb5, 0x86, 0x51, 0x79, 0xdd, 0x82, 0xdd, 0x82, 0x51,
+ 0x79, 0xb5, 0x86, 0xe3, 0xe9, 0x88, 0x19, 0x77, 0x22, 0x99, 0x7e, 0x69, 0x81, 0x21, 0x7d, 0x21,
+ 0x7d, 0x69, 0x81, 0x99, 0x7e, 0xb0, 0x4d, 0x80, 0xb5, 0x7f, 0xc9, 0x80, 0xb5, 0x7f, 0x15, 0x81,
+ 0x80, 0x20, 0xcd, 0x81, 0xcd, 0x81, 0xb0, 0x4d, 0x80, 0x4d, 0x80, 0x4d, 0x80, 0xc9, 0x80, 0x80,
+ 0x15, 0x81, 0xe1,
+}
+
+var MapsEVStation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8d, 0x8f, 0x75, 0x76,
+ 0x20, 0x09, 0x80, 0xf9, 0x7f, 0x01, 0x21, 0x88, 0x5e, 0x8c, 0x21, 0x71, 0x20, 0x39, 0x84, 0x39,
+ 0x84, 0xa0, 0x59, 0x88, 0x11, 0x76, 0x8e, 0xe1, 0x77, 0x8e, 0x74, 0xb1, 0x80, 0xc5, 0x82, 0x3d,
+ 0x82, 0x8a, 0x8a, 0x8a, 0xb5, 0x80, 0x80, 0x65, 0x81, 0xd9, 0x7f, 0x84, 0x95, 0x7f, 0xe8, 0x9a,
+ 0xb0, 0x80, 0x19, 0x81, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0x90, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9,
+ 0x6e, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x7c, 0xe8, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x68, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xe9, 0xc0, 0xe7, 0xa8, 0xe8, 0x86, 0xe7, 0x86, 0xe9, 0x94, 0xb0, 0x80, 0xc5, 0x82,
+ 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0x90, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xe8, 0x74, 0xb0, 0x80, 0xa1,
+ 0x7e, 0x71, 0x7f, 0x61, 0x7d, 0x8d, 0x7e, 0x75, 0x7c, 0xe2, 0x70, 0x98, 0xe9, 0x6e, 0xe7, 0x78,
+ 0x20, 0x90, 0x62, 0xe9, 0x94, 0xe7, 0x88, 0x20, 0x70, 0x9c, 0xe3, 0xa8, 0x60, 0xb0, 0xe9, 0x7e,
+ 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0x92, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0x84, 0xe9, 0x80, 0x84,
+ 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0xe1,
+}
+
+var MapsFlight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x90, 0xe9, 0x78,
+ 0x00, 0x84, 0x74, 0xe8, 0x5e, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a,
+ 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x96, 0x00, 0x58, 0x88, 0xe9, 0x88, 0x20, 0xa0, 0x76, 0xe9, 0x96,
+ 0x20, 0x78, 0x86, 0xe9, 0x86, 0x21, 0x8e, 0x7c, 0x8e, 0x84, 0xe9, 0x7a, 0x20, 0x78, 0x7a, 0xe8,
+ 0x86, 0x20, 0xa0, 0x8a, 0xe1,
+}
+
+var MapsHotel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x84, 0xb0, 0x51,
+ 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x92, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x74, 0xb1, 0x82,
+ 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3, 0xb0, 0x68, 0xe6, 0x7c, 0xe9, 0x9c, 0xe6, 0x5c,
+ 0xe8, 0x64, 0xe6, 0x54, 0xe9, 0xbc, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0xc8, 0xe9, 0x8c, 0xe7, 0x88,
+ 0xe8, 0x7c, 0xb0, 0x80, 0x95, 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe1,
+}
+
+var MapsLayers = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x15, 0x8d,
+ 0x01, 0x3d, 0x71, 0x9d, 0x81, 0x5c, 0x21, 0x84, 0x23, 0xa4, 0x9c, 0xa4, 0x64, 0xbd, 0x7c, 0x79,
+ 0x7d, 0x41, 0x71, 0x7d, 0x8b, 0xe2, 0x80, 0x90, 0x20, 0xbd, 0x8e, 0x8d, 0x74, 0x02, 0xa4, 0x74,
+ 0x80, 0x58, 0x5c, 0x74, 0x20, 0x45, 0x83, 0x89, 0x82, 0x00, 0x80, 0x90, 0xe1,
+}
+
+var MapsLayersClear = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa1, 0x8f, 0xfd, 0x85,
+ 0x00, 0xa4, 0x21, 0x84, 0x22, 0x29, 0x7d, 0x29, 0x7d, 0xa1, 0x7d, 0xd9, 0x81, 0xd9, 0x82, 0xd9,
+ 0x82, 0xe3, 0x1d, 0x7f, 0x91, 0x76, 0x01, 0xa4, 0x74, 0x80, 0x58, 0x22, 0x2d, 0x7a, 0x89, 0x84,
+ 0xc1, 0x8f, 0xc1, 0x8f, 0xd1, 0x84, 0x45, 0x7c, 0xe2, 0x8d, 0x6e, 0x54, 0x00, 0x58, 0x8d, 0x6c,
+ 0x20, 0x71, 0x88, 0x71, 0x88, 0x00, 0x5c, 0x74, 0x20, 0x45, 0x83, 0x89, 0x82, 0x00, 0x80, 0x90,
+ 0x22, 0x31, 0x84, 0xbd, 0x7c, 0xd9, 0x82, 0xd9, 0x82, 0xf1, 0x78, 0x7d, 0x85, 0x01, 0x3d, 0x71,
+ 0x9d, 0x81, 0x5c, 0x21, 0x84, 0x21, 0xa4, 0x9c, 0xe5, 0x89, 0x4d, 0x78, 0x02, 0x75, 0x91, 0xa4,
+ 0xa8, 0x75, 0x8f, 0x8d, 0x6e, 0x54, 0xe1,
+}
+
+var MapsLocalActivity = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x80, 0xb0, 0x80,
+ 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x20,
+ 0xfd, 0x7f, 0x90, 0xa0, 0x39, 0x6e, 0x05, 0x7c, 0x60, 0xcd, 0x7d, 0x60, 0x80, 0xb0, 0x80, 0x35,
+ 0x82, 0x35, 0x7e, 0x88, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe3, 0x29, 0x77, 0x99, 0x89, 0x00, 0x80,
+ 0x8a, 0x20, 0xd9, 0x78, 0x99, 0x84, 0x00, 0x76, 0x61, 0x81, 0x21, 0x6d, 0x79, 0xa1, 0x7a, 0x7d,
+ 0x88, 0x81, 0x7f, 0x00, 0x80, 0x99, 0x73, 0x21, 0x19, 0x83, 0xe5, 0x87, 0x7d, 0x88, 0x81, 0x80,
+ 0x00, 0x8a, 0x61, 0x81, 0x20, 0x29, 0x82, 0x3d, 0x88, 0xe1,
+}
+
+var MapsLocalAirport = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x90, 0xe9, 0x78,
+ 0x00, 0x84, 0x74, 0xe8, 0x5e, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a,
+ 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x96, 0x00, 0x58, 0x88, 0xe9, 0x88, 0x20, 0xa0, 0x76, 0xe9, 0x96,
+ 0x20, 0x78, 0x86, 0xe9, 0x86, 0x21, 0x8e, 0x7c, 0x8e, 0x84, 0xe9, 0x7a, 0x20, 0x78, 0x7a, 0xe8,
+ 0x86, 0x20, 0xa0, 0x8a, 0xe1,
+}
+
+var MapsLocalATM = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x94, 0xe7, 0x88,
+ 0xe9, 0x7c, 0xe7, 0x84, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x74, 0xb0,
+ 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe7, 0x74, 0xe9, 0x7c, 0xe7, 0x90, 0xe9, 0x78,
+ 0xe7, 0x78, 0xe9, 0x7c, 0xe7, 0x78, 0xe9, 0x84, 0xe7, 0x7c, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0xe9,
+ 0x80, 0x7c, 0x84, 0xe9, 0x8c, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x8c,
+ 0xe9, 0x84, 0xe7, 0x70, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x84, 0xe2, 0xa0, 0x60, 0xe6, 0x60, 0xb0,
+ 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6,
+ 0x60, 0xe8, 0x68, 0xe7, 0xc0, 0xe9, 0xb0, 0xe1,
+}
+
+var MapsLocalBar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x64, 0xe8, 0x5c,
+ 0xe6, 0x5c, 0xe9, 0x88, 0x20, 0xa0, 0xa4, 0xe9, 0x94, 0xe6, 0x68, 0xe9, 0x88, 0xe7, 0xb0, 0xe9,
+ 0x78, 0xe6, 0x84, 0xe8, 0x88, 0x20, 0xa0, 0x5c, 0xe3, 0xe1, 0x64, 0x88, 0x20, 0x71, 0x7c, 0x78,
+ 0xe7, 0x61, 0x99, 0x20, 0x71, 0x7c, 0x88, 0xe6, 0xe1, 0x76, 0xe1,
+}
+
+var MapsLocalCafe = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x5c, 0xe6, 0x60,
+ 0xe9, 0xa8, 0xb0, 0x80, 0x6d, 0x84, 0x95, 0x83, 0x90, 0x90, 0x90, 0xe7, 0x98, 0xb0, 0x6d, 0x84,
+ 0x80, 0x90, 0x6d, 0x7c, 0x90, 0x70, 0xe9, 0x74, 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe9, 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80,
+ 0x94, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe2, 0x58, 0xa4, 0xe7, 0xc8, 0xe9, 0x78,
+ 0xe6, 0x58, 0xe9, 0x88, 0xe1,
+}
+
+var MapsLocalCarWash = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x64, 0xb1, 0xa9,
+ 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x80, 0x7c, 0x7a, 0x99, 0x7a, 0x7a, 0x99, 0x7a, 0x80,
+ 0x8e, 0x5a, 0x8e, 0x5e, 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x6c, 0x80,
+ 0xb1, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x80, 0x7c, 0x7a, 0x99, 0x7a, 0x7a, 0x99,
+ 0x7a, 0x80, 0x7a, 0x5a, 0x7a, 0x5e, 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3,
+ 0x6c, 0x80, 0xb1, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x80, 0x7c, 0x7a, 0x99, 0x7a,
+ 0x7a, 0x99, 0x7a, 0x80, 0x66, 0x5a, 0x66, 0x5e, 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86,
+ 0x86, 0xe3, 0xd9, 0x97, 0x05, 0x86, 0xa0, 0x71, 0x8d, 0xd9, 0x76, 0x51, 0x8c, 0x6c, 0x96, 0x6c,
+ 0xe6, 0x6a, 0xb0, 0xb1, 0x7e, 0x80, 0x91, 0x7d, 0xd9, 0x80, 0x29, 0x7d, 0x05, 0x82, 0x00, 0x5c,
+ 0x88, 0xe9, 0xa0, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d,
+ 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x7c, 0xe7, 0xb0, 0xe9, 0x84, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0xe8, 0x88, 0x20, 0xd9, 0x7b, 0x05, 0x74, 0xe2, 0x6a, 0x98, 0xb0, 0x59, 0x7e, 0x80, 0x7a,
+ 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xac, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86,
+ 0xe2, 0x64, 0x84, 0x20, 0x86, 0x6e, 0xe7, 0xac, 0x20, 0x86, 0x92, 0xe6, 0x64, 0xe1,
+}
+
+var MapsLocalConvenienceStore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x6c, 0xe8, 0x60,
+ 0xe6, 0x64, 0xe9, 0x8c, 0xe6, 0x58, 0xe9, 0xb4, 0xe7, 0xa0, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90,
+ 0xe7, 0xa0, 0xe8, 0x6c, 0xe7, 0x74, 0xe3, 0x60, 0x8c, 0xe7, 0x78, 0xe9, 0x84, 0xe7, 0x88, 0xe9,
+ 0x84, 0xe7, 0x74, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x7c, 0xe7, 0x78, 0xe9, 0x7c, 0xe7, 0x8c, 0xe9,
+ 0x8c, 0xe3, 0x94, 0x88, 0xe7, 0x7c, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x84, 0xe9, 0x88,
+ 0xe7, 0x84, 0xe9, 0x78, 0xe7, 0x84, 0xe9, 0x94, 0xe1,
+}
+
+var MapsLocalDining = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x35, 0x78, 0xb1, 0x82,
+ 0x20, 0xa9, 0x85, 0x59, 0x7a, 0x00, 0xd5, 0x6f, 0x5e, 0xb0, 0xe1, 0x7c, 0x21, 0x83, 0xe1, 0x7c,
+ 0x31, 0x88, 0x80, 0x51, 0x8b, 0x20, 0x61, 0x88, 0x61, 0x88, 0xe3, 0x91, 0x8d, 0x61, 0x7c, 0xb1,
+ 0x0d, 0x83, 0x71, 0x81, 0x5d, 0x87, 0x6d, 0x80, 0x8d, 0x8a, 0x3d, 0x7d, 0xd5, 0x83, 0x2d, 0x7c,
+ 0x91, 0x84, 0xb5, 0x76, 0xa1, 0x81, 0xc5, 0x73, 0xa0, 0x9e, 0x25, 0x6d, 0x85, 0x89, 0xe1, 0x6d,
+ 0xb5, 0x85, 0xb5, 0x71, 0xb1, 0xd1, 0x7c, 0x31, 0x83, 0xd1, 0x7b, 0x7d, 0x87, 0x3d, 0x7d, 0x8d,
+ 0x8a, 0x8d, 0x7b, 0x71, 0x84, 0x79, 0x6c, 0x85, 0x93, 0x79, 0x6c, 0x85, 0x93, 0x20, 0xd5, 0x82,
+ 0xd5, 0x82, 0x00, 0x80, 0xd5, 0x84, 0x21, 0xc5, 0x8d, 0xc5, 0x8d, 0xd5, 0x82, 0x2d, 0x7d, 0x00,
+ 0xd5, 0x82, 0x84, 0x20, 0xf1, 0x82, 0x11, 0x7d, 0xe1,
+}
+
+var MapsLocalDrink = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x58, 0x20, 0x09,
+ 0x84, 0x79, 0xa4, 0xa0, 0x45, 0x72, 0x75, 0x92, 0xf5, 0x73, 0xa8, 0x6c, 0xa8, 0xe7, 0xa8, 0xb0,
+ 0x0d, 0x82, 0x80, 0xbd, 0x83, 0x75, 0x7e, 0xf9, 0x83, 0x79, 0x7c, 0x00, 0xa4, 0x58, 0xe6, 0x5c,
+ 0xe3, 0xa4, 0xc4, 0xb1, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0x80, 0x78, 0x8c, 0x35,
+ 0x75, 0x8c, 0x35, 0x75, 0x80, 0x8c, 0x88, 0x8c, 0x90, 0xb0, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x8c,
+ 0x74, 0x8c, 0xe3, 0xa9, 0x8c, 0x54, 0xe7, 0xb5, 0x66, 0x20, 0x21, 0x7f, 0x70, 0xe7, 0x11, 0x9b,
+ 0x20, 0x1d, 0x7f, 0x90, 0xe1,
+}
+
+var MapsLocalFlorist = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xa8, 0xb1, 0xf1,
+ 0x89, 0x80, 0xa4, 0xf1, 0x77, 0xa4, 0x5c, 0x11, 0x76, 0x80, 0x5c, 0x11, 0x88, 0x5c, 0xa4, 0xe2,
+ 0x35, 0x73, 0x81, 0x7c, 0xb1, 0x80, 0xc5, 0x82, 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0x0d, 0x81, 0x80,
+ 0x09, 0x82, 0xad, 0x7f, 0xd5, 0x82, 0x21, 0x7f, 0x00, 0x76, 0x82, 0xb0, 0x80, 0xc5, 0x82, 0x3d,
+ 0x82, 0x8a, 0x8a, 0x8a, 0x90, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x20, 0xf5, 0x7f, 0xa1, 0x7f, 0xb5,
+ 0xd1, 0x80, 0x91, 0x80, 0xc9, 0x81, 0xe1, 0x80, 0xd5, 0x82, 0xe1, 0x80, 0xc5, 0x82, 0x80, 0x8a,
+ 0xc5, 0x7d, 0x8a, 0x76, 0x80, 0x05, 0x7e, 0xd5, 0x7e, 0x4d, 0x7c, 0x25, 0x7d, 0x81, 0x7b, 0xb1,
+ 0x81, 0x35, 0x7f, 0xdd, 0x82, 0x7d, 0x7d, 0xdd, 0x82, 0x81, 0x7b, 0x80, 0x3d, 0x7d, 0xc5, 0x7d,
+ 0x76, 0x76, 0x76, 0xf5, 0x7e, 0x80, 0xf9, 0x7d, 0x55, 0x80, 0x2d, 0x7d, 0xe1, 0x80, 0x00, 0x8a,
+ 0x5e, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76, 0x8a,
+ 0x20, 0x0d, 0x80, 0x61, 0x80, 0xb3, 0x31, 0x7f, 0x71, 0x7f, 0x39, 0x7e, 0x21, 0x7f, 0x2d, 0x7d,
+ 0x21, 0x7f, 0x3d, 0x7d, 0x80, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0x80, 0xfd, 0x81, 0x2d, 0x81, 0xb5,
+ 0x83, 0xdd, 0x82, 0x81, 0x84, 0x51, 0x7e, 0xcd, 0x80, 0x25, 0x7d, 0x85, 0x82, 0x25, 0x7d, 0x81,
+ 0x84, 0xe2, 0x80, 0x66, 0xb0, 0xc5, 0x82, 0x80, 0x8a, 0x3d, 0x82, 0x8a, 0x8a, 0x92, 0xc5, 0x7d,
+ 0x8a, 0x76, 0x8a, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0xe2, 0x5c, 0x84,
+ 0xb1, 0x80, 0xf1, 0x89, 0x11, 0x88, 0xa4, 0xa4, 0xa4, 0x80, 0x11, 0x76, 0xf1, 0x77, 0x5c, 0x5c,
+ 0x5c, 0xe1,
+}
+
+var MapsLocalGasStation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8d, 0x8f, 0x75, 0x76,
+ 0x20, 0x09, 0x80, 0xf9, 0x7f, 0x01, 0x21, 0x88, 0x5e, 0x8c, 0x21, 0x71, 0x20, 0x39, 0x84, 0x39,
+ 0x84, 0xa0, 0x59, 0x88, 0x11, 0x76, 0x8e, 0xe1, 0x77, 0x8e, 0x74, 0xb1, 0x80, 0xc5, 0x82, 0x3d,
+ 0x82, 0x8a, 0x8a, 0x8a, 0xb5, 0x80, 0x80, 0x65, 0x81, 0xd9, 0x7f, 0x84, 0x95, 0x7f, 0xe8, 0x9a,
+ 0xb0, 0x80, 0x19, 0x81, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0x90, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9,
+ 0x6e, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0x7c, 0xe8, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x68, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xe9, 0xc0, 0xe7, 0xa8, 0xe8, 0x86, 0xe7, 0x86, 0xe9, 0x94, 0xb0, 0x80, 0xc5, 0x82,
+ 0x3d, 0x82, 0x8a, 0x8a, 0x8a, 0x90, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0xe8, 0x74, 0xb0, 0x80, 0xa1,
+ 0x7e, 0x71, 0x7f, 0x61, 0x7d, 0x8d, 0x7e, 0x75, 0x7c, 0xe2, 0x80, 0x78, 0xe6, 0x68, 0xe8, 0x64,
+ 0xe7, 0x98, 0xe9, 0x94, 0xe3, 0x98, 0x80, 0xb0, 0xe9, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c, 0x7c,
+ 0x92, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0x84,
+ 0xe1,
+}
+
+var MapsLocalGroceryStore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x98, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x92, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88,
+ 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x54, 0x58, 0xe9, 0x88, 0xe7,
+ 0x88, 0x21, 0x31, 0x87, 0x2d, 0x8f, 0x4d, 0x7d, 0xe9, 0x84, 0xb1, 0xb1, 0x7f, 0x95, 0x80, 0x85,
+ 0x7f, 0x3d, 0x81, 0x85, 0x7f, 0xf1, 0x81, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xe9, 0x78, 0xe6, 0xd9, 0x76, 0xb1, 0xb9, 0x7f, 0x80, 0x81, 0x7f, 0xc9, 0x7f, 0x81, 0x7f,
+ 0x81, 0x7f, 0x80, 0xe9, 0x7f, 0x05, 0x80, 0xd5, 0x7f, 0x11, 0x80, 0xc5, 0x7f, 0x00, 0x35, 0x78,
+ 0x84, 0xe7, 0xe9, 0x8e, 0xb0, 0x81, 0x81, 0x80, 0xd1, 0x82, 0x2d, 0x7f, 0x81, 0x83, 0xf1, 0x7d,
+ 0x20, 0x29, 0x87, 0x05, 0x73, 0xb1, 0x29, 0x80, 0xb9, 0x7f, 0x41, 0x80, 0x65, 0x7f, 0x41, 0x80,
+ 0x0d, 0x7f, 0x80, 0xe5, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x71, 0x72, 0x20, 0x19, 0x7e,
+ 0x78, 0xe6, 0x54, 0xe3, 0xc0, 0xc0, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c,
+ 0x88, 0x92, 0xc5, 0x81, 0x88, 0xfd, 0x83, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe1,
+}
+
+var MapsLocalHospital = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x7c, 0xac,
+ 0xe7, 0x70, 0xe9, 0x90, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x70,
+ 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x90, 0xe1,
+}
+
+var MapsLocalHotel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x84, 0xb0, 0x51,
+ 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x92, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x74, 0xb1, 0x82,
+ 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3, 0xb0, 0x68, 0xe6, 0x7c, 0xe9, 0x9c, 0xe6, 0x5c,
+ 0xe8, 0x64, 0xe6, 0x54, 0xe9, 0xbc, 0xe7, 0x88, 0xe9, 0x74, 0xe7, 0xc8, 0xe9, 0x8c, 0xe7, 0x88,
+ 0xe8, 0x7c, 0xb0, 0x80, 0x95, 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe1,
+}
+
+var MapsLocalLaundryService = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x59, 0x7a, 0xa9, 0x89,
+ 0xb1, 0x21, 0x83, 0x21, 0x83, 0x31, 0x88, 0x21, 0x83, 0x51, 0x8b, 0x80, 0x21, 0x83, 0xe1, 0x7c,
+ 0x21, 0x83, 0xd1, 0x77, 0x80, 0xb1, 0x74, 0x00, 0x59, 0x7a, 0xa9, 0x89, 0xe2, 0x98, 0x05, 0x6c,
+ 0x00, 0x68, 0x58, 0xa0, 0xcd, 0x71, 0x58, 0x60, 0xcd, 0x6d, 0x60, 0x60, 0xe9, 0xc0, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe2,
+ 0x78, 0x60, 0xb0, 0x19, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84, 0x7c,
+ 0x84, 0x7c, 0x19, 0x7f, 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x74, 0x80, 0xb0, 0x19,
+ 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84, 0x92, 0x19, 0x7f, 0x84, 0x7c, 0x84, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe3, 0x94, 0xc0, 0xb0, 0x61, 0x79, 0x80, 0x68, 0xa1,
+ 0x7a, 0x68, 0x68, 0x92, 0x61, 0x85, 0x68, 0x98, 0x68, 0x98, 0x61, 0x85, 0x98, 0x98, 0xa1, 0x7a,
+ 0x98, 0x68, 0x98, 0xe1,
+}
+
+var MapsLocalLibrary = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x19, 0x7f, 0xa0,
+ 0x45, 0x7b, 0xb5, 0x7a, 0xf5, 0x74, 0x70, 0x5c, 0x70, 0xe9, 0xac, 0xb0, 0xf5, 0x86, 0x80, 0x45,
+ 0x8d, 0xb5, 0x82, 0xa4, 0x19, 0x87, 0xa0, 0xbd, 0x84, 0xb5, 0x90, 0x0d, 0x8b, 0x9c, 0xa4, 0x9c,
+ 0xe8, 0x70, 0xb0, 0x0d, 0x79, 0x80, 0xbd, 0x72, 0xb5, 0x82, 0x5c, 0x19, 0x87, 0xe2, 0x80, 0x70,
+ 0xb0, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x92, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x74,
+ 0xb1, 0x82, 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe1,
+}
+
+var MapsLocalMall = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x68, 0xe7, 0x78,
+ 0xb0, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x80, 0x6c, 0x7d, 0x6e, 0x6c, 0x68, 0xe7,
+ 0x78, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0xa0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80,
+ 0x5c, 0xb0, 0x51, 0x83, 0x80, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0xe6, 0x74, 0xb0, 0x80, 0xb1, 0x7c,
+ 0xb1, 0x82, 0x74, 0x8c, 0x74, 0xe3, 0x80, 0xa8, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b, 0x6c,
+ 0x6c, 0xe7, 0x88, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x90, 0x8c, 0x51, 0x7d,
+ 0x8c, 0x74, 0xe7, 0x88, 0xb0, 0x80, 0x85, 0x85, 0x85, 0x7b, 0x94, 0x6c, 0x94, 0xe1,
+}
+
+var MapsLocalMovies = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x5c, 0xe9, 0x88,
+ 0xe7, 0x78, 0xe8, 0x5c, 0xe6, 0x70, 0xe9, 0x88, 0xe7, 0x78, 0xe8, 0x5c, 0xe6, 0x60, 0xe9, 0xc8,
+ 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe2, 0x70, 0x94, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0xa8, 0xa0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var MapsLocalOffer = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd1, 0x92, 0x29, 0x7f,
+ 0x00, 0xd5, 0x80, 0x2d, 0x6d, 0xa0, 0x19, 0x80, 0x75, 0x6c, 0x19, 0x7f, 0x58, 0x7c, 0x58, 0xe6,
+ 0x60, 0xa0, 0xcd, 0x6d, 0x58, 0x58, 0xcd, 0x6d, 0x58, 0x60, 0xe9, 0x9c, 0xb0, 0x80, 0x1d, 0x81,
+ 0x75, 0x80, 0x1d, 0x82, 0x31, 0x81, 0xd5, 0x82, 0x20, 0xa4, 0xa4, 0xa0, 0xe9, 0x7f, 0x8d, 0x93,
+ 0xe9, 0x80, 0xa8, 0x84, 0xa8, 0xb0, 0x19, 0x81, 0x80, 0x1d, 0x82, 0x8d, 0x7f, 0xd5, 0x82, 0xd5,
+ 0x7e, 0x20, 0x9c, 0x64, 0xa0, 0x8d, 0x93, 0x19, 0x84, 0xa8, 0x19, 0x83, 0xa8, 0x84, 0xb0, 0x80,
+ 0xe5, 0x7e, 0x8d, 0x7f, 0xe5, 0x7d, 0xd1, 0x7e, 0x29, 0x7d, 0xe2, 0x66, 0x6c, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var MapsLocalParking = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x5c, 0xe6, 0x68,
+ 0xe9, 0xc8, 0xe7, 0x90, 0xe8, 0x8c, 0xe7, 0x8c, 0xb0, 0xa1, 0x86, 0x80, 0x98, 0xa1, 0x7a, 0x98,
+ 0x68, 0x80, 0xa1, 0x88, 0x5c, 0x84, 0x5c, 0xe3, 0x69, 0x80, 0xa0, 0xe6, 0x78, 0xe9, 0x70, 0xe7,
+ 0x69, 0x86, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x90, 0x35, 0x7e, 0x88, 0x78,
+ 0x88, 0xe1,
+}
+
+var MapsLocalPharmacy = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x64, 0xe7, 0xb5,
+ 0x7a, 0x01, 0x9e, 0xb5, 0x6b, 0x4d, 0x8a, 0x54, 0x20, 0x19, 0x7d, 0x90, 0xe6, 0x5c, 0xe9, 0x88,
+ 0x21, 0x88, 0x98, 0x78, 0x98, 0xe9, 0x88, 0xe7, 0xc8, 0xe9, 0x78, 0x21, 0x78, 0x68, 0x88, 0x68,
+ 0xe9, 0x78, 0xe2, 0x90, 0x88, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x74, 0xe9,
+ 0x78, 0xe7, 0x8c, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe1,
+}
+
+var MapsLocalPhone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x41, 0x75, 0x99, 0x7d,
+ 0xb0, 0xe1, 0x82, 0xa9, 0x85, 0x85, 0x87, 0x4d, 0x8a, 0x31, 0x8d, 0x2d, 0x8d, 0x20, 0x69, 0x84,
+ 0x99, 0x7b, 0xb0, 0x8d, 0x80, 0x75, 0x7f, 0x59, 0x81, 0x4d, 0x7f, 0x09, 0x82, 0x85, 0x7f, 0xa0,
+ 0x19, 0x8b, 0x99, 0x86, 0x85, 0x8d, 0x8e, 0xa0, 0x8e, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0xe5, 0x80,
+ 0x84, 0x84, 0xe9, 0x8e, 0xb0, 0x80, 0x1d, 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xa0, 0x39, 0x7d,
+ 0xa4, 0x5c, 0xc9, 0x82, 0x5c, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0xe9, 0x80, 0x7c, 0x84, 0x7c, 0xe7,
+ 0x8e, 0xb2, 0x1d, 0x81, 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x80, 0x7d, 0x82, 0x69, 0x80, 0xe9,
+ 0x84, 0x25, 0x81, 0x25, 0x87, 0x39, 0x80, 0xb1, 0x80, 0x11, 0x80, 0x7d, 0x81, 0x85, 0x7f, 0x09,
+ 0x82, 0x20, 0x99, 0x7b, 0x6d, 0x84, 0xe1,
+}
+
+var MapsLocalPizza = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb0, 0xe1,
+ 0x78, 0x80, 0x75, 0x72, 0x19, 0x83, 0x05, 0x6e, 0x90, 0x00, 0x80, 0xa8, 0x20, 0xfd, 0x91, 0x05,
+ 0x60, 0xa0, 0x91, 0x8d, 0x19, 0x6f, 0x25, 0x87, 0x58, 0x80, 0x58, 0xe2, 0x6c, 0x6c, 0xb0, 0x80,
+ 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x92, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe3, 0x94, 0xa0, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35,
+ 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x88, 0xe1,
+}
+
+var MapsLocalPlay = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x80, 0xb0, 0x80,
+ 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe9, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x20,
+ 0xfd, 0x7f, 0x90, 0xa0, 0x39, 0x6e, 0x05, 0x7c, 0x60, 0xcd, 0x7d, 0x60, 0x80, 0xb0, 0x80, 0x35,
+ 0x82, 0x35, 0x7e, 0x88, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe3, 0x29, 0x77, 0x99, 0x89, 0x00, 0x80,
+ 0x8a, 0x20, 0xd9, 0x78, 0x99, 0x84, 0x00, 0x76, 0x61, 0x81, 0x21, 0x6d, 0x79, 0xa1, 0x7a, 0x7d,
+ 0x88, 0x81, 0x7f, 0x00, 0x80, 0x99, 0x73, 0x21, 0x19, 0x83, 0xe5, 0x87, 0x7d, 0x88, 0x81, 0x80,
+ 0x00, 0x8a, 0x61, 0x81, 0x20, 0x29, 0x82, 0x3d, 0x88, 0xe1,
+}
+
+var MapsLocalPostOffice = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0x90,
+ 0x01, 0x80, 0x84, 0x60, 0x70, 0xe9, 0x78, 0x21, 0xa0, 0x94, 0xa0, 0x6c, 0xe9, 0x88, 0xe1,
+}
+
+var MapsLocalPrintshop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x70, 0xe6, 0x64,
+ 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x98, 0xe7, 0x90, 0xe9, 0x90, 0xe7,
+ 0xb0, 0xe9, 0x70, 0xe7, 0x90, 0xe8, 0x7c, 0xb0, 0x80, 0xb1, 0x7c, 0x51, 0x7d, 0x74, 0x74, 0x74,
+ 0xe3, 0x74, 0xac, 0xe6, 0x70, 0xe8, 0x88, 0xe7, 0xa0, 0xe9, 0x94, 0xe3, 0x8c, 0x64, 0xb0, 0xe5,
+ 0x7e, 0x80, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0x90, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xb0, 0x1d, 0x81,
+ 0x80, 0x84, 0xe5, 0x80, 0x84, 0x84, 0x90, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe2, 0x98, 0x5c, 0xe6,
+ 0x68, 0xe9, 0x90, 0xe7, 0xb0, 0xe8, 0x5c, 0xe1,
+}
+
+var MapsLocalSee = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x58, 0x20, 0x59,
+ 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0,
+ 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x58, 0xe6, 0x74, 0xe3, 0x8c, 0xbc, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x85, 0x7b,
+ 0x6c, 0x6c, 0x92, 0x7d, 0x84, 0x6c, 0x94, 0x6c, 0x94, 0x7d, 0x84, 0x94, 0x94, 0x85, 0x7b, 0x94,
+ 0x6c, 0x94, 0xe2, 0x99, 0x79, 0x80, 0xd1, 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0xcd, 0x8c, 0x80,
+ 0x69, 0x86, 0x69, 0x86, 0x00, 0x04, 0x35, 0x73, 0x80, 0xe1,
+}
+
+var MapsLocalShipping = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x70, 0xe7, 0x74,
+ 0xe8, 0x60, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xac, 0xe7,
+ 0x88, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x90, 0x8c, 0x51, 0x7d, 0x8c, 0x74,
+ 0xe7, 0x98, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x90, 0x8c, 0x51, 0x7d, 0x8c,
+ 0x74, 0xe7, 0x88, 0xe8, 0x80, 0x20, 0x74, 0x70, 0xe2, 0x68, 0x9a, 0xb0, 0x59, 0x7e, 0x80, 0x7a,
+ 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xb6, 0x5c, 0x20, 0xf1, 0x83, 0x8a, 0xe6, 0x94, 0xe9, 0x76, 0xe7,
+ 0x8a, 0xe3, 0x7a, 0xa4, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81,
+ 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var MapsLocalTaxi = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd9, 0x8d, 0x05, 0x74,
+ 0xa0, 0x71, 0x8d, 0xd9, 0x72, 0x51, 0x8c, 0x64, 0x96, 0x64, 0xe7, 0x76, 0xe8, 0x5c, 0xe6, 0x74,
+ 0xe9, 0x88, 0xe7, 0x76, 0xb0, 0xb1, 0x7e, 0x80, 0x91, 0x7d, 0xd9, 0x80, 0x29, 0x7d, 0x05, 0x82,
+ 0x00, 0x5c, 0x80, 0xe9, 0xa0, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84,
+ 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x7c, 0xe7, 0xb0, 0xe9, 0x84, 0xb0,
+ 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19,
+ 0x7f, 0x84, 0x7c, 0xe8, 0x80, 0x20, 0xd9, 0x7b, 0x05, 0x74, 0xe2, 0x6a, 0x90, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xac, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e,
+ 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86,
+ 0x7a, 0x86, 0xe2, 0x64, 0x7c, 0x20, 0x86, 0x6e, 0xe7, 0xac, 0x20, 0x86, 0x92, 0xe6, 0x64, 0xe1,
+}
+
+var MapsMap = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa2, 0x5c, 0xb0, 0xe5,
+ 0x7f, 0x80, 0xcd, 0x7f, 0x05, 0x80, 0xb1, 0x7f, 0x0d, 0x80, 0x02, 0x8c, 0x35, 0x72, 0x74, 0x5c,
+ 0xbd, 0x6e, 0xcd, 0x71, 0xb0, 0x95, 0x7f, 0x25, 0x80, 0x45, 0x7f, 0x81, 0x80, 0x45, 0x7f, 0xf5,
+ 0x80, 0xe8, 0xa2, 0xb1, 0x80, 0x8d, 0x80, 0x75, 0x80, 0x82, 0x82, 0x82, 0x1d, 0x80, 0x80, 0x35,
+ 0x80, 0xfd, 0x7f, 0x51, 0x80, 0xf5, 0x7f, 0x01, 0x74, 0xcd, 0x8d, 0x8c, 0xa4, 0x20, 0x49, 0x8b,
+ 0x35, 0x7c, 0xb0, 0x6d, 0x80, 0xd9, 0x7f, 0xb9, 0x80, 0x7d, 0x7f, 0xb9, 0x80, 0x09, 0x7f, 0xe8,
+ 0x5e, 0xb0, 0x80, 0x75, 0x7f, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe2, 0x8c, 0x9c, 0x20, 0x68, 0xcd,
+ 0x7b, 0xe8, 0x64, 0x20, 0x98, 0x35, 0x84, 0xe8, 0x9c, 0xe1,
+}
+
+var MapsMyLocation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0xb0, 0x95,
+ 0x7b, 0x80, 0x70, 0x95, 0x83, 0x70, 0x90, 0x92, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x6d, 0x7c,
+ 0x90, 0x70, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe3, 0xe1, 0x91, 0x8c, 0xa0, 0xf5, 0x90, 0xa9, 0x75,
+ 0x59, 0x8a, 0x0d, 0x6f, 0x84, 0x21, 0x6e, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x21, 0x84, 0xa0, 0xa9,
+ 0x75, 0x0d, 0x6f, 0x0d, 0x6f, 0xa9, 0x75, 0x21, 0x6e, 0x7c, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x21,
+ 0x84, 0xb0, 0xed, 0x80, 0x59, 0x88, 0x8d, 0x87, 0xf5, 0x8e, 0xe1, 0x8f, 0xe1, 0x8f, 0xe8, 0xac,
+ 0xe7, 0x88, 0xe9, 0xe1, 0x7b, 0xb0, 0x59, 0x88, 0x15, 0x7f, 0xf5, 0x8e, 0x75, 0x78, 0xe1, 0x8f,
+ 0x21, 0x70, 0xe6, 0xac, 0xe9, 0x78, 0xe7, 0xe1, 0x7b, 0xe2, 0x80, 0x9c, 0xb0, 0x45, 0x78, 0x80,
+ 0x64, 0xbd, 0x79, 0x64, 0x64, 0x92, 0x45, 0x86, 0x64, 0x9c, 0x64, 0x9c, 0x45, 0x86, 0x9c, 0x9c,
+ 0xbd, 0x79, 0x9c, 0x64, 0x9c, 0xe1,
+}
+
+var MapsNavigation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0x02, 0x62,
+ 0x99, 0x90, 0x69, 0x72, 0xa4, 0x80, 0x98, 0x20, 0x99, 0x8d, 0x8c, 0x00, 0x9e, 0x99, 0x90, 0xe1,
+}
+
+var MapsNearMe = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0x00, 0x5c,
+ 0x11, 0x7d, 0xe9, 0xf9, 0x81, 0x20, 0xad, 0x8d, 0x4d, 0x85, 0x00, 0xf9, 0x80, 0xa4, 0xe7, 0xf9,
+ 0x81, 0x00, 0xa4, 0x5c, 0xe1,
+}
+
+var MapsPersonPin = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x58, 0xe6, 0x64,
+ 0xa0, 0xcd, 0x6f, 0x58, 0x5c, 0xcd, 0x6d, 0x5c, 0x60, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x90, 0x21, 0x8c, 0x8c, 0x8c, 0x74, 0xe7, 0x90, 0xb0, 0x35, 0x82,
+ 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0xe3, 0x64, 0x99, 0x86, 0xb3, 0xfd, 0x82, 0x80, 0x69, 0x85, 0x6d, 0x82, 0x69, 0x85, 0x69,
+ 0x85, 0x80, 0xfd, 0x82, 0x95, 0x7d, 0x69, 0x85, 0x99, 0x7a, 0x69, 0x85, 0x05, 0x7d, 0x80, 0x99,
+ 0x7a, 0x95, 0x7d, 0x99, 0x7a, 0x99, 0x7a, 0x80, 0x05, 0x7d, 0x6d, 0x82, 0x99, 0x7a, 0x69, 0x85,
+ 0x99, 0x7a, 0xe2, 0x98, 0x90, 0xe6, 0x68, 0xe9, 0x35, 0x7e, 0xb0, 0x80, 0x78, 0x90, 0xcd, 0x79,
+ 0x98, 0xcd, 0x79, 0x90, 0x98, 0x35, 0x82, 0x98, 0x35, 0x86, 0xe8, 0x90, 0xe1,
+}
+
+var MapsPersonPinCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb1, 0x45,
+ 0x78, 0x80, 0x64, 0x45, 0x86, 0x64, 0x9c, 0x80, 0x81, 0x8a, 0x9c, 0xb4, 0x9c, 0xb4, 0x90, 0x9c,
+ 0x81, 0x70, 0x9c, 0x4c, 0xb0, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0xe3, 0x80, 0x88,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x92, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe3, 0x80, 0xa8, 0xb1, 0xa9, 0x7c, 0x80,
+ 0xbd, 0x79, 0x4d, 0x7e, 0x70, 0xb5, 0x7b, 0x09, 0x80, 0x59, 0x7d, 0x59, 0x85, 0xe5, 0x7b, 0x90,
+ 0xe5, 0x7b, 0x90, 0xf5, 0x87, 0x75, 0x81, 0x90, 0x1d, 0x84, 0xa0, 0x45, 0x86, 0x4d, 0x82, 0x59,
+ 0x83, 0x88, 0x80, 0x88, 0xe1,
+}
+
+var MapsPinDrop = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x70, 0xb0, 0x80,
+ 0x61, 0x79, 0xa1, 0x7a, 0x68, 0x68, 0x68, 0x80, 0x68, 0x61, 0x71, 0x68, 0x70, 0xb0, 0x80, 0x92,
+ 0x98, 0xac, 0x98, 0xac, 0x90, 0x98, 0x66, 0x98, 0x54, 0xe3, 0x60, 0x80, 0xb0, 0x80, 0xcd, 0x7d,
+ 0xcd, 0x81, 0x78, 0x88, 0x78, 0x92, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88,
+ 0x78, 0x35, 0x7e, 0x78, 0x78, 0xe2, 0x64, 0xa0, 0xe9, 0x88, 0xe7, 0xb8, 0xe9, 0x78, 0xe6, 0x64,
+ 0xe1,
+}
+
+var MapsPlace = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xb1, 0x45,
+ 0x78, 0x80, 0x64, 0x45, 0x86, 0x64, 0x9c, 0x80, 0x81, 0x8a, 0x9c, 0xb4, 0x9c, 0xb4, 0x90, 0x9c,
+ 0x81, 0x70, 0x9c, 0x4c, 0xb0, 0x80, 0x45, 0x78, 0xbd, 0x79, 0x64, 0x64, 0x64, 0xe3, 0x80, 0xa6,
+ 0xb0, 0x3d, 0x7d, 0x80, 0x76, 0xc5, 0x7d, 0x76, 0x76, 0x92, 0x3d, 0x82, 0x76, 0x8a, 0x76, 0x8a,
+ 0x3d, 0x82, 0x8a, 0x8a, 0xc5, 0x7d, 0x8a, 0x76, 0x8a, 0xe1,
+}
+
+var MapsRateReview = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x68, 0x88, 0xe9, 0x0d, 0x7b, 0x00, 0xc5, 0x81,
+ 0x4d, 0x71, 0xb0, 0x65, 0x80, 0x9d, 0x7f, 0x05, 0x81, 0x9d, 0x7f, 0x69, 0x81, 0x80, 0x20, 0x8d,
+ 0x83, 0x8d, 0x83, 0xb0, 0x65, 0x80, 0x65, 0x80, 0x65, 0x80, 0x05, 0x81, 0x80, 0x69, 0x81, 0x00,
+ 0xf5, 0x78, 0x88, 0xe6, 0x68, 0xe3, 0xb0, 0x80, 0xe6, 0x7a, 0x20, 0x88, 0x78, 0xe7, 0x96, 0xe9,
+ 0x88, 0xe1,
+}
+
+var MapsRestaurant = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7c, 0x74, 0xe7, 0x78,
+ 0xe8, 0x58, 0xe7, 0x78, 0xe9, 0x9c, 0xe7, 0x78, 0xe8, 0x58, 0xe6, 0x5c, 0xe9, 0x9c, 0xb0, 0x80,
+ 0x41, 0x84, 0x51, 0x83, 0xb1, 0x87, 0x81, 0x87, 0xf5, 0x87, 0xe8, 0xa8, 0xe7, 0x8a, 0xe8, 0xf5,
+ 0x81, 0xb0, 0x31, 0x84, 0xbd, 0x7f, 0x81, 0x87, 0x4d, 0x7c, 0x81, 0x87, 0x0d, 0x78, 0xe8, 0x58,
+ 0xe7, 0x78, 0xe9, 0x9c, 0xe3, 0x94, 0x74, 0xe9, 0xa0, 0xe7, 0x8a, 0xe9, 0xa0, 0xe7, 0x8a, 0xe8,
+ 0x58, 0xb0, 0x7d, 0x7a, 0x80, 0x6c, 0x7d, 0x84, 0x6c, 0x90, 0xe1,
+}
+
+var MapsRestaurantMenu = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x35, 0x78, 0xb1, 0x82,
+ 0x20, 0xa9, 0x85, 0x59, 0x7a, 0x00, 0xd5, 0x6f, 0x5e, 0xb0, 0xe1, 0x7c, 0x21, 0x83, 0xe1, 0x7c,
+ 0x31, 0x88, 0x80, 0x51, 0x8b, 0x20, 0x61, 0x88, 0x61, 0x88, 0xe3, 0x91, 0x8d, 0x61, 0x7c, 0xb1,
+ 0x0d, 0x83, 0x71, 0x81, 0x5d, 0x87, 0x6d, 0x80, 0x8d, 0x8a, 0x3d, 0x7d, 0xd5, 0x83, 0x2d, 0x7c,
+ 0x91, 0x84, 0xb5, 0x76, 0xa1, 0x81, 0xc5, 0x73, 0xa0, 0x9e, 0x25, 0x6d, 0x85, 0x89, 0xe1, 0x6d,
+ 0xb5, 0x85, 0xb5, 0x71, 0xb1, 0xd1, 0x7c, 0x31, 0x83, 0xd1, 0x7b, 0x7d, 0x87, 0x3d, 0x7d, 0x8d,
+ 0x8a, 0x8d, 0x7b, 0x71, 0x84, 0x79, 0x6c, 0x85, 0x93, 0x79, 0x6c, 0x85, 0x93, 0x20, 0xd5, 0x82,
+ 0xd5, 0x82, 0x00, 0x80, 0xd5, 0x84, 0x21, 0xc5, 0x8d, 0xc5, 0x8d, 0xd5, 0x82, 0x2d, 0x7d, 0x00,
+ 0xd5, 0x82, 0x84, 0x20, 0xf1, 0x82, 0x11, 0x7d, 0xe1,
+}
+
+var MapsSatellite = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x64, 0xfd, 0x71, 0xe7, 0x8c,
+ 0xb0, 0x80, 0x51, 0x83, 0x51, 0x7d, 0x05, 0x86, 0x74, 0x05, 0x86, 0xe8, 0xfd, 0x71, 0xe2, 0x64,
+ 0x80, 0xe9, 0x78, 0xb0, 0x85, 0x85, 0x80, 0x94, 0x81, 0x7b, 0x94, 0xfd, 0x75, 0xe7, 0x88, 0xa0,
+ 0x80, 0xb5, 0x79, 0xbd, 0x79, 0x80, 0x64, 0x80, 0xe3, 0x80, 0x98, 0x21, 0x8e, 0x6e, 0x8a, 0x05,
+ 0x86, 0x00, 0x8a, 0x80, 0x20, 0x92, 0x98, 0xe6, 0x64, 0xe1,
+}
+
+var MapsStoreMallDirectory = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe6, 0x60,
+ 0xe9, 0x88, 0xe7, 0xc0, 0xe8, 0x60, 0xe3, 0x84, 0xa8, 0xe9, 0x78, 0x20, 0x7c, 0x6c, 0xe6, 0x60,
+ 0x00, 0x5c, 0x80, 0xe9, 0x88, 0xe7, 0x84, 0xe9, 0x98, 0xe7, 0xa8, 0xe8, 0x88, 0xe7, 0x90, 0xe9,
+ 0x98, 0xe7, 0x88, 0xe8, 0x88, 0xe7, 0x84, 0xe3, 0x5c, 0x90, 0xe6, 0x68, 0xe9, 0x70, 0xe7, 0x98,
+ 0xe9, 0x90, 0xe1,
+}
+
+var MapsStreetView = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x21, 0x81, 0xa9, 0x84,
+ 0xa0, 0x71, 0x80, 0x35, 0x85, 0x80, 0x0d, 0x86, 0x80, 0x8e, 0xe9, 0x96, 0xe7, 0x9c, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x0d, 0x82, 0xb1, 0x1d, 0x7e, 0x55, 0x7f, 0x19,
+ 0x7c, 0xf5, 0x7e, 0x74, 0xf5, 0x7e, 0xf5, 0x7b, 0x80, 0x21, 0x78, 0x69, 0x81, 0x21, 0x75, 0xa9,
+ 0x83, 0xe2, 0x84, 0x68, 0xd1, 0x94, 0x94, 0x00, 0x04, 0xa8, 0x80, 0x94, 0x94, 0x00, 0x04, 0x58,
+ 0x80, 0xe1, 0xc0, 0x7e, 0x68, 0xb0, 0x80, 0xd5, 0x7d, 0x8d, 0x80, 0xcd, 0x7b, 0x79, 0x81, 0x74,
+ 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x19,
+ 0x81, 0x75, 0x80, 0x19, 0x82, 0x31, 0x81, 0xd5, 0x82, 0x00, 0xd1, 0x82, 0x35, 0x7d, 0xa0, 0x75,
+ 0x80, 0xd9, 0x7a, 0x7e, 0x99, 0x77, 0x7e, 0x68, 0xe1,
+}
+
+var MapsSubway = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x76, 0x74, 0xe7,
+ 0xa8, 0xe9, 0x94, 0xe7, 0x58, 0xe2, 0x99, 0x8b, 0x99, 0x6d, 0xa0, 0x90, 0x31, 0x6c, 0xb9, 0x83,
+ 0x58, 0x80, 0x58, 0xb0, 0x49, 0x7c, 0x80, 0x70, 0x31, 0x80, 0x69, 0x74, 0x99, 0x81, 0xa0, 0x11,
+ 0x6f, 0xb1, 0x6f, 0x58, 0x19, 0x74, 0x58, 0xbd, 0x79, 0xe8, 0xa8, 0xe7, 0xd0, 0xe8, 0xbd, 0x79,
+ 0xb0, 0x80, 0x61, 0x7a, 0xf1, 0x7c, 0xf5, 0x75, 0x99, 0x77, 0xdd, 0x73, 0xe3, 0x69, 0x80, 0x29,
+ 0x9a, 0xb0, 0x80, 0xe9, 0x82, 0xa9, 0x7d, 0x41, 0x85, 0xc1, 0x7a, 0x41, 0x85, 0x00, 0x92, 0x41,
+ 0x8f, 0xe8, 0xa0, 0xe7, 0x7a, 0x20, 0x7a, 0x7a, 0xe7, 0x59, 0x7a, 0x20, 0x7a, 0x86, 0xe6, 0x6e,
+ 0xe9, 0x41, 0x7f, 0x00, 0x41, 0x79, 0x9a, 0xb0, 0x19, 0x7d, 0x80, 0xc1, 0x7a, 0xa9, 0x7d, 0xc1,
+ 0x7a, 0xc1, 0x7a, 0xe8, 0x74, 0xb1, 0x80, 0xc1, 0x7a, 0x8c, 0x74, 0x98, 0x74, 0xa1, 0x86, 0x80,
+ 0x98, 0xc1, 0x80, 0x98, 0x8c, 0xe9, 0xc1, 0x8d, 0xe2, 0x8a, 0x90, 0xd1, 0x84, 0x84, 0x00, 0x04,
+ 0x88, 0x80, 0x84, 0x84, 0x00, 0x04, 0x78, 0x80, 0xe2, 0x6e, 0x90, 0xd1, 0x84, 0x84, 0x00, 0x04,
+ 0x88, 0x80, 0x84, 0x84, 0x00, 0x04, 0x78, 0x80, 0xe1,
+}
+
+var MapsTerrain = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x88, 0x68, 0x21, 0x81,
+ 0x78, 0x94, 0xb5, 0x85, 0x99, 0x87, 0x00, 0x7e, 0x90, 0xb0, 0xa1, 0x7c, 0x81, 0x7b, 0x6e, 0x68,
+ 0x6e, 0x68, 0x00, 0x54, 0x98, 0xe7, 0xd8, 0x00, 0x88, 0x68, 0xe1,
+}
+
+var MapsTraffic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x78, 0xe7, 0x74,
+ 0xe9, 0xb9, 0x7d, 0xb0, 0x75, 0x83, 0x1d, 0x7f, 0x8c, 0x05, 0x7c, 0x8c, 0x49, 0x78, 0xe7, 0x74,
+ 0xe8, 0x60, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x70, 0xb0, 0xe9, 0x7e,
+ 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x84, 0xe6, 0x60, 0xb0, 0x80, 0xb9, 0x83, 0x91, 0x82,
+ 0xd5, 0x86, 0x8c, 0xb9, 0x87, 0xe8, 0x78, 0xe6, 0x60, 0xb0, 0x80, 0xb9, 0x83, 0x91, 0x82, 0xd5,
+ 0x86, 0x8c, 0xb9, 0x87, 0xe8, 0x8c, 0xe6, 0x60, 0xb0, 0x80, 0xb9, 0x83, 0x91, 0x82, 0xd5, 0x86,
+ 0x8c, 0xb9, 0x87, 0xe8, 0xa0, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0xa0,
+ 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0xb9, 0x7d, 0xb0, 0x75, 0x83, 0x1d,
+ 0x7f, 0x8c, 0x05, 0x7c, 0x8c, 0x49, 0x78, 0xe7, 0x74, 0xe9, 0xb9, 0x7d, 0xb0, 0x75, 0x83, 0x1d,
+ 0x7f, 0x8c, 0x78, 0x8c, 0x49, 0x78, 0xe2, 0x80, 0x9c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0x88, 0xe3, 0x80, 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd,
+ 0x81, 0x78, 0x88, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x80,
+ 0x6c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78,
+ 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe1,
+}
+
+var MapsTrain = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0x70,
+ 0x58, 0x60, 0x5a, 0x60, 0x68, 0xe9, 0xa6, 0xb0, 0x80, 0xdd, 0x83, 0x25, 0x83, 0x8e, 0x8e, 0x8e,
+ 0x20, 0x7a, 0x86, 0xe9, 0x82, 0xe7, 0x75, 0x84, 0x20, 0x88, 0x78, 0xe6, 0x88, 0x20, 0x88, 0x88,
+ 0xe7, 0x88, 0xe9, 0x7e, 0x20, 0x7a, 0x7a, 0xb0, 0xdd, 0x83, 0x80, 0x8e, 0xdd, 0x7c, 0x8e, 0x72,
+ 0xe8, 0x68, 0xb0, 0x80, 0x72, 0xd9, 0x78, 0x70, 0x60, 0x70, 0xe3, 0x6e, 0xbc, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x8e, 0x64, 0xe6, 0x68, 0xe9, 0x70, 0xe7, 0x94, 0xe9,
+ 0x90, 0xe3, 0x88, 0x80, 0xe9, 0x70, 0xe7, 0x94, 0xe9, 0x90, 0xe6, 0x84, 0xe3, 0x8e, 0x9c, 0xb0,
+ 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var MapsTram = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0xe1, 0x89, 0xe8,
+ 0x72, 0xb0, 0x80, 0x69, 0x7a, 0xc9, 0x7a, 0x35, 0x79, 0xfd, 0x73, 0x09, 0x79, 0x00, 0x81, 0x83,
+ 0x5e, 0xe6, 0x94, 0xe8, 0x58, 0xe6, 0x6c, 0xe9, 0x86, 0xe7, 0x81, 0x89, 0x20, 0x7d, 0x7e, 0x09,
+ 0x83, 0xa0, 0xb9, 0x77, 0x39, 0x72, 0x64, 0x79, 0x73, 0x64, 0x72, 0xe9, 0xe1, 0x90, 0xb0, 0x80,
+ 0xe5, 0x82, 0x61, 0x82, 0x51, 0x85, 0x31, 0x85, 0xf5, 0x85, 0x00, 0x68, 0xa6, 0xe9, 0x82, 0xe7,
+ 0x75, 0x84, 0x20, 0x88, 0x78, 0xe6, 0x88, 0x20, 0x88, 0x88, 0xe7, 0x88, 0xe9, 0x7e, 0x20, 0x7a,
+ 0x7a, 0xe7, 0xd9, 0x7f, 0xb0, 0x61, 0x83, 0x80, 0x29, 0x85, 0x41, 0x7d, 0x29, 0x85, 0xe1, 0x79,
+ 0xe2, 0x80, 0x9a, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a,
+ 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x94, 0x6e, 0xe6,
+ 0x6c, 0xe8, 0x74, 0xe7, 0xa8, 0xe9, 0x94, 0xe1,
+}
+
+var MapsTransferWithinAStation = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x88, 0x8e, 0xe9,
+ 0x81, 0x7c, 0x21, 0x05, 0x7b, 0x8a, 0xfd, 0x84, 0x8a, 0xe8, 0x94, 0xe6, 0xa8, 0xe9, 0x7a, 0xe3,
+ 0x05, 0x86, 0x81, 0x88, 0xe6, 0x88, 0xe9, 0x86, 0xe7, 0x05, 0x8b, 0xe8, 0xac, 0x00, 0xa8, 0xa2,
+ 0x20, 0x05, 0x7b, 0x76, 0xe2, 0x76, 0x66, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe3, 0x81, 0x78, 0xcd, 0x86, 0x00, 0x5c, 0xac, 0xe7, 0x35, 0x84, 0x21, 0x81, 0x83, 0x60, 0x4d,
+ 0x84, 0x88, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0xe9, 0x86, 0x21, 0xe9, 0x7b, 0xe9, 0x7b, 0x35, 0x81,
+ 0x74, 0xa0, 0xb5, 0x7d, 0x80, 0x99, 0x81, 0x84, 0x8c, 0x84, 0xe9, 0x78, 0xb0, 0x4d, 0x7c, 0x80,
+ 0x19, 0x79, 0x7c, 0x4d, 0x77, 0x19, 0x7b, 0x20, 0x19, 0x7e, 0xcd, 0x7c, 0xb1, 0x4d, 0x7f, 0xcd,
+ 0x7e, 0x7c, 0x19, 0x7e, 0x99, 0x7c, 0x19, 0x7e, 0x81, 0x7f, 0x80, 0x7e, 0x19, 0x80, 0x81, 0x7e,
+ 0x4d, 0x80, 0x00, 0x58, 0x99, 0x78, 0xe8, 0x84, 0xe7, 0x88, 0xe9, 0x4d, 0x79, 0x20, 0x81, 0x83,
+ 0x81, 0x7e, 0xe1,
+}
+
+var MapsZoomOutMap = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x5c, 0x23, 0x99,
+ 0x84, 0x99, 0x84, 0x3d, 0x7a, 0xc1, 0x85, 0xd5, 0x82, 0xd5, 0x82, 0xc1, 0x85, 0x3d, 0x7a, 0x00,
+ 0xa4, 0x74, 0xe8, 0x5c, 0xe2, 0x5c, 0x74, 0x23, 0x99, 0x84, 0x69, 0x7b, 0xc1, 0x85, 0xc5, 0x85,
+ 0xd5, 0x82, 0x2d, 0x7d, 0x3d, 0x7a, 0x41, 0x7a, 0x00, 0x74, 0x5c, 0xe6, 0x5c, 0xe3, 0x98, 0xb0,
+ 0x23, 0x69, 0x7b, 0x69, 0x7b, 0xc5, 0x85, 0x41, 0x7a, 0x2d, 0x7d, 0x2d, 0x7d, 0x41, 0x7a, 0xc5,
+ 0x85, 0x00, 0x5c, 0x8c, 0xe9, 0x98, 0xe3, 0xb0, 0x68, 0x23, 0x69, 0x7b, 0x99, 0x84, 0x41, 0x7a,
+ 0x3d, 0x7a, 0x2d, 0x7d, 0xd5, 0x82, 0xc5, 0x85, 0xc1, 0x85, 0x00, 0x8c, 0xa4, 0xe7, 0x98, 0xe1,
+}
+
+var NavigationApps = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x70, 0xe7, 0x90,
+ 0xe8, 0x60, 0xe6, 0x60, 0xe9, 0x90, 0xe3, 0x98, 0xb0, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9,
+ 0x90, 0xe2, 0x60, 0xa0, 0xe7, 0x90, 0xe9, 0x70, 0xe6, 0x60, 0xe9, 0x90, 0xe3, 0x80, 0x68, 0xe7,
+ 0x90, 0xe9, 0x70, 0xe6, 0x60, 0xe9, 0x90, 0xe3, 0x98, 0x80, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70,
+ 0xe9, 0x90, 0xe2, 0x90, 0x60, 0xe9, 0x90, 0xe7, 0x90, 0xe8, 0x60, 0xe7, 0x70, 0xe3, 0x68, 0x90,
+ 0xe7, 0x90, 0xe8, 0x60, 0xe7, 0x70, 0xe9, 0x90, 0xe3, 0x98, 0x98, 0xe7, 0x90, 0xe9, 0x70, 0xe7,
+ 0x70, 0xe9, 0x90, 0xe3, 0x80, 0x98, 0xe7, 0x90, 0xe9, 0x70, 0xe7, 0x70, 0xe9, 0x90, 0xe1,
+}
+
+var NavigationArrowBack = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x7c, 0xe6, 0xa9,
+ 0x77, 0x20, 0x2d, 0x8b, 0xd5, 0x74, 0x01, 0x80, 0x60, 0x60, 0x80, 0x21, 0xa0, 0xa0, 0xd5, 0x82,
+ 0x2d, 0x7d, 0x00, 0xa9, 0x77, 0x84, 0xe6, 0xa0, 0xe9, 0x78, 0xe1,
+}
+
+var NavigationArrowDownward = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x80, 0x20, 0x31,
+ 0x7d, 0x31, 0x7d, 0x00, 0x84, 0x59, 0x88, 0xe8, 0x60, 0xe7, 0x78, 0xe9, 0x59, 0x98, 0x01, 0xd9,
+ 0x72, 0x29, 0x7d, 0x60, 0x80, 0x21, 0xa0, 0xa0, 0xa0, 0x60, 0xe1,
+}
+
+var NavigationArrowDropDown = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x78, 0x21, 0x94,
+ 0x94, 0x94, 0x6c, 0xe1,
+}
+
+var NavigationArrowDropDownCircle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xb0, 0x20, 0x70, 0x70, 0xe7, 0xa0,
+ 0x20, 0x70, 0x90, 0xe1,
+}
+
+var NavigationArrowDropUp = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x88, 0x21, 0x94,
+ 0x6c, 0x94, 0x94, 0xe1,
+}
+
+var NavigationArrowForward = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x60, 0x20, 0x2d,
+ 0x7d, 0xd5, 0x82, 0x00, 0x59, 0x88, 0x7c, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0x59, 0x98, 0x01, 0x2d,
+ 0x7d, 0x2d, 0x8d, 0x80, 0xa0, 0x20, 0xa0, 0x60, 0xe1,
+}
+
+var NavigationArrowUpward = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x80, 0x20, 0xd5,
+ 0x82, 0xd5, 0x82, 0x00, 0x7c, 0xa9, 0x77, 0xe8, 0xa0, 0xe7, 0x88, 0xe8, 0xa9, 0x77, 0x20, 0x2d,
+ 0x8b, 0x2d, 0x8b, 0x02, 0xa0, 0x80, 0x80, 0x60, 0x60, 0x80, 0xe1,
+}
+
+var NavigationCancel = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x94, 0x2d, 0x9b, 0x0b, 0x2d, 0x87, 0x94,
+ 0x80, 0xd5, 0x82, 0xd5, 0x78, 0x94, 0x6c, 0x2d, 0x87, 0x2d, 0x7d, 0x80, 0x6c, 0xd5, 0x78, 0xd5,
+ 0x78, 0x6c, 0x80, 0x2d, 0x7d, 0x2d, 0x87, 0x6c, 0x94, 0xd5, 0x78, 0xd5, 0x82, 0x80, 0x94, 0x2d,
+ 0x87, 0xe1,
+}
+
+var NavigationCheck = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x59, 0x88, 0x00,
+ 0xa9, 0x71, 0x80, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x74, 0x9c, 0x21, 0xb0, 0x50, 0x2d, 0x7d,
+ 0x2d, 0x7d, 0xe1,
+}
+
+var NavigationChevronLeft = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd5, 0x86, 0xd5, 0x76,
+ 0x01, 0x88, 0x68, 0x70, 0x80, 0x21, 0x98, 0x98, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0xa9, 0x7d, 0x80,
+ 0xe1,
+}
+
+var NavigationChevronRight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x68, 0x20, 0x2d,
+ 0x7d, 0xd5, 0x82, 0x00, 0x59, 0x82, 0x80, 0x20, 0xd5, 0x76, 0x2d, 0x89, 0x00, 0x78, 0x98, 0x20,
+ 0x98, 0x68, 0xe1,
+}
+
+var NavigationClose = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0xd5, 0x74, 0x0a,
+ 0x2d, 0x8b, 0x64, 0x80, 0x2d, 0x7d, 0xd5, 0x74, 0x64, 0x64, 0xd5, 0x74, 0x2d, 0x7d, 0x80, 0x64,
+ 0x2d, 0x8b, 0xd5, 0x74, 0x9c, 0x80, 0xd5, 0x82, 0x2d, 0x8b, 0x9c, 0x9c, 0x2d, 0x8b, 0xd5, 0x82,
+ 0x80, 0xe1,
+}
+
+var NavigationExpandLess = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0x00, 0x68,
+ 0x88, 0x20, 0xd5, 0x82, 0xd5, 0x82, 0x00, 0x80, 0xa9, 0x7d, 0x20, 0x2d, 0x89, 0x2d, 0x89, 0x00,
+ 0x98, 0x88, 0xe1,
+}
+
+var NavigationExpandMore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x89, 0x2d, 0x79,
+ 0x00, 0x80, 0x59, 0x82, 0x20, 0xd5, 0x76, 0xd5, 0x76, 0x00, 0x68, 0x78, 0x21, 0x98, 0x98, 0x98,
+ 0x68, 0xe1,
+}
+
+var NavigationFirstPage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd1, 0x8c, 0x31, 0x89,
+ 0x00, 0xa5, 0x83, 0x80, 0x20, 0x31, 0x89, 0xd1, 0x76, 0x01, 0x94, 0x68, 0x7c, 0x80, 0x20, 0x98,
+ 0x98, 0xe2, 0x68, 0x68, 0xe7, 0x88, 0xe9, 0xb0, 0xe7, 0x78, 0xe1,
+}
+
+var NavigationFullscreen = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x88, 0xe7, 0x78,
+ 0xe9, 0x94, 0xe7, 0x94, 0xe9, 0x78, 0xe7, 0x74, 0xe9, 0x74, 0xe3, 0x78, 0x70, 0xe7, 0x88, 0xe9,
+ 0x74, 0xe7, 0x8c, 0xe9, 0x78, 0xe6, 0x64, 0xe9, 0x94, 0xe3, 0xb0, 0x9c, 0xe7, 0x74, 0xe9, 0x88,
+ 0xe7, 0x94, 0xe8, 0x88, 0xe7, 0x78, 0xe9, 0x8c, 0xe3, 0x74, 0x50, 0xe9, 0x88, 0xe7, 0x8c, 0xe9,
+ 0x8c, 0xe7, 0x88, 0xe8, 0x64, 0xe6, 0x88, 0xe1,
+}
+
+var NavigationFullscreenExit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x90, 0xe7, 0x8c,
+ 0xe9, 0x8c, 0xe7, 0x88, 0xe8, 0x88, 0xe6, 0x64, 0xe9, 0x88, 0xe3, 0x8c, 0x60, 0xe7, 0x74, 0xe9,
+ 0x88, 0xe7, 0x94, 0xe8, 0x64, 0xe7, 0x78, 0xe9, 0x8c, 0xe3, 0x98, 0xac, 0xe7, 0x88, 0xe9, 0x74,
+ 0xe7, 0x8c, 0xe9, 0x78, 0xe6, 0x88, 0xe9, 0x94, 0xe3, 0x88, 0x54, 0xe9, 0x74, 0xe7, 0x78, 0xe9,
+ 0x94, 0xe7, 0x94, 0xe9, 0x78, 0xe7, 0x74, 0xe1,
+}
+
+var NavigationLastPage = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x31, 0x73, 0xd1, 0x76,
+ 0x00, 0x5d, 0x7c, 0x80, 0x20, 0xd1, 0x76, 0x31, 0x89, 0x00, 0x6c, 0x98, 0x21, 0x98, 0x68, 0x68,
+ 0x68, 0xe2, 0x90, 0x68, 0xe7, 0x88, 0xe9, 0xb0, 0xe7, 0x78, 0xe1,
+}
+
+var NavigationMenu = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x98, 0xe7, 0xc8,
+ 0xe9, 0x78, 0xe6, 0x5c, 0xe9, 0x88, 0xe3, 0x80, 0x6c, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe9,
+ 0x88, 0xe3, 0x80, 0x64, 0xe9, 0x88, 0xe7, 0xc8, 0xe9, 0x78, 0xe6, 0x5c, 0xe1,
+}
+
+var NavigationMoreHoriz = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x68, 0x78, 0xb0, 0xcd,
+ 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0xb0, 0x80, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x68, 0x80, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var NavigationMoreVert = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x70, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x80, 0x88, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd,
+ 0x81, 0x78, 0x88, 0x92, 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e,
+ 0x78, 0x78, 0x78, 0xe3, 0x80, 0x98, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x92,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var NavigationRefresh = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x4d, 0x8b, 0xb5, 0x74,
+ 0xa1, 0x69, 0x88, 0xcd, 0x71, 0x6d, 0x84, 0x60, 0x80, 0x60, 0x29, 0x77, 0x60, 0x05, 0x70, 0x29,
+ 0x77, 0x05, 0x70, 0x80, 0x80, 0x29, 0x77, 0xa0, 0x80, 0xa0, 0xb0, 0x75, 0x87, 0x80, 0xb1, 0x8d,
+ 0xe9, 0x7a, 0x75, 0x8f, 0x68, 0xe6, 0x4d, 0x8b, 0xb1, 0x59, 0x7e, 0xa9, 0x84, 0xf1, 0x79, 0x90,
+ 0xb5, 0x74, 0x90, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x90, 0x61, 0x85, 0x68, 0x98,
+ 0x68, 0xb0, 0x51, 0x83, 0x80, 0x49, 0x86, 0x61, 0x81, 0x75, 0x88, 0x8d, 0x83, 0x00, 0x84, 0x7c,
+ 0xe7, 0x9c, 0xe8, 0x60, 0x20, 0x4d, 0x7b, 0xb5, 0x84, 0xe1,
+}
+
+var NavigationSubdirectoryArrowLeft = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x74, 0x20, 0xd5,
+ 0x82, 0xd5, 0x82, 0x00, 0xa9, 0x77, 0x88, 0xe6, 0x98, 0xe8, 0x5c, 0xe7, 0x88, 0xe9, 0xb4, 0xe6,
+ 0xa9, 0x77, 0x20, 0x2d, 0x87, 0x2d, 0x87, 0x01, 0x78, 0xa4, 0x60, 0x8c, 0x20, 0x98, 0x68, 0xe1,
+}
+
+var NavigationSubdirectoryArrowRight = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x12, 0x8a, 0x22, 0x68,
+ 0x98, 0x2d, 0x7d, 0x2d, 0x7d, 0x2d, 0x87, 0xd5, 0x78, 0xe6, 0x01, 0x2b, 0xe8, 0x5e, 0xe7, 0x88,
+ 0xe9, 0xa8, 0xe7, 0x59, 0x92, 0x20, 0xd5, 0x78, 0xd5, 0x78, 0x00, 0x01, 0x3d, 0x72, 0x20, 0x98,
+ 0x98, 0xe3, 0x01, 0xc7, 0x82, 0x00, 0x88, 0xa4, 0x20, 0x2d, 0x7d, 0x2d, 0x7d, 0x00, 0x59, 0x88,
+ 0x90, 0xe6, 0x60, 0xe8, 0x5c, 0xe7, 0x88, 0xe9, 0xac, 0xe7, 0x59, 0x94, 0x20, 0xd5, 0x78, 0xd5,
+ 0x78, 0x00, 0x88, 0x74, 0x20, 0x98, 0x98, 0xe1,
+}
+
+var NavigationUnfoldLess = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd5, 0x76, 0x2d, 0x8d,
+ 0x02, 0xa9, 0x79, 0xa0, 0x80, 0xa9, 0x89, 0x59, 0x86, 0xa0, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0x00,
+ 0x80, 0x88, 0x20, 0xd5, 0x76, 0x2d, 0x89, 0xe3, 0x59, 0x92, 0xa9, 0x65, 0x02, 0x59, 0x86, 0x60,
+ 0x80, 0x59, 0x76, 0xa9, 0x79, 0x60, 0x20, 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x80, 0x78, 0x20, 0x2d,
+ 0x89, 0xd5, 0x76, 0xe1,
+}
+
+var NavigationUnfoldMore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xa9, 0x73, 0x00,
+ 0x59, 0x86, 0x74, 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x80, 0x5c, 0x20, 0xd5, 0x76, 0x2d, 0x89,
+ 0x01, 0xa9, 0x79, 0x74, 0x80, 0xa9, 0x73, 0xe3, 0x80, 0xb1, 0x98, 0x00, 0xa9, 0x79, 0x8c, 0x20,
+ 0x2d, 0x7d, 0xd5, 0x82, 0x00, 0x80, 0xa4, 0x20, 0x2d, 0x89, 0xd5, 0x76, 0x01, 0x59, 0x86, 0x8c,
+ 0x80, 0x59, 0x8c, 0xe1,
+}
+
+var NotificationADB = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x90, 0xb0, 0x80,
+ 0xbd, 0x87, 0x45, 0x86, 0x9c, 0x9c, 0x9c, 0x90, 0x9c, 0xbd, 0x79, 0x9c, 0x64, 0xe9, 0x70, 0xe6,
+ 0x64, 0xe9, 0x90, 0xe2, 0x41, 0x88, 0xbd, 0x70, 0x22, 0x35, 0x84, 0xcd, 0x7b, 0x59, 0x7e, 0x59,
+ 0x7e, 0x65, 0x7b, 0x9d, 0x84, 0xa0, 0x51, 0x84, 0x91, 0x6e, 0x3d, 0x82, 0x5c, 0x80, 0x5c, 0xb0,
+ 0xc5, 0x7d, 0x80, 0xb1, 0x7b, 0x91, 0x80, 0xd1, 0x79, 0x81, 0x81, 0x00, 0x35, 0x75, 0xe5, 0x6a,
+ 0x21, 0x59, 0x7e, 0xa9, 0x81, 0x35, 0x84, 0x35, 0x84, 0xa0, 0x49, 0x74, 0x49, 0x73, 0x64, 0x5d,
+ 0x77, 0x64, 0x78, 0xe9, 0x84, 0xe7, 0xb8, 0xe9, 0x7c, 0xb0, 0x80, 0x5d, 0x7b, 0xb9, 0x7d, 0x49,
+ 0x77, 0x41, 0x7a, 0xbd, 0x74, 0xe2, 0x74, 0x74, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0x19, 0x7f, 0x7c,
+ 0x7c, 0x90, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84, 0x84,
+ 0x90, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe3, 0x98, 0x80, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0x19, 0x7f,
+ 0x7c, 0x7c, 0x90, 0xe5, 0x80, 0x7c, 0x84, 0x7c, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0xe9, 0x80, 0x84,
+ 0x84, 0x90, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe1,
+}
+
+var NotificationAirlineSeatFlat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x7c, 0xe9, 0x88,
+ 0xe6, 0x74, 0xe8, 0x6c, 0xe7, 0xa4, 0xb0, 0x6d, 0x84, 0x80, 0x90, 0x95, 0x83, 0x90, 0x90, 0xe2,
+ 0x58, 0x88, 0xe9, 0x88, 0xe7, 0x98, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x98, 0xe9, 0x78,
+ 0xe6, 0x58, 0xe3, 0x4d, 0x8a, 0x35, 0x7c, 0xb3, 0x51, 0x82, 0xa1, 0x7d, 0x49, 0x82, 0xd5, 0x79,
+ 0xe9, 0x7f, 0x85, 0x77, 0xa1, 0x7d, 0xb1, 0x7d, 0xd5, 0x79, 0xb9, 0x7d, 0x85, 0x77, 0x19, 0x80,
+ 0xb1, 0x7d, 0x61, 0x82, 0xb9, 0x7d, 0x2d, 0x86, 0x19, 0x80, 0x7d, 0x88, 0x5d, 0x82, 0x51, 0x82,
+ 0x29, 0x86, 0x49, 0x82, 0x7d, 0x88, 0xe9, 0x7f, 0xe1,
+}
+
+var NotificationAirlineSeatFlatAngled = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x85, 0x94, 0x91, 0x84,
+ 0x23, 0xa1, 0x7e, 0xc5, 0x83, 0x45, 0x67, 0x15, 0x77, 0x29, 0x84, 0xb1, 0x74, 0x21, 0x91, 0x31,
+ 0x86, 0xb0, 0x35, 0x84, 0x85, 0x81, 0x61, 0x86, 0x21, 0x86, 0xd9, 0x84, 0x49, 0x8a, 0xe2, 0x56,
+ 0x45, 0x80, 0x20, 0x9a, 0xb1, 0x84, 0xe8, 0x9c, 0xe7, 0xa0, 0xe9, 0xbd, 0x7c, 0x00, 0x0d, 0x91,
+ 0x9c, 0x20, 0x61, 0x81, 0x3d, 0x7c, 0x01, 0x61, 0x6c, 0x81, 0x7c, 0x56, 0x45, 0x80, 0xe3, 0x99,
+ 0x8b, 0x25, 0x7c, 0xb3, 0xfd, 0x82, 0x91, 0x7e, 0x41, 0x84, 0xfd, 0x7a, 0xd1, 0x82, 0x70, 0x91,
+ 0x7e, 0x05, 0x7d, 0xfd, 0x7a, 0xc1, 0x7b, 0x70, 0x31, 0x7d, 0x05, 0x7d, 0x71, 0x81, 0xc1, 0x7b,
+ 0x05, 0x85, 0x31, 0x7d, 0x90, 0x71, 0x81, 0xfd, 0x82, 0x05, 0x85, 0x41, 0x84, 0x90, 0xd1, 0x82,
+ 0xe1,
+}
+
+var NotificationAirlineSeatIndividualSuite = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6c, 0x84, 0xb0, 0x51,
+ 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x92, 0x51, 0x7d, 0x74, 0x74, 0x74, 0x74, 0xb1, 0x82,
+ 0x74, 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3, 0xb0, 0x68, 0xe6, 0x7c, 0xe9, 0x9c, 0xe6, 0x5c,
+ 0xe8, 0x6c, 0xe6, 0x54, 0xe9, 0xa8, 0xe7, 0xd8, 0xe8, 0x7c, 0xb0, 0x80, 0x95, 0x7b, 0x6d, 0x7c,
+ 0x70, 0x70, 0x70, 0xe1,
+}
+
+var NotificationAirlineSeatLegroomExtra = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x60, 0x80, 0xe8, 0x5c,
+ 0xe6, 0x58, 0xe9, 0xa4, 0xb0, 0x80, 0x85, 0x85, 0x7d, 0x84, 0x94, 0x94, 0x94, 0xe7, 0x98, 0xe9,
+ 0x78, 0xe6, 0x6c, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xe3, 0xa9, 0xa5, 0x79,
+ 0x8a, 0xb0, 0x41, 0x7f, 0x91, 0x7e, 0x6d, 0x7d, 0x11, 0x7e, 0xf1, 0x7b, 0xbd, 0x7e, 0x21, 0xd1,
+ 0x7d, 0x82, 0x2d, 0x79, 0x0d, 0x72, 0xb0, 0x55, 0x7f, 0xa1, 0x7e, 0xf1, 0x7d, 0xc5, 0x7d, 0x6d,
+ 0x7c, 0xc5, 0x7d, 0x00, 0x7c, 0x74, 0xe8, 0x5c, 0xe6, 0x64, 0xe9, 0xa0, 0xb0, 0x80, 0x51, 0x83,
+ 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe7, 0x9c, 0x21, 0xd1, 0x86, 0x9c, 0x71, 0x87, 0x99, 0x7c, 0xb0,
+ 0x8d, 0x81, 0x4d, 0x7f, 0x35, 0x82, 0x6d, 0x7d, 0x69, 0x81, 0xe1, 0x7b, 0xe1,
+}
+
+var NotificationAirlineSeatLegroomNormal = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x80, 0xe8, 0x5c,
+ 0xe6, 0x5c, 0xe9, 0xa4, 0xb0, 0x80, 0x85, 0x85, 0x7d, 0x84, 0x94, 0x94, 0x94, 0xe7, 0x98, 0xe9,
+ 0x78, 0xe6, 0x70, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xe3, 0xbe, 0x98, 0xe7,
+ 0x7a, 0xe8, 0x7c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x80, 0xe8, 0x5c,
+ 0xe6, 0x68, 0xe9, 0xa0, 0xb0, 0x80, 0x4d, 0x83, 0xb5, 0x82, 0x8c, 0x8c, 0x8c, 0xe7, 0x9c, 0xe9,
+ 0x9c, 0xe7, 0x92, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x90, 0xa9, 0x7e, 0x7a,
+ 0x7a, 0x7a, 0xe1,
+}
+
+var NotificationAirlineSeatLegroomReduced = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xf1, 0x8f, 0x69, 0x8e,
+ 0xa0, 0x51, 0x90, 0x51, 0x90, 0xd9, 0x8e, 0xa4, 0x9a, 0xa4, 0xe7, 0x6e, 0xe9, 0x74, 0x20, 0x84,
+ 0x70, 0xe6, 0x74, 0xb0, 0xb5, 0x7c, 0x80, 0x74, 0x4d, 0x7d, 0x74, 0x74, 0xe8, 0x5c, 0xe7, 0x98,
+ 0xe9, 0x98, 0xe7, 0x94, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x20, 0x78, 0x9c,
+ 0xe7, 0xe5, 0x82, 0xb0, 0x75, 0x81, 0x80, 0xc9, 0x82, 0xfd, 0x80, 0x0d, 0x83, 0x69, 0x82, 0xe2,
+ 0x64, 0x80, 0xe8, 0x5c, 0xe6, 0x5c, 0xe9, 0xa4, 0xb0, 0x80, 0x85, 0x85, 0x7d, 0x84, 0x94, 0x94,
+ 0x94, 0xe7, 0x90, 0xe9, 0x78, 0xe7, 0x70, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74,
+ 0xe1,
+}
+
+var NotificationAirlineSeatReclineExtra = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xb5, 0x72, 0x49, 0x73,
+ 0xb3, 0x31, 0x7e, 0xbd, 0x7e, 0xc1, 0x7d, 0x3d, 0x7c, 0x05, 0x7f, 0x71, 0x7a, 0x45, 0x81, 0x31,
+ 0x7e, 0xc5, 0x83, 0xc1, 0x7d, 0x91, 0x85, 0x05, 0x7f, 0xd1, 0x81, 0x45, 0x81, 0x41, 0x82, 0xc5,
+ 0x83, 0xfd, 0x80, 0x91, 0x85, 0xbd, 0x7e, 0xcd, 0x81, 0x41, 0x7c, 0x3d, 0x82, 0x71, 0x7a, 0xfd,
+ 0x80, 0xe2, 0x90, 0x9c, 0xe6, 0xdd, 0x79, 0xb0, 0x0d, 0x7d, 0x80, 0x85, 0x7a, 0xd9, 0x7d, 0x11,
+ 0x7a, 0xed, 0x7a, 0x00, 0x60, 0x6c, 0xe6, 0x58, 0x20, 0xfd, 0x83, 0x85, 0x93, 0xb0, 0xbd, 0x80,
+ 0xe1, 0x84, 0xf1, 0x84, 0x7d, 0x88, 0xe1, 0x89, 0x7d, 0x88, 0xe6, 0x90, 0xe9, 0x78, 0xe3, 0x75,
+ 0x80, 0x70, 0xe6, 0xb5, 0x7e, 0x20, 0xf5, 0x7d, 0xcd, 0x77, 0xb0, 0x29, 0x83, 0xc9, 0x81, 0x91,
+ 0x86, 0x19, 0x83, 0x4d, 0x8a, 0x71, 0x82, 0xe9, 0xc1, 0x7b, 0xb0, 0xbd, 0x7c, 0x9d, 0x80, 0x21,
+ 0x79, 0x75, 0x7f, 0xa1, 0x76, 0x85, 0x7d, 0x20, 0xb5, 0x7c, 0x71, 0x7d, 0xb1, 0x8d, 0x7f, 0xa9,
+ 0x7f, 0x05, 0x7f, 0x69, 0x7f, 0x79, 0x7e, 0x3d, 0x7f, 0x61, 0x7f, 0xd1, 0x7f, 0xb1, 0x7e, 0xc5,
+ 0x7f, 0x05, 0x7e, 0xe1, 0x7f, 0x20, 0xf5, 0x7f, 0x05, 0x80, 0xb0, 0x8d, 0x7d, 0x71, 0x80, 0xe9,
+ 0x7b, 0xc5, 0x82, 0x59, 0x7c, 0x35, 0x85, 0x20, 0xb5, 0x82, 0xd9, 0x8b, 0xb0, 0x8d, 0x80, 0xd5,
+ 0x82, 0x05, 0x83, 0xe1, 0x84, 0xe5, 0x85, 0xe1, 0x84, 0xe7, 0xb5, 0x8d, 0x00, 0xa2, 0xa4, 0x21,
+ 0x86, 0x7a, 0x75, 0x74, 0x6e, 0xe1,
+}
+
+var NotificationAirlineSeatReclineNormal = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x2d, 0x77, 0xd5, 0x72,
+ 0xb3, 0x71, 0x7e, 0x71, 0x7e, 0x71, 0x7e, 0xe9, 0x7b, 0x80, 0x59, 0x7a, 0x91, 0x81, 0x71, 0x7e,
+ 0x19, 0x84, 0x71, 0x7e, 0xa9, 0x85, 0x80, 0x91, 0x81, 0x91, 0x81, 0x91, 0x81, 0x19, 0x84, 0x80,
+ 0xa9, 0x85, 0x71, 0x7e, 0x91, 0x81, 0xe9, 0x7b, 0x91, 0x81, 0x59, 0x7a, 0x80, 0xe2, 0x68, 0x90,
+ 0xe8, 0x6c, 0xe6, 0x60, 0xe9, 0xa4, 0xb0, 0x80, 0x85, 0x85, 0x7d, 0x84, 0x94, 0x94, 0x94, 0xe7,
+ 0x98, 0xe9, 0x78, 0xe6, 0x74, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0x51, 0x7d, 0x74, 0x74, 0xe3, 0xb8,
+ 0x21, 0x88, 0x00, 0xe1, 0x85, 0x8c, 0xe6, 0x7e, 0xe9, 0xa5, 0x78, 0xb0, 0xcd, 0x82, 0x4d, 0x82,
+ 0x35, 0x87, 0x51, 0x84, 0x96, 0x51, 0x84, 0xe9, 0xb1, 0x7b, 0xb0, 0xb1, 0x7c, 0x0d, 0x80, 0xc9,
+ 0x78, 0x45, 0x7e, 0xa9, 0x76, 0xed, 0x7b, 0x20, 0x35, 0x7d, 0xe9, 0x7c, 0xb1, 0x9d, 0x7f, 0x91,
+ 0x7f, 0x25, 0x7f, 0x3d, 0x7f, 0xa1, 0x7e, 0xfd, 0x7e, 0x69, 0x7f, 0xb5, 0x7f, 0xc1, 0x7e, 0x8d,
+ 0x7f, 0x11, 0x7e, 0x8d, 0x7f, 0xe7, 0xf5, 0x7f, 0xa0, 0x05, 0x78, 0x6c, 0x6c, 0x05, 0x78, 0x6c,
+ 0x81, 0x7a, 0xe8, 0x8c, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe7, 0x21, 0x8a,
+ 0x20, 0x8e, 0x8e, 0x00, 0xa0, 0x21, 0x90, 0xe1,
+}
+
+var NotificationBluetoothAudio = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x7d, 0x84, 0x05, 0x80,
+ 0x20, 0xa5, 0x84, 0xa5, 0x84, 0xb1, 0x91, 0x80, 0x8d, 0x7e, 0xe1, 0x80, 0xfd, 0x7c, 0xe1, 0x80,
+ 0x59, 0x7b, 0x80, 0x61, 0x7e, 0xb1, 0x7f, 0xd1, 0x7c, 0x25, 0x7f, 0x61, 0x7b, 0x20, 0x59, 0x7b,
+ 0xa9, 0x84, 0xe3, 0x95, 0x8a, 0x69, 0x75, 0x20, 0x79, 0x7d, 0x89, 0x82, 0xb0, 0x41, 0x81, 0x69,
+ 0x82, 0xf9, 0x81, 0x25, 0x85, 0xf9, 0x81, 0x0d, 0x88, 0x90, 0x49, 0x7f, 0xa1, 0x85, 0x09, 0x7e,
+ 0x0d, 0x88, 0x20, 0x69, 0x82, 0x69, 0x82, 0xb1, 0xf1, 0x81, 0xe9, 0x7c, 0x11, 0x83, 0x45, 0x79,
+ 0x11, 0x83, 0x61, 0x75, 0x80, 0x31, 0x7c, 0xe9, 0x7e, 0xa1, 0x78, 0x11, 0x7d, 0x99, 0x75, 0xe3,
+ 0x59, 0x78, 0xfd, 0x81, 0x00, 0x78, 0x58, 0xe7, 0x7c, 0xe9, 0x2d, 0x8f, 0x05, 0xd5, 0x70, 0x64,
+ 0x5c, 0xd5, 0x74, 0x2d, 0x79, 0x80, 0x5c, 0x2d, 0x8b, 0xd5, 0x70, 0x9c, 0x74, 0xd5, 0x84, 0xe8,
+ 0xa8, 0xe7, 0x84, 0x20, 0x69, 0x8b, 0x99, 0x74, 0x00, 0xd5, 0x7e, 0x80, 0x20, 0x95, 0x88, 0x69,
+ 0x77, 0xe2, 0x7c, 0xa9, 0x73, 0x20, 0xc5, 0x83, 0xc5, 0x83, 0x00, 0x7c, 0x2d, 0x7b, 0xe9, 0x7d,
+ 0x78, 0xe3, 0xc5, 0x83, 0xf1, 0x94, 0x00, 0x7c, 0x59, 0x8c, 0xe9, 0x7d, 0x78, 0x20, 0xc5, 0x83,
+ 0xc5, 0x83, 0xe1,
+}
+
+var NotificationConfirmationNumber = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x78, 0xe9, 0x70,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x05,
+ 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0xe9, 0x90, 0xb1, 0x35, 0x82, 0x05, 0x80, 0xfd, 0x83, 0xcd,
+ 0x81, 0xfd, 0x83, 0x88, 0x80, 0x35, 0x82, 0x35, 0x7e, 0x88, 0x05, 0x7c, 0x88, 0x00, 0x58, 0x98,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x90,
+ 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe2, 0x84, 0x96, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x80, 0x6e, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x6e, 0xe7, 0x78,
+ 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var NotificationDiscFull = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x90, 0xe7, 0x88,
+ 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x88, 0xe3, 0x80, 0x5c, 0xe9, 0x94, 0xe7, 0x88, 0xe8, 0x6c, 0xe7,
+ 0x78, 0xe2, 0x78, 0x60, 0xa0, 0x29, 0x73, 0x60, 0x58, 0x29, 0x77, 0x58, 0x80, 0x91, 0x29, 0x87,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0x80, 0xd9, 0x84, 0x60, 0x78, 0x60, 0xe3, 0x80,
+ 0xa8, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0x35, 0x7e, 0x78, 0x78, 0x92, 0xcd, 0x81, 0x78, 0x88, 0x78,
+ 0x88, 0xcd, 0x81, 0x88, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe1,
+}
+
+var NotificationDoNotDisturb = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb1, 0x29, 0x77, 0x80, 0x60,
+ 0xd9, 0x78, 0x60, 0x60, 0x80, 0x4d, 0x7c, 0x45, 0x81, 0xe9, 0x78, 0x61, 0x83, 0x35, 0x76, 0x00,
+ 0xcd, 0x89, 0xa1, 0x8c, 0xa0, 0x19, 0x87, 0xbd, 0x8e, 0xb5, 0x83, 0xa0, 0x80, 0xa0, 0xe3, 0xa1,
+ 0x8c, 0xcd, 0x79, 0x00, 0x35, 0x76, 0x61, 0x73, 0xa0, 0xe9, 0x78, 0x45, 0x71, 0x4d, 0x7c, 0x60,
+ 0x80, 0x60, 0xb1, 0xd9, 0x88, 0x80, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0x80, 0xb5, 0x83, 0xbd, 0x7e,
+ 0x19, 0x87, 0xa1, 0x7c, 0xcd, 0x89, 0xe1,
+}
+
+var NotificationDoNotDisturbAlt = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0x6a,
+ 0x58, 0x58, 0x6a, 0x58, 0x80, 0x91, 0x92, 0xa8, 0xa8, 0xa8, 0xa8, 0x6e, 0xa8, 0x58, 0x80, 0x96,
+ 0x58, 0x80, 0x58, 0xe2, 0x60, 0x80, 0xb1, 0x80, 0x35, 0x77, 0x35, 0x87, 0x60, 0xa0, 0x60, 0xb5,
+ 0x83, 0x80, 0x19, 0x87, 0x4d, 0x81, 0xcd, 0x89, 0x69, 0x83, 0x00, 0x69, 0x73, 0xcd, 0x89, 0xa0,
+ 0x4d, 0x71, 0x19, 0x87, 0x60, 0xb5, 0x83, 0x60, 0x80, 0xe3, 0xa0, 0xa0, 0xb0, 0x4d, 0x7c, 0x80,
+ 0xe9, 0x78, 0xb5, 0x7e, 0x35, 0x76, 0x99, 0x7c, 0x20, 0x69, 0x96, 0x99, 0x69, 0xb1, 0x19, 0x82,
+ 0xb5, 0x82, 0x69, 0x83, 0x19, 0x86, 0x69, 0x83, 0xcd, 0x89, 0x80, 0xcd, 0x88, 0xcd, 0x78, 0xa0,
+ 0x60, 0xa0, 0xe1,
+}
+
+var NotificationDoNotDisturbOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x7c, 0xe9, 0x88,
+ 0xe7, 0x15, 0x7d, 0x20, 0x5d, 0x89, 0x5d, 0x89, 0xa0, 0xb1, 0x92, 0x25, 0x88, 0xa8, 0x39, 0x84,
+ 0xa8, 0x80, 0xb1, 0x80, 0xf5, 0x74, 0x0d, 0x77, 0x58, 0x58, 0x58, 0xc9, 0x7b, 0x80, 0xdd, 0x77,
+ 0x51, 0x81, 0xa5, 0x74, 0x91, 0x83, 0x00, 0x15, 0x83, 0x7c, 0xe6, 0x94, 0xe2, 0x8d, 0x6c, 0x8d,
+ 0x6c, 0x00, 0x54, 0x15, 0x6f, 0x20, 0x91, 0x85, 0x91, 0x85, 0xa0, 0x51, 0x6d, 0xdd, 0x77, 0x58,
+ 0xc9, 0x7b, 0x58, 0x80, 0xb1, 0x80, 0x0d, 0x8b, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0x39, 0x84, 0x80,
+ 0x25, 0x88, 0xb1, 0x7e, 0x5d, 0x8b, 0x71, 0x7c, 0x00, 0xed, 0x90, 0xac, 0x20, 0x8d, 0x82, 0x75,
+ 0x7d, 0x01, 0x7c, 0x7c, 0x8d, 0x6c, 0x8d, 0x6c, 0xe2, 0x6c, 0x84, 0xe9, 0x78, 0xe7, 0xed, 0x82,
+ 0x20, 0x88, 0x88, 0xe6, 0x6c, 0xe1,
+}
+
+var NotificationDoNotDisturbOn = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x94, 0xac, 0xe6, 0x6c, 0xe9, 0x78, 0xe7,
+ 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var NotificationDriveETA = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd9, 0x8d, 0x05, 0x72,
+ 0xa0, 0x71, 0x8d, 0xd9, 0x70, 0x51, 0x8c, 0x60, 0x96, 0x60, 0xe6, 0x6a, 0xb0, 0xb1, 0x7e, 0x80,
+ 0x91, 0x7d, 0xd9, 0x80, 0x29, 0x7d, 0x05, 0x82, 0x00, 0x5c, 0x7c, 0xe9, 0xa0, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0xe9, 0x7c, 0xe7, 0xb0, 0xe9, 0x84, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84,
+ 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x7c, 0x20, 0xd9, 0x7b,
+ 0x05, 0x74, 0xe2, 0x6a, 0x8c, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59,
+ 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xac,
+ 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a,
+ 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe2, 0x64, 0x78, 0x20, 0x86, 0x6e,
+ 0xe7, 0xac, 0x20, 0x86, 0x92, 0xe6, 0x64, 0xe1,
+}
+
+var NotificationEnhancedEncryption = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x70, 0xe7, 0x7c,
+ 0xe9, 0x78, 0xb0, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0x80, 0x6c, 0x7d, 0x6e, 0x6c,
+ 0x68, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa8,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0xcd, 0x6d, 0x78, 0xb0, 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd, 0x79, 0x35, 0x86, 0xcd, 0x79, 0x90,
+ 0x35, 0x86, 0xc9, 0x82, 0x35, 0x86, 0x35, 0x86, 0xe9, 0x88, 0xe6, 0xcd, 0x79, 0xe9, 0x78, 0xe2,
+ 0x90, 0x90, 0xe7, 0x74, 0xe9, 0x8c, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x74, 0xe9, 0x78, 0xe7, 0x8c,
+ 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x8c, 0xe9, 0x88, 0xe1,
+}
+
+var NotificationEventAvailable = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x11, 0x89, 0x21, 0x7e,
+ 0x00, 0xf1, 0x86, 0x78, 0x22, 0x3d, 0x76, 0xc5, 0x89, 0xc5, 0x7b, 0xc5, 0x7b, 0xe1, 0x7d, 0x21,
+ 0x82, 0x00, 0x31, 0x7d, 0x94, 0x20, 0xe1, 0x8b, 0x21, 0x74, 0xe2, 0x9c, 0x5c, 0xe7, 0x7c, 0xe8,
+ 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe6, 0x70, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7c, 0xb0,
+ 0xcd, 0x7d, 0x80, 0x05, 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6,
+ 0x64, 0xe8, 0x70, 0xe7, 0xb8, 0xe9, 0xac, 0xe1,
+}
+
+var NotificationEventBusy = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa1, 0x7a, 0x94, 0x20,
+ 0xe1, 0x84, 0x21, 0x7b, 0x00, 0x61, 0x84, 0x94, 0x20, 0x21, 0x82, 0xe1, 0x7d, 0x00, 0xa1, 0x81,
+ 0x86, 0x20, 0xe1, 0x84, 0x21, 0x7b, 0x00, 0x61, 0x84, 0x78, 0x20, 0x21, 0x7b, 0xe1, 0x84, 0x00,
+ 0xa1, 0x7a, 0x78, 0x20, 0xe1, 0x7d, 0x21, 0x82, 0x00, 0x61, 0x7d, 0x86, 0x20, 0x21, 0x7b, 0xe1,
+ 0x84, 0x00, 0xa1, 0x7a, 0x94, 0xe2, 0x9c, 0x5c, 0xe7, 0x7c, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88,
+ 0xe6, 0x70, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x05, 0x7c,
+ 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88,
+ 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x64, 0xe8, 0x70, 0xe7, 0xb8,
+ 0xe9, 0xac, 0xe1,
+}
+
+var NotificationEventNote = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x78, 0xe6, 0x6c,
+ 0xe9, 0x88, 0xe7, 0xa8, 0xe9, 0x78, 0xe3, 0x88, 0x64, 0xe7, 0x7c, 0xe8, 0x54, 0xe7, 0x78, 0xe9,
+ 0x88, 0xe6, 0x70, 0xe8, 0x54, 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x7c, 0xb0, 0xcd, 0x7d, 0x80, 0x05,
+ 0x7c, 0xcd, 0x81, 0x05, 0x7c, 0x88, 0x00, 0x5c, 0x9c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xc0, 0xe6, 0x64, 0xe8, 0x70, 0xe7,
+ 0xb8, 0xe9, 0xac, 0xe2, 0x88, 0x88, 0xe6, 0x6c, 0xe9, 0x88, 0xe7, 0x9c, 0xe9, 0x78, 0xe1,
+}
+
+var NotificationFolderSpecial = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x68, 0xe6, 0x80,
+ 0x20, 0x78, 0x78, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0,
+ 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88,
+ 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3,
+ 0xe1, 0x7b, 0xac, 0x01, 0x8c, 0x91, 0x86, 0x21, 0x80, 0x94, 0x22, 0x91, 0x81, 0x59, 0x79, 0xd1,
+ 0x7a, 0x85, 0x7b, 0xd5, 0x86, 0x69, 0x7f, 0x00, 0x8c, 0x70, 0x22, 0xad, 0x82, 0x49, 0x86, 0xd5,
+ 0x86, 0x99, 0x80, 0xd1, 0x7a, 0x7d, 0x84, 0x00, 0xe1, 0x8b, 0x94, 0xe1,
+}
+
+var NotificationLiveTV = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x68, 0xe6, 0xd5,
+ 0x82, 0x20, 0x99, 0x86, 0x69, 0x79, 0x00, 0x90, 0x58, 0x22, 0x70, 0x90, 0x70, 0x70, 0x99, 0x7e,
+ 0x69, 0x81, 0x00, 0x2d, 0x7d, 0x68, 0xe6, 0x5c, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x70, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x70, 0xe7, 0xc8, 0xe9, 0xb0, 0xe2, 0x74, 0x78,
+ 0xe9, 0xa0, 0x20, 0x9c, 0x70, 0xe1,
+}
+
+var NotificationMMS = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x64, 0x88, 0x21, 0x8e, 0x6e, 0x8a, 0x05, 0x86,
+ 0x00, 0x8a, 0x70, 0x20, 0x92, 0x98, 0xe6, 0x64, 0xe1,
+}
+
+var NotificationMore = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x5c, 0xe6, 0x6c,
+ 0xb0, 0xa1, 0x7e, 0x80, 0x89, 0x7d, 0xb5, 0x80, 0xd1, 0x7c, 0xc5, 0x81, 0x00, 0x50, 0xfd, 0x7f,
+ 0x20, 0xd1, 0x8a, 0x3d, 0x90, 0xb0, 0xb9, 0x80, 0x11, 0x81, 0xf1, 0x81, 0xc9, 0x81, 0x51, 0x83,
+ 0xc9, 0x81, 0xe6, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0,
+ 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x86, 0xb0, 0x59, 0x7e, 0x80, 0x7a,
+ 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x94, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a,
+ 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86,
+ 0xe3, 0x94, 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a,
+ 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var NotificationNetworkCheck = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xcd, 0x87, 0x64, 0xb0,
+ 0xa9, 0x7f, 0x80, 0x5d, 0x7f, 0x31, 0x80, 0x31, 0x7f, 0x75, 0x80, 0x20, 0xe1, 0x7f, 0x4d, 0x80,
+ 0x00, 0x7d, 0x7c, 0x11, 0x8a, 0xb2, 0xb1, 0x7f, 0x91, 0x80, 0x7d, 0x7f, 0x39, 0x81, 0x7d, 0x7f,
+ 0xf1, 0x81, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x05, 0x84, 0x05, 0x84, 0x05, 0x84, 0xf1, 0x81, 0x80,
+ 0x89, 0x83, 0xa5, 0x7e, 0xed, 0x83, 0xd5, 0x7c, 0x20, 0x05, 0x80, 0xf1, 0x7f, 0x00, 0xcd, 0x88,
+ 0x66, 0xb0, 0x80, 0x75, 0x7f, 0x8d, 0x7f, 0x7e, 0x7e, 0x7e, 0xe2, 0x54, 0x74, 0x20, 0x88, 0x88,
+ 0xb0, 0xc1, 0x85, 0x41, 0x7a, 0x91, 0x8d, 0xd9, 0x77, 0x11, 0x95, 0xc1, 0x78, 0x20, 0x61, 0x82,
+ 0xa5, 0x7a, 0xa0, 0xcd, 0x7b, 0xb1, 0x6f, 0x79, 0x71, 0x8d, 0x72, 0x54, 0x74, 0xe3, 0xd0, 0x88,
+ 0x20, 0x88, 0x78, 0xb0, 0xbd, 0x7c, 0xbd, 0x7c, 0xe9, 0x78, 0x61, 0x7a, 0xd5, 0x74, 0xdd, 0x78,
+ 0x20, 0xf1, 0x7e, 0xa1, 0x85, 0xb0, 0xfd, 0x82, 0x3d, 0x81, 0xcd, 0x85, 0x11, 0x83, 0x3d, 0x88,
+ 0x85, 0x85, 0xe3, 0x70, 0x90, 0x20, 0x88, 0x78, 0xb0, 0x69, 0x7e, 0x69, 0x7e, 0x99, 0x7c, 0x29,
+ 0x7d, 0xad, 0x7a, 0x39, 0x7c, 0x20, 0xe5, 0x7e, 0xd9, 0x85, 0xa0, 0x71, 0x88, 0x9d, 0x84, 0x41,
+ 0x89, 0x41, 0x85, 0x94, 0x8c, 0xe2, 0x64, 0x84, 0x20, 0x88, 0x88, 0xb0, 0x45, 0x82, 0xbd, 0x7d,
+ 0x1d, 0x85, 0x69, 0x7c, 0x11, 0x88, 0x05, 0x7c, 0x20, 0x91, 0x82, 0x3d, 0x7a, 0xa0, 0x59, 0x7b,
+ 0x15, 0x7c, 0x09, 0x76, 0xfd, 0x7d, 0x64, 0x84, 0xe1,
+}
+
+var NotificationNetworkLocked = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9e, 0x78, 0xb0, 0x59,
+ 0x80, 0x80, 0xad, 0x80, 0x11, 0x80, 0x82, 0x19, 0x80, 0xe8, 0x54, 0x00, 0x54, 0xa0, 0xe7, 0xb4,
+ 0xe9, 0x74, 0xb0, 0x80, 0x3d, 0x7e, 0xc5, 0x80, 0xa1, 0x7c, 0x84, 0x89, 0x7b, 0xe8, 0x8a, 0xb0,
+ 0x80, 0x0d, 0x7b, 0x0d, 0x84, 0x6e, 0x92, 0x6e, 0xe3, 0x8a, 0x98, 0xe9, 0x7a, 0xb0, 0x80, 0x3d,
+ 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0xe9, 0x86, 0xb0, 0xe9,
+ 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84,
+ 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x70, 0xb0,
+ 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x80, 0xe7, 0x74, 0xe9, 0x7a, 0xb0,
+ 0x80, 0x59, 0x7e, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x90, 0x86, 0x59, 0x81, 0x86, 0x86, 0xe9, 0x86,
+ 0xe1,
+}
+
+var NotificationNoEncryption = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x91, 0x93, 0x01,
+ 0x71, 0x70, 0x64, 0x5c, 0x71, 0x74, 0x20, 0x15, 0x84, 0x15, 0x84, 0xa0, 0xd9, 0x70, 0x35, 0x79,
+ 0x60, 0x81, 0x7a, 0x60, 0x78, 0xe9, 0xa8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xb0, 0xb0, 0x79, 0x80, 0x80, 0xe9, 0x80, 0xe9, 0x7f, 0x51, 0x81, 0xc5, 0x7f, 0x01, 0x91,
+ 0x8f, 0xac, 0xa4, 0x91, 0x93, 0xe2, 0xcd, 0x79, 0x68, 0xb0, 0x80, 0x95, 0x7c, 0xc9, 0x82, 0xcd,
+ 0x79, 0x35, 0x86, 0xcd, 0x79, 0x90, 0x35, 0x86, 0xc9, 0x82, 0x35, 0x86, 0x35, 0x86, 0xe9, 0x88,
+ 0xe6, 0x51, 0x7b, 0x00, 0xa0, 0xb1, 0x8c, 0xe8, 0x78, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78,
+ 0x78, 0x78, 0xe7, 0x7c, 0xe9, 0x78, 0xb1, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0xe5,
+ 0x7a, 0x80, 0xb9, 0x76, 0xdd, 0x83, 0x21, 0x76, 0xd1, 0x88, 0x20, 0xb1, 0x83, 0xb1, 0x83, 0xe8,
+ 0x68, 0xe1,
+}
+
+var NotificationOnDemandVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xac, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe2, 0x90, 0x7c, 0x20, 0x64, 0x90, 0xe8, 0x6c, 0xe1,
+}
+
+var NotificationPersonalVideo = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa4, 0x5c, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x94, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x94, 0xb0, 0x35,
+ 0x82, 0x80, 0xfd, 0x83, 0x35, 0x7e, 0xfd, 0x83, 0x78, 0x00, 0xac, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80, 0xb8, 0xe6, 0x5c, 0xe8, 0x64, 0xe7, 0xc8, 0xe9, 0xb0,
+ 0xe1,
+}
+
+var NotificationPhoneBluetoothSpeaker = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x69, 0x85, 0x76, 0x00,
+ 0x94, 0x69, 0x76, 0xe8, 0x7c, 0xe7, 0x82, 0x22, 0xb5, 0x85, 0x4d, 0x7a, 0xb5, 0x7b, 0xb5, 0x7b,
+ 0x4d, 0x84, 0xb5, 0x7b, 0x00, 0x96, 0x54, 0xe7, 0x7e, 0xe9, 0x99, 0x87, 0x04, 0x69, 0x85, 0x5a,
+ 0x88, 0x69, 0x6e, 0x99, 0x89, 0x68, 0x88, 0x99, 0x79, 0x69, 0x85, 0x76, 0xe2, 0x98, 0xd5, 0x6d,
+ 0x20, 0xe1, 0x81, 0xe1, 0x81, 0x00, 0x98, 0x99, 0x71, 0xe8, 0xd5, 0x6d, 0xe3, 0x80, 0x95, 0x88,
+ 0x20, 0xe1, 0x81, 0xe1, 0x81, 0x00, 0x98, 0x2d, 0x7a, 0xe9, 0x3d, 0x7c, 0xe2, 0xa0, 0x8e, 0xb1,
+ 0x85, 0x7d, 0x80, 0x1d, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9, 0x7f, 0x85,
+ 0x7e, 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x55, 0x7a, 0x21,
+ 0x7d, 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x99, 0x7b, 0xb0, 0x8d,
+ 0x80, 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79, 0xe9, 0x74,
+ 0x72, 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60,
+ 0xb2, 0xe5, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f, 0xc4, 0xc4,
+ 0xc4, 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5, 0x7e, 0x1d,
+ 0x7f, 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var NotificationPhoneForwarded = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x7c, 0x20, 0x94,
+ 0x6c, 0x00, 0x98, 0x54, 0xe9, 0x8c, 0xe7, 0x70, 0xe9, 0x90, 0xe7, 0x90, 0xe9, 0x8c, 0xe3, 0x88,
+ 0x92, 0xb1, 0x85, 0x7d, 0x80, 0x1d, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9,
+ 0x7f, 0x85, 0x7e, 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x55,
+ 0x7a, 0x21, 0x7d, 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x99, 0x7b,
+ 0xb0, 0x8d, 0x80, 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79,
+ 0xe5, 0x74, 0x72, 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c,
+ 0xe6, 0x60, 0xb2, 0xe5, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f,
+ 0xc4, 0xc4, 0xc4, 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5,
+ 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe1,
+}
+
+var NotificationPhoneInTalk = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x8e, 0xb1, 0x85,
+ 0x7d, 0x80, 0x1d, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9, 0x7f, 0x85, 0x7e,
+ 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x55, 0x7a, 0x21, 0x7d,
+ 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x99, 0x7b, 0xb0, 0x8d, 0x80,
+ 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79, 0xe9, 0x74, 0x72,
+ 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb2,
+ 0xe5, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f, 0xc4, 0xc4, 0xc4,
+ 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x7c, 0x72, 0xe7, 0x88, 0xb0, 0x80, 0x11, 0x76, 0xf1, 0x77, 0x5c, 0x5c,
+ 0x5c, 0xe9, 0x88, 0xb0, 0xbd, 0x87, 0x80, 0x9c, 0x45, 0x86, 0x9c, 0x9c, 0xe3, 0x70, 0x80, 0xe7,
+ 0x88, 0xb0, 0x80, 0x7d, 0x7a, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0xe9, 0x88, 0xb0, 0x51, 0x83, 0x80,
+ 0x8c, 0xb1, 0x82, 0x8c, 0x8c, 0xe1,
+}
+
+var NotificationPhoneLocked = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x8e, 0xb1, 0x85,
+ 0x7d, 0x80, 0x19, 0x7b, 0x99, 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9, 0x7f, 0x85, 0x7e,
+ 0xf1, 0x7f, 0xf9, 0x7d, 0x7d, 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x55, 0x7a, 0x21, 0x7d,
+ 0xb5, 0x75, 0x7d, 0x78, 0xd1, 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x99, 0x7b, 0xb0, 0x8d, 0x80,
+ 0x75, 0x7f, 0xb5, 0x80, 0xa9, 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79, 0xe9, 0x74, 0x72,
+ 0x7d, 0x72, 0x72, 0x60, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb2,
+ 0xe9, 0x7e, 0x80, 0x7c, 0xe5, 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f, 0xc4, 0xc4, 0xc4,
+ 0x1d, 0x81, 0x80, 0x84, 0x1d, 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x80, 0x52, 0xe8, 0x5e, 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76,
+ 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0xe9, 0x82, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0xe9, 0x80,
+ 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81, 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0,
+ 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f,
+ 0x7c, 0x7c, 0x7c, 0xe3, 0x69, 0x7e, 0x80, 0xe7, 0x35, 0x79, 0xe8, 0x5e, 0xb0, 0x80, 0x21, 0x7e,
+ 0x85, 0x81, 0x99, 0x7c, 0x69, 0x83, 0x99, 0x7c, 0x90, 0x69, 0x83, 0x85, 0x81, 0x69, 0x83, 0x69,
+ 0x83, 0xe9, 0x82, 0xe1,
+}
+
+var NotificationPhoneMissed = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x6a, 0x66, 0x20, 0x96,
+ 0x96, 0x00, 0x9c, 0x60, 0x22, 0x7c, 0x7c, 0x68, 0x98, 0x6e, 0x6e, 0xe7, 0x8e, 0xe8, 0x5c, 0xe6,
+ 0x64, 0xe9, 0x98, 0xe7, 0x86, 0xe9, 0x72, 0xe3, 0x69, 0xa2, 0x59, 0x96, 0xa0, 0x51, 0x91, 0x8d,
+ 0x83, 0x11, 0x89, 0x80, 0x80, 0x80, 0x80, 0xb1, 0x6e, 0x8d, 0x83, 0x99, 0x68, 0x59, 0x89, 0xb0,
+ 0xa1, 0x7f, 0x61, 0x80, 0x69, 0x7f, 0xe1, 0x80, 0x69, 0x7f, 0x6d, 0x81, 0x90, 0x39, 0x80, 0x0d,
+ 0x81, 0x99, 0x80, 0x69, 0x81, 0x20, 0xf5, 0x84, 0xf5, 0x84, 0xb3, 0x5d, 0x80, 0x5d, 0x80, 0xdd,
+ 0x80, 0x99, 0x80, 0x6d, 0x81, 0x99, 0x80, 0x8d, 0x80, 0x80, 0x0d, 0x81, 0xc9, 0x7f, 0x69, 0x81,
+ 0x71, 0x7f, 0x95, 0x81, 0x89, 0x7e, 0x61, 0x83, 0x49, 0x7d, 0x55, 0x85, 0x4d, 0x7c, 0xa9, 0x80,
+ 0xad, 0x7f, 0x21, 0x81, 0xfd, 0x7e, 0x21, 0x81, 0x35, 0x7e, 0xe9, 0xcd, 0x79, 0xa0, 0xb5, 0x79,
+ 0x81, 0x84, 0xcd, 0x7c, 0x88, 0x80, 0x88, 0x90, 0x4d, 0x86, 0x81, 0x80, 0x35, 0x89, 0x71, 0x81,
+ 0xe9, 0x35, 0x86, 0xb3, 0x80, 0xcd, 0x80, 0x75, 0x80, 0x79, 0x81, 0x21, 0x81, 0xcd, 0x81, 0xf5,
+ 0x81, 0xfd, 0x80, 0xc1, 0x83, 0x3d, 0x82, 0x55, 0x85, 0xb5, 0x83, 0x5d, 0x80, 0x59, 0x80, 0xd9,
+ 0x80, 0x91, 0x80, 0x69, 0x81, 0x91, 0x80, 0x8d, 0x80, 0x80, 0x0d, 0x81, 0xc9, 0x7f, 0x6d, 0x81,
+ 0x69, 0x7f, 0x20, 0xf5, 0x84, 0x0d, 0x7b, 0xb0, 0x5d, 0x80, 0xa5, 0x7f, 0x99, 0x80, 0x25, 0x7f,
+ 0x99, 0x80, 0x99, 0x7e, 0x90, 0xc5, 0x7f, 0xf1, 0x7e, 0x69, 0x7f, 0x91, 0x7e, 0xe1,
+}
+
+var NotificationPhonePaused = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x5c, 0xe7, 0x78,
+ 0xe9, 0x9c, 0xe7, 0x88, 0xe8, 0x5c, 0xe3, 0x8c, 0xb2, 0xb1, 0x85, 0x7d, 0x80, 0x1d, 0x7b, 0x99,
+ 0x7f, 0xdd, 0x78, 0xdd, 0x7e, 0x51, 0x7f, 0xc9, 0x7f, 0x85, 0x7e, 0xf1, 0x7f, 0xf9, 0x7d, 0x7d,
+ 0x80, 0x20, 0x99, 0x7b, 0x69, 0x84, 0xb0, 0x55, 0x7a, 0x21, 0x7d, 0xb5, 0x75, 0x7d, 0x78, 0xd1,
+ 0x72, 0xd5, 0x72, 0x20, 0x69, 0x84, 0x99, 0x7b, 0xb0, 0x8d, 0x80, 0x75, 0x7f, 0xb5, 0x80, 0xa9,
+ 0x7e, 0x7d, 0x80, 0xf9, 0x7d, 0xa0, 0x69, 0x79, 0xe9, 0x74, 0x72, 0x7d, 0x72, 0x72, 0x60, 0xb0,
+ 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x60, 0xb2, 0xe5, 0x7e, 0x80, 0x7c, 0xe5,
+ 0x80, 0x7c, 0x84, 0x80, 0xc9, 0x92, 0x39, 0x8f, 0xc4, 0xc4, 0xc4, 0x1d, 0x81, 0x80, 0x84, 0x1d,
+ 0x7f, 0x84, 0x7c, 0xe9, 0x72, 0xb0, 0x80, 0xe5, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe2, 0x9c,
+ 0x5c, 0xe9, 0x9c, 0xe7, 0x88, 0xe8, 0x5c, 0xe7, 0x78, 0xe1,
+}
+
+var NotificationPower = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x05, 0x88, 0x6c, 0xe6,
+ 0x90, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x90, 0xe7, 0x70, 0xe8, 0x5c, 0xe7, 0x78, 0xe9, 0x90, 0xe7,
+ 0xfd, 0x7f, 0xa0, 0x6c, 0xfd, 0x75, 0x68, 0xfd, 0x77, 0x68, 0xfd, 0x79, 0xe9, 0xfd, 0x8a, 0x00,
+ 0x76, 0x98, 0xe9, 0x8c, 0xe7, 0x94, 0xe9, 0x74, 0x20, 0x8e, 0xf9, 0x78, 0xe8, 0xfd, 0x79, 0xb0,
+ 0x80, 0x05, 0x7e, 0x7c, 0x05, 0x7c, 0x05, 0x7c, 0x05, 0x7c, 0xe1,
+}
+
+var NotificationPriorityHigh = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x5c, 0xe7, 0x90,
+ 0xe9, 0xb0, 0xe7, 0x70, 0xe2, 0x78, 0x9c, 0xd1, 0x88, 0x88, 0x00, 0x04, 0x90, 0x80, 0x88, 0x88,
+ 0x00, 0x04, 0x70, 0x80, 0xe1,
+}
+
+var NotificationRVHookup = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x94, 0xe8, 0x7c,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x6c, 0xe9, 0x78, 0x21, 0x74, 0x8c,
+ 0x8c, 0x8c, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x8c, 0xe6, 0x60, 0xe9, 0x8c, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x88, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c,
+ 0x90, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x78, 0xe3, 0x5c, 0x8c, 0xb1,
+ 0xe5, 0x7e, 0x80, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0x80, 0xe5, 0x7e, 0xe5, 0x80, 0x7c, 0x84, 0x7c,
+ 0x90, 0x84, 0xe5, 0x80, 0x84, 0x84, 0xb0, 0x80, 0x1d, 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe3,
+ 0x9c, 0x68, 0xe7, 0x70, 0xe9, 0x74, 0xe7, 0x90, 0xe9, 0x8c, 0xe2, 0x94, 0x58, 0xe9, 0x88, 0xe6,
+ 0x74, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x88, 0x20, 0x8c, 0x74, 0xe1,
+}
+
+var NotificationSDCard = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x58, 0xe6, 0x78,
+ 0x01, 0x0d, 0x70, 0x70, 0x60, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x80, 0x70, 0xe7, 0x78, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x90,
+ 0xe3, 0x8c, 0x80, 0xe7, 0x78, 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x90, 0xe3, 0x8c, 0x80, 0xe7, 0x78,
+ 0xe8, 0x60, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var NotificationSIMCardAlert = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x58, 0xe6, 0x78,
+ 0x01, 0x0d, 0x70, 0x70, 0x60, 0xa0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x84, 0x94, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe8, 0x70, 0xe7, 0x88, 0xe9, 0x94, 0xe1,
+}
+
+var NotificationSMS = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x7c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90, 0x80,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var NotificationSMSFailed = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x84, 0x88, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var NotificationSync = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x60, 0xe8, 0x54,
+ 0x21, 0x70, 0x90, 0x90, 0x90, 0xe9, 0x74, 0xb1, 0xa1, 0x86, 0x80, 0x98, 0x61, 0x85, 0x98, 0x98,
+ 0x80, 0x09, 0x82, 0x7d, 0x7f, 0xf1, 0x83, 0x9d, 0x7e, 0x9d, 0x85, 0x20, 0xed, 0x82, 0xed, 0x82,
+ 0xa0, 0x15, 0x8f, 0x0d, 0x86, 0xa0, 0x25, 0x83, 0xa0, 0x80, 0xb0, 0x80, 0x29, 0x77, 0xd9, 0x78,
+ 0x60, 0x60, 0x60, 0xe3, 0x80, 0xb8, 0xb1, 0x61, 0x79, 0x80, 0x68, 0xa1, 0x7a, 0x68, 0x68, 0x80,
+ 0xf9, 0x7d, 0x85, 0x80, 0x11, 0x7c, 0x65, 0x81, 0x65, 0x7a, 0x20, 0x15, 0x7d, 0x15, 0x7d, 0xa0,
+ 0xed, 0x70, 0xf5, 0x79, 0x60, 0xdd, 0x7c, 0x60, 0x80, 0xb0, 0x80, 0xd9, 0x88, 0x29, 0x87, 0xa0,
+ 0xa0, 0xa0, 0xe9, 0x8c, 0x21, 0x90, 0x70, 0x70, 0x70, 0xe9, 0x8c, 0xe1,
+}
+
+var NotificationSyncDisabled = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0xb5, 0x74, 0xe8,
+ 0x85, 0x70, 0xb0, 0x69, 0x7e, 0x69, 0x80, 0xe9, 0x7c, 0x11, 0x81, 0x8d, 0x7b, 0xed, 0x81, 0x20,
+ 0xf1, 0x82, 0xf1, 0x82, 0xb0, 0x7d, 0x80, 0xc1, 0x7f, 0x82, 0x85, 0x7f, 0x89, 0x81, 0x55, 0x7f,
+ 0xe2, 0xbd, 0x6d, 0xd1, 0x72, 0x20, 0xb5, 0x84, 0xb5, 0x84, 0xa0, 0xe9, 0x70, 0xfd, 0x79, 0x60,
+ 0xe1, 0x7c, 0x60, 0x80, 0xb0, 0x80, 0x6d, 0x84, 0xd1, 0x81, 0x69, 0x88, 0xb5, 0x84, 0x4d, 0x8b,
+ 0x00, 0x60, 0xa0, 0xe7, 0x98, 0xe8, 0x88, 0x20, 0x89, 0x7b, 0x79, 0x84, 0xa0, 0x59, 0x75, 0x4d,
+ 0x86, 0x68, 0x51, 0x83, 0x68, 0x80, 0xb0, 0x80, 0x7c, 0x81, 0x80, 0x21, 0x7c, 0x61, 0x81, 0x75,
+ 0x7a, 0x20, 0x2d, 0x90, 0x2d, 0x90, 0xb0, 0x85, 0x7f, 0x45, 0x80, 0x7e, 0x7d, 0x80, 0x79, 0x7e,
+ 0xb1, 0x80, 0xe9, 0x2d, 0x84, 0xb0, 0x99, 0x81, 0x99, 0x7f, 0x19, 0x83, 0xf1, 0x7e, 0x75, 0x84,
+ 0x15, 0x7e, 0x21, 0xb9, 0x84, 0xb9, 0x84, 0x8d, 0x82, 0x75, 0x7d, 0x00, 0x45, 0x70, 0x45, 0x70,
+ 0x20, 0x75, 0x7d, 0x8d, 0x82, 0xe2, 0xa0, 0x60, 0xe6, 0x88, 0xe9, 0x98, 0x20, 0x79, 0x84, 0x89,
+ 0x7b, 0xa0, 0xa9, 0x8a, 0xb5, 0x79, 0x98, 0xb1, 0x7c, 0x98, 0x80, 0xb0, 0x80, 0x84, 0x81, 0x7f,
+ 0xe1, 0x83, 0xa1, 0x7e, 0x8d, 0x85, 0x20, 0xf1, 0x82, 0xf1, 0x82, 0xa0, 0x19, 0x8f, 0x05, 0x86,
+ 0xa0, 0x21, 0x83, 0xa0, 0x80, 0xb0, 0x80, 0x95, 0x7b, 0x31, 0x7e, 0x99, 0x77, 0x4d, 0x7b, 0xb5,
+ 0x74, 0x00, 0xa0, 0x60, 0xe1,
+}
+
+var NotificationSyncProblem = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x80, 0xb0, 0x80,
+ 0x6d, 0x84, 0xd1, 0x81, 0x69, 0x88, 0xb5, 0x84, 0x4d, 0x8b, 0x00, 0x5c, 0xa0, 0xe7, 0x98, 0xe8,
+ 0x88, 0x20, 0x89, 0x7b, 0x79, 0x84, 0xa0, 0x59, 0x73, 0x4d, 0x86, 0x64, 0x51, 0x83, 0x64, 0x80,
+ 0xb0, 0x80, 0xc9, 0x7a, 0x59, 0x83, 0x59, 0x76, 0x90, 0xb5, 0x74, 0xe8, 0x85, 0x70, 0xa0, 0x19,
+ 0x73, 0x4d, 0x72, 0x5c, 0x8d, 0x78, 0x5c, 0x80, 0xe3, 0xa0, 0x94, 0xe7, 0x88, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe2, 0xa4, 0x60, 0xe6, 0x8c, 0xe9, 0x98, 0x20, 0x79, 0x84, 0x89, 0x7b, 0xa0,
+ 0xa9, 0x8c, 0xb5, 0x79, 0x9c, 0xb1, 0x7c, 0x9c, 0x80, 0xb0, 0x80, 0x39, 0x85, 0xa9, 0x7c, 0xa9,
+ 0x89, 0x70, 0x4d, 0x8b, 0xe9, 0x2d, 0x84, 0xa0, 0xe9, 0x8c, 0xb5, 0x8d, 0xa4, 0x75, 0x87, 0xa4,
+ 0x80, 0xb0, 0x80, 0x95, 0x7b, 0x31, 0x7e, 0x99, 0x77, 0x4d, 0x7b, 0xb5, 0x74, 0x00, 0xa4, 0x60,
+ 0xe2, 0x7c, 0x84, 0xe7, 0x88, 0xe8, 0x6c, 0xe7, 0x78, 0xe9, 0x98, 0xe1,
+}
+
+var NotificationSystemUpdate = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x05, 0x6a, 0x00,
+ 0x6c, 0x54, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xc8, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x5c, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe2, 0x94,
+ 0x9c, 0xe6, 0x6c, 0xe8, 0x64, 0xe7, 0xa8, 0xe9, 0xb8, 0xe3, 0x7c, 0x68, 0xe7, 0x74, 0xe8, 0x70,
+ 0xe7, 0x78, 0xe9, 0x94, 0xe7, 0x74, 0x21, 0x90, 0x90, 0x90, 0x70, 0xe1,
+}
+
+var NotificationTapAndPlay = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x90, 0xe9, 0x88,
+ 0xb0, 0x85, 0x85, 0x80, 0x94, 0x7d, 0x84, 0x94, 0x94, 0xe7, 0x88, 0xb0, 0x80, 0x45, 0x78, 0xbd,
+ 0x79, 0x64, 0x64, 0x64, 0xe3, 0x80, 0x90, 0xe9, 0x8c, 0xe7, 0x8c, 0xb0, 0x80, 0xb1, 0x7c, 0x51,
+ 0x7d, 0x74, 0x74, 0x74, 0xe3, 0x80, 0x60, 0xe9, 0x88, 0xb0, 0xf1, 0x89, 0x80, 0xa4, 0x11, 0x88,
+ 0xa4, 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0xd9, 0x73, 0x29, 0x76, 0x54, 0x54, 0x54, 0xe2, 0x94, 0x05,
+ 0x6a, 0x00, 0x6c, 0x54, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xbd, 0x8e,
+ 0xb0, 0x61, 0x81, 0x55, 0x80, 0xb9, 0x82, 0xbd, 0x80, 0x88, 0x49, 0x81, 0xe8, 0x64, 0xe7, 0xa8,
+ 0xe9, 0xb4, 0xe7, 0xf1, 0x79, 0xb0, 0x0d, 0x81, 0x7d, 0x82, 0xb1, 0x81, 0x31, 0x85, 0xe9, 0x81,
+ 0x90, 0xe6, 0x94, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x5c, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe1,
+}
+
+var NotificationTimeToLeave = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xd9, 0x8d, 0x05, 0x72,
+ 0xa0, 0x71, 0x8d, 0xd9, 0x70, 0x51, 0x8c, 0x60, 0x96, 0x60, 0xe6, 0x6a, 0xb0, 0xb1, 0x7e, 0x80,
+ 0x91, 0x7d, 0xd9, 0x80, 0x29, 0x7d, 0x05, 0x82, 0x00, 0x5c, 0x7c, 0xe9, 0xa0, 0xb0, 0x80, 0x19,
+ 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84,
+ 0x7c, 0xe9, 0x7c, 0xe7, 0xb0, 0xe9, 0x84, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84, 0x84, 0x84,
+ 0xe7, 0x84, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe8, 0x7c, 0x20, 0xd9, 0x7b,
+ 0x05, 0x74, 0xe2, 0x6a, 0x8c, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59,
+ 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xac,
+ 0x80, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a,
+ 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe2, 0x64, 0x78, 0x20, 0x86, 0x6e,
+ 0xe7, 0xac, 0x20, 0x86, 0x92, 0xe6, 0x64, 0xe1,
+}
+
+var NotificationVibration = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x50, 0x8c, 0xe7, 0x88,
+ 0xe8, 0x74, 0xe6, 0x50, 0xe9, 0x98, 0xe3, 0x8c, 0x88, 0xe7, 0x88, 0xe8, 0x6c, 0xe6, 0x5c, 0xe9,
+ 0xa8, 0xe3, 0xcc, 0x60, 0xe9, 0x98, 0xe7, 0x88, 0xe8, 0x74, 0xe7, 0x78, 0xe3, 0x74, 0xa0, 0xe7,
+ 0x88, 0xe8, 0x6c, 0xe7, 0x78, 0xe9, 0xa8, 0xe2, 0x92, 0x5c, 0xe6, 0x6e, 0xb0, 0x59, 0x7e, 0x80,
+ 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe9, 0xbc, 0xb0, 0x80, 0xa9, 0x81, 0x59, 0x81, 0x86, 0x86, 0x86,
+ 0xe7, 0xa4, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe8, 0x62, 0xb0, 0x80, 0x59,
+ 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0x7e, 0xc0, 0xe6, 0x70, 0xe8, 0x64, 0xe7, 0xa0, 0xe9,
+ 0xb8, 0xe1,
+}
+
+var NotificationVoiceChat = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x58, 0xe6, 0x60,
+ 0xa0, 0xcd, 0x6d, 0x58, 0x05, 0x6c, 0xcd, 0x6d, 0x05, 0x6c, 0x60, 0x00, 0x58, 0xa8, 0x20, 0x90,
+ 0x70, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x60, 0xb0, 0x80,
+ 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xb0, 0x20, 0x70, 0x99, 0x79, 0xe8, 0x88,
+ 0xe6, 0x68, 0xe8, 0x68, 0xe7, 0xa0, 0xe9, 0x69, 0x86, 0x20, 0x90, 0x99, 0x79, 0xe9, 0xa0, 0xe1,
+}
+
+var NotificationVPNLock = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x60, 0xe8, 0x5e,
+ 0xb0, 0x80, 0x3d, 0x7d, 0xc5, 0x7d, 0x76, 0x76, 0x76, 0x90, 0x76, 0x3d, 0x82, 0x76, 0x8a, 0xe9,
+ 0x82, 0xb0, 0xe5, 0x7e, 0x80, 0x7c, 0xe9, 0x80, 0x7c, 0x84, 0xe9, 0x90, 0xb0, 0x80, 0x19, 0x81,
+ 0xe5, 0x80, 0x84, 0x84, 0x84, 0xe7, 0x94, 0xb0, 0x1d, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c,
+ 0xe9, 0x70, 0xb0, 0x80, 0xe9, 0x7e, 0x1d, 0x7f, 0x7c, 0x7c, 0x7c, 0xe3, 0x69, 0x7e, 0x80, 0xe7,
+ 0x35, 0x79, 0xe8, 0x5e, 0xb0, 0x80, 0x21, 0x7e, 0x85, 0x81, 0x99, 0x7c, 0x69, 0x83, 0x99, 0x7c,
+ 0x90, 0x69, 0x83, 0x85, 0x81, 0x69, 0x83, 0x69, 0x83, 0xe9, 0x82, 0xe3, 0x75, 0x7b, 0xa0, 0xb1,
+ 0x15, 0x80, 0xa9, 0x80, 0x29, 0x80, 0x51, 0x81, 0x29, 0x80, 0x84, 0x80, 0x29, 0x84, 0x69, 0x7e,
+ 0xf1, 0x87, 0xcd, 0x7b, 0xcd, 0x8a, 0xa0, 0x49, 0x89, 0x2d, 0x8b, 0xc9, 0x87, 0x94, 0x8c, 0x94,
+ 0xe7, 0x7c, 0xe9, 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6, 0x6c, 0xe9,
+ 0x78, 0xe7, 0x88, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x78, 0xe7, 0x88,
+ 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0xed, 0x6e, 0xb0, 0x1d, 0x7e, 0x69,
+ 0x7f, 0x19, 0x7c, 0x15, 0x7f, 0x74, 0x15, 0x7f, 0xa0, 0xf5, 0x72, 0x5c, 0x54, 0xf5, 0x76, 0x54,
+ 0x84, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0xb0, 0x80, 0x51, 0x7f,
+ 0xf5, 0x7f, 0xa9, 0x7e, 0xe9, 0x7f, 0x7c, 0xe7, 0xf5, 0x7b, 0xe2, 0x78, 0xdd, 0x91, 0xa0, 0x1d,
+ 0x74, 0xe1, 0x90, 0x5c, 0x29, 0x8a, 0x5c, 0x84, 0xb0, 0x80, 0xc5, 0x7e, 0x29, 0x80, 0x91, 0x7d,
+ 0x6d, 0x80, 0x6d, 0x7c, 0x00, 0x70, 0x90, 0xe9, 0x84, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe9, 0xdd, 0x83, 0xe1,
+}
+
+var NotificationWC = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x66, 0xa8, 0xe8, 0x8a,
+ 0xe6, 0x60, 0xe8, 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe7, 0x8c, 0xb0,
+ 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0xe9, 0x96, 0xe7, 0x7a, 0xe9, 0x9e, 0xe7, 0x70,
+ 0xe3, 0xb2, 0x80, 0xe8, 0x90, 0xe7, 0x8c, 0x20, 0xe9, 0x7a, 0xbd, 0x70, 0xa0, 0x61, 0x8c, 0x19,
+ 0x77, 0xd9, 0x8a, 0x6c, 0x21, 0x89, 0x6c, 0xe7, 0xc5, 0x7f, 0xb0, 0x49, 0x7e, 0x80, 0xc1, 0x7c,
+ 0x19, 0x81, 0x35, 0x7c, 0xbd, 0x82, 0x00, 0x80, 0x90, 0xe7, 0x8c, 0xe9, 0x98, 0xe7, 0x8c, 0xe2,
+ 0x6e, 0x68, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78,
+ 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0xa4, 0x80, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x92, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x78, 0xcd, 0x81,
+ 0x78, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe1,
+}
+
+var NotificationWiFi = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x54, 0x74, 0x20, 0x88,
+ 0x88, 0xb0, 0xf1, 0x89, 0x11, 0x76, 0x11, 0x9a, 0x11, 0x76, 0xc8, 0x80, 0x20, 0x88, 0x78, 0xa0,
+ 0xd9, 0x89, 0xd9, 0x6d, 0x29, 0x76, 0xd9, 0x6d, 0x54, 0x74, 0xe3, 0xa0, 0xa0, 0x21, 0x8c, 0x8c,
+ 0x8c, 0x74, 0xb0, 0xb1, 0x7c, 0xb1, 0x7c, 0x51, 0x77, 0xb1, 0x7c, 0x68, 0x80, 0xe3, 0x70, 0x70,
+ 0x20, 0x88, 0x88, 0xb0, 0x85, 0x85, 0x7d, 0x7a, 0x7d, 0x8e, 0x7d, 0x7a, 0xa8, 0x80, 0x20, 0x88,
+ 0x78, 0xb0, 0x45, 0x78, 0x45, 0x78, 0xbd, 0x6b, 0x45, 0x78, 0x48, 0x80, 0xe1,
+}
+
+var PlacesACUnit = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x7c, 0xe7, 0xa9,
+ 0x77, 0x21, 0x7d, 0x86, 0x85, 0x79, 0x2d, 0x7d, 0x31, 0x7d, 0x00, 0x8c, 0x7c, 0xe7, 0x78, 0xe9,
+ 0x78, 0x21, 0x51, 0x89, 0xb1, 0x76, 0x31, 0x7d, 0x2d, 0x7d, 0x00, 0x84, 0x59, 0x74, 0xe8, 0x58,
+ 0xe7, 0x78, 0xe9, 0x59, 0x88, 0x21, 0x85, 0x79, 0x85, 0x79, 0x31, 0x7d, 0xd5, 0x82, 0x00, 0x7c,
+ 0x74, 0xe9, 0x88, 0xe7, 0x78, 0x21, 0xb1, 0x76, 0xb1, 0x76, 0x2d, 0x7d, 0xd1, 0x82, 0x00, 0x59,
+ 0x74, 0x7c, 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0x59, 0x88, 0x21, 0x85, 0x79, 0x7d, 0x86, 0xd5, 0x82,
+ 0xd1, 0x82, 0x00, 0x74, 0x84, 0xe7, 0x88, 0xe9, 0x88, 0x21, 0xb1, 0x76, 0x51, 0x89, 0xd1, 0x82,
+ 0xd5, 0x82, 0x00, 0x7c, 0xa9, 0x8b, 0xe8, 0xa8, 0xe7, 0x88, 0xe9, 0xa9, 0x77, 0x21, 0x7d, 0x86,
+ 0x7d, 0x86, 0xd1, 0x82, 0x2d, 0x7d, 0x00, 0x84, 0x8c, 0xe9, 0x78, 0xe7, 0x88, 0x21, 0x51, 0x89,
+ 0x51, 0x89, 0xd5, 0x82, 0x31, 0x7d, 0x00, 0xa9, 0x8b, 0x84, 0xe6, 0xa8, 0xe1,
+}
+
+var PlacesAirportShuttle = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x94, 0x64, 0xe6, 0x5c,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xa4, 0xe7, 0x88, 0xb0, 0x80, 0x51,
+ 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x90, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe7, 0x96, 0xb0, 0x80,
+ 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0x90, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe7, 0x8a, 0xe8,
+ 0x7c, 0x00, 0x94, 0x64, 0xe2, 0x5c, 0x7c, 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe6, 0x5c, 0xe3,
+ 0x8c, 0x9a, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x9c, 0x66, 0xe7, 0x70,
+ 0xe9, 0x70, 0xe7, 0x90, 0xe9, 0x90, 0xe3, 0x92, 0x9a, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e,
+ 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86,
+ 0x7a, 0x86, 0xe3, 0x76, 0x66, 0xe9, 0x70, 0xe7, 0x84, 0x20, 0x90, 0x90, 0xe6, 0x8c, 0xe1,
+}
+
+var PlacesAllInclusive = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x35, 0x8d, 0x41, 0x75,
+ 0xb0, 0x21, 0x7d, 0x80, 0x69, 0x7a, 0x21, 0x81, 0x75, 0x78, 0x11, 0x83, 0x24, 0x59, 0x7a, 0x8a,
+ 0xfd, 0x7f, 0xfd, 0x7f, 0xf9, 0x7c, 0xad, 0x82, 0x05, 0x80, 0x05, 0x80, 0x9d, 0x7a, 0xc9, 0x84,
+ 0xb1, 0xb9, 0x7e, 0x45, 0x81, 0x05, 0x7d, 0xf9, 0x81, 0x35, 0x7b, 0xf9, 0x81, 0x45, 0x7c, 0x80,
+ 0x39, 0x79, 0xf9, 0x7c, 0x39, 0x79, 0x41, 0x79, 0x90, 0x0d, 0x83, 0x41, 0x79, 0xc9, 0x86, 0x41,
+ 0x79, 0xb0, 0xd1, 0x81, 0x80, 0x85, 0x83, 0xb5, 0x80, 0xe1, 0x84, 0x11, 0x82, 0x22, 0x45, 0x82,
+ 0x05, 0x82, 0x05, 0x83, 0x55, 0x7d, 0x79, 0x7d, 0xc5, 0x7d, 0xb0, 0xf5, 0x7d, 0xf9, 0x7d, 0x41,
+ 0x7b, 0xd9, 0x7c, 0x5d, 0x78, 0xd9, 0x7c, 0xa0, 0xd9, 0x6c, 0x41, 0x75, 0x50, 0x11, 0x7a, 0x50,
+ 0x80, 0x90, 0xd9, 0x84, 0xc1, 0x8a, 0xcd, 0x8a, 0xc1, 0x8a, 0xb0, 0xe1, 0x82, 0x80, 0x99, 0x85,
+ 0xe1, 0x7e, 0x8d, 0x87, 0xf1, 0x7c, 0x00, 0x80, 0xb1, 0x82, 0x23, 0x05, 0x80, 0x05, 0x80, 0x05,
+ 0x83, 0x51, 0x7d, 0xfd, 0x7f, 0xfd, 0x7f, 0x65, 0x85, 0x3d, 0x7b, 0xb1, 0x49, 0x81, 0xb9, 0x7e,
+ 0xfd, 0x82, 0x05, 0x7e, 0xcd, 0x84, 0x05, 0x7e, 0xbd, 0x83, 0x80, 0xc9, 0x86, 0x09, 0x83, 0xc9,
+ 0x86, 0xc1, 0x86, 0x90, 0xf5, 0x7c, 0xc1, 0x86, 0x39, 0x79, 0xc1, 0x86, 0xb0, 0x31, 0x7e, 0x80,
+ 0x7d, 0x7c, 0x4d, 0x7f, 0x21, 0x7b, 0xf5, 0x7d, 0x22, 0xbd, 0x7d, 0xfd, 0x7d, 0xfd, 0x7c, 0xb1,
+ 0x82, 0x89, 0x82, 0x3d, 0x82, 0xb0, 0x0d, 0x82, 0x09, 0x82, 0xc1, 0x84, 0x25, 0x83, 0xa1, 0x87,
+ 0x25, 0x83, 0xa0, 0x29, 0x93, 0xc1, 0x8a, 0xb0, 0xf1, 0x85, 0xb0, 0x80, 0x90, 0x29, 0x7b, 0x41,
+ 0x75, 0x35, 0x75, 0x41, 0x75, 0xe1,
+}
+
+var PlacesBeachAccess = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x41, 0x82, 0x1d, 0x85,
+ 0x22, 0xdd, 0x82, 0x25, 0x7d, 0xe9, 0x8c, 0xe5, 0x8c, 0x25, 0x7d, 0xdd, 0x82, 0xe3, 0x99, 0x88,
+ 0x8d, 0x74, 0x20, 0xbd, 0x85, 0x45, 0x7a, 0xb1, 0x19, 0x78, 0x19, 0x78, 0x4d, 0x6b, 0x19, 0x78,
+ 0x65, 0x63, 0xfd, 0x7f, 0xe1, 0x87, 0x65, 0x7d, 0xa1, 0x90, 0x7d, 0x7f, 0xe1, 0x96, 0xc1, 0x85,
+ 0xe3, 0x11, 0x69, 0x4d, 0x7a, 0xb0, 0x21, 0x78, 0xe9, 0x87, 0x21, 0x78, 0xb5, 0x94, 0x05, 0x80,
+ 0x9d, 0x9c, 0x20, 0xbd, 0x85, 0x45, 0x7a, 0xb0, 0xbd, 0x79, 0xc1, 0x79, 0xa5, 0x77, 0x62, 0x41,
+ 0x7a, 0x21, 0x69, 0xe3, 0x0d, 0x80, 0xf9, 0x7f, 0xb2, 0xfd, 0x7f, 0x05, 0x80, 0xfd, 0x7f, 0x05,
+ 0x80, 0xfd, 0x7f, 0x05, 0x80, 0xfd, 0x7f, 0x05, 0x80, 0xfd, 0x7f, 0x05, 0x80, 0xfd, 0x7f, 0x05,
+ 0x80, 0x41, 0x7f, 0x05, 0x86, 0x59, 0x82, 0xc5, 0x8d, 0x9d, 0x88, 0x09, 0x94, 0x20, 0x75, 0x8b,
+ 0x8d, 0x74, 0xb0, 0xb9, 0x79, 0xbd, 0x79, 0xfd, 0x71, 0xa5, 0x76, 0xf5, 0x6b, 0x69, 0x77, 0xe1,
+}
+
+var PlacesBusinessCenter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x90, 0xe9, 0x7c,
+ 0xe6, 0x05, 0x6e, 0x00, 0x5c, 0x9c, 0xb0, 0x80, 0x39, 0x82, 0xc9, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0xb0, 0x39, 0x82, 0x80, 0x88, 0x39, 0x7e, 0x88, 0x78, 0xe9, 0x70, 0xe6, 0x88, 0xe9, 0x84,
+ 0xe7, 0x70, 0xe3, 0xa8, 0x5c, 0xe7, 0xfd, 0x77, 0xe9, 0x78, 0x20, 0x78, 0x78, 0xe7, 0x70, 0x00,
+ 0x70, 0x64, 0xe9, 0x88, 0xe6, 0x05, 0x70, 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88,
+ 0xe9, 0x8c, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x98, 0xe9, 0x78, 0xe7,
+ 0x90, 0xe9, 0x88, 0xe7, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x74,
+ 0xb0, 0xfd, 0x7f, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0xfd, 0x7b, 0x78, 0xe3, 0xfd, 0x73, 0x80, 0xe7,
+ 0x70, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x88, 0xe1,
+}
+
+var PlacesCasino = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x6e, 0x98, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x80, 0x5c, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e,
+ 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86,
+ 0x7a, 0x86, 0xe3, 0x92, 0x92, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59,
+ 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x92,
+ 0x92, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a,
+ 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0x80, 0x5c, 0xb0, 0x59, 0x7e,
+ 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81, 0x86,
+ 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var PlacesChildCare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xe5, 0x95, 0x55, 0x81,
+ 0xb0, 0x11, 0x80, 0x91, 0x7f, 0x1d, 0x80, 0x21, 0x7f, 0x1d, 0x80, 0xad, 0x7e, 0x90, 0xf5, 0x7f,
+ 0x19, 0x7f, 0xe5, 0x7f, 0xad, 0x7e, 0xb1, 0x7d, 0x7f, 0xfd, 0x7c, 0x49, 0x7d, 0x89, 0x7a, 0x65,
+ 0x7a, 0xa9, 0x79, 0xf5, 0x7e, 0xc5, 0x7d, 0x71, 0x7d, 0xcd, 0x7b, 0x9d, 0x7b, 0x31, 0x7a, 0xa0,
+ 0xb9, 0x88, 0xb5, 0x6f, 0x91, 0x84, 0x5c, 0x80, 0x5c, 0x90, 0x49, 0x77, 0xb5, 0x81, 0x1d, 0x74,
+ 0x85, 0x84, 0xb2, 0x2d, 0x7e, 0xa1, 0x81, 0xad, 0x7c, 0x99, 0x83, 0x9d, 0x7b, 0xd1, 0x85, 0x1d,
+ 0x7d, 0xdd, 0x80, 0xe9, 0x7a, 0x51, 0x83, 0x65, 0x7a, 0x59, 0x86, 0xf1, 0x7f, 0x71, 0x80, 0xe5,
+ 0x7f, 0xe1, 0x80, 0xe5, 0x7f, 0x55, 0x81, 0x90, 0x0d, 0x80, 0xe9, 0x80, 0x1d, 0x80, 0x55, 0x81,
+ 0xb1, 0x85, 0x80, 0x05, 0x83, 0xb9, 0x82, 0x79, 0x85, 0x9d, 0x85, 0x59, 0x86, 0x0d, 0x81, 0x35,
+ 0x82, 0x89, 0x82, 0x2d, 0x84, 0x59, 0x84, 0xc9, 0x85, 0xa0, 0x3d, 0x77, 0x45, 0x90, 0x69, 0x7b,
+ 0xa4, 0x80, 0xa4, 0x90, 0xc5, 0x88, 0x45, 0x7e, 0xf1, 0x8b, 0x75, 0x7b, 0xb1, 0xd1, 0x81, 0x65,
+ 0x7e, 0x4d, 0x83, 0x71, 0x7c, 0x59, 0x84, 0x39, 0x7a, 0xe5, 0x82, 0x21, 0x7f, 0x19, 0x85, 0xb1,
+ 0x7c, 0x9d, 0x85, 0xa9, 0x79, 0xe2, 0x9c, 0x88, 0xb1, 0xcd, 0x7f, 0x80, 0x9d, 0x7f, 0xf9, 0x7f,
+ 0x71, 0x7f, 0xf1, 0x7f, 0x9d, 0x7f, 0x55, 0x81, 0x05, 0x7f, 0x95, 0x82, 0x49, 0x7e, 0xb9, 0x83,
+ 0xa0, 0x35, 0x89, 0x79, 0x8b, 0xe5, 0x84, 0x9c, 0x80, 0x9c, 0x90, 0xcd, 0x76, 0x79, 0x7d, 0x4d,
+ 0x74, 0xa9, 0x79, 0xb2, 0x41, 0x7f, 0xdd, 0x7e, 0xad, 0x7e, 0x9d, 0x7d, 0x49, 0x7e, 0x49, 0x7c,
+ 0xd1, 0x7f, 0x09, 0x80, 0xa1, 0x7f, 0x11, 0x80, 0x6d, 0x7f, 0x11, 0x80, 0xcd, 0x7d, 0x80, 0x78,
+ 0x35, 0x7e, 0x78, 0x78, 0x90, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xb1, 0x35, 0x80, 0x80, 0x65, 0x80,
+ 0x09, 0x80, 0x91, 0x80, 0x11, 0x80, 0x65, 0x80, 0xad, 0x7e, 0xfd, 0x80, 0x6d, 0x7d, 0xb9, 0x81,
+ 0x49, 0x7c, 0xa0, 0xcd, 0x76, 0x89, 0x74, 0x1d, 0x7b, 0x64, 0x80, 0x64, 0x90, 0x35, 0x89, 0x89,
+ 0x82, 0xb5, 0x8b, 0x59, 0x86, 0xb2, 0xc1, 0x80, 0x25, 0x81, 0x55, 0x81, 0x65, 0x82, 0xb9, 0x81,
+ 0xb9, 0x83, 0x31, 0x80, 0xf9, 0x7f, 0x61, 0x80, 0xf1, 0x7f, 0x95, 0x80, 0xf1, 0x7f, 0x35, 0x82,
+ 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x90, 0x35, 0x7e, 0x88, 0x78, 0x88, 0xe3, 0x52, 0x80, 0xb0,
+ 0x85, 0x81, 0x89, 0x83, 0xf9, 0x84, 0x8c, 0x92, 0x8c, 0x90, 0x7d, 0x87, 0x89, 0x7d, 0x92, 0x74,
+ 0xe6, 0x6e, 0xe2, 0x81, 0x82, 0x7a, 0xd1, 0x81, 0x82, 0x81, 0x82, 0x00, 0x04, 0x8a, 0x80, 0x81,
+ 0x82, 0x81, 0x82, 0x00, 0x04, 0x76, 0x80, 0xe2, 0x81, 0x78, 0x7a, 0xd1, 0x81, 0x82, 0x81, 0x82,
+ 0x00, 0x04, 0x8a, 0x80, 0x81, 0x82, 0x81, 0x82, 0x00, 0x04, 0x76, 0x80, 0xe1,
+}
+
+var PlacesChildFriendly = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x84, 0x58, 0xe9, 0xa0,
+ 0xe7, 0xa0, 0xb0, 0x80, 0x29, 0x77, 0xd9, 0x78, 0x60, 0x60, 0x60, 0xe3, 0xa9, 0x8c, 0xc9, 0x9b,
+ 0xa0, 0xbd, 0x90, 0x11, 0x85, 0xa4, 0xb1, 0x81, 0xa4, 0x7c, 0xe6, 0xe1, 0x74, 0x20, 0x19, 0x7e,
+ 0x78, 0xe6, 0x58, 0xe9, 0x88, 0xe7, 0x75, 0x84, 0x90, 0xc9, 0x83, 0x25, 0x88, 0x3d, 0x84, 0xd5,
+ 0x88, 0xa0, 0x7d, 0x72, 0x05, 0x88, 0x62, 0x55, 0x8a, 0x62, 0x9a, 0xb1, 0x80, 0xe1, 0x83, 0x21,
+ 0x83, 0x8e, 0x8e, 0x8e, 0x89, 0x83, 0x80, 0x71, 0x86, 0x65, 0x7d, 0xed, 0x86, 0x74, 0xe7, 0x29,
+ 0x84, 0xb2, 0x7d, 0x80, 0x65, 0x83, 0x65, 0x83, 0x8c, 0xed, 0x86, 0x8c, 0xe1, 0x83, 0x80, 0x8e,
+ 0xe1, 0x7c, 0x8e, 0x72, 0x80, 0xed, 0x7d, 0x19, 0x7f, 0x11, 0x7c, 0xa9, 0x7d, 0xc9, 0x7a, 0xe2,
+ 0x70, 0xa0, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe3, 0xa4, 0x80, 0xb0, 0x59,
+ 0x7e, 0x80, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x92, 0x59, 0x81, 0x7a, 0x86, 0x7a, 0x86, 0x59, 0x81,
+ 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x86, 0xe1,
+}
+
+var PlacesFitnessCenter = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x25, 0x91, 0xb5, 0x85,
+ 0x05, 0xa8, 0xdd, 0x82, 0x25, 0x91, 0x80, 0x94, 0x25, 0x87, 0xdd, 0x78, 0x6c, 0x80, 0xdd, 0x6e,
+ 0x25, 0x7d, 0x58, 0x20, 0x29, 0x7d, 0xdd, 0x82, 0x00, 0x71, 0x77, 0x58, 0x23, 0xb5, 0x7b, 0x4d,
+ 0x84, 0x29, 0x7d, 0x25, 0x7d, 0x25, 0x7d, 0xdd, 0x82, 0xdd, 0x82, 0xd9, 0x82, 0x00, 0x58, 0x71,
+ 0x77, 0x20, 0xdd, 0x82, 0xdd, 0x82, 0x05, 0x58, 0x25, 0x7d, 0xdd, 0x6e, 0x80, 0x6c, 0xdd, 0x78,
+ 0x25, 0x87, 0x94, 0x80, 0x25, 0x91, 0xdd, 0x82, 0xa8, 0x20, 0xd9, 0x82, 0x25, 0x7d, 0x00, 0x91,
+ 0x88, 0xa8, 0x23, 0x4d, 0x84, 0xb5, 0x7b, 0xd9, 0x82, 0xdd, 0x82, 0xdd, 0x82, 0x25, 0x7d, 0x25,
+ 0x7d, 0x29, 0x7d, 0x00, 0xa8, 0x91, 0x88, 0xe1,
+}
+
+var PlacesFreeBreakfast = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x5c, 0xe6, 0x60,
+ 0xe9, 0xa8, 0xb0, 0x80, 0x6d, 0x84, 0x95, 0x83, 0x90, 0x90, 0x90, 0xe7, 0x98, 0xb0, 0x6d, 0x84,
+ 0x80, 0x90, 0x6d, 0x7c, 0x90, 0x70, 0xe9, 0x74, 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35,
+ 0x7e, 0x88, 0x78, 0xe9, 0x74, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x80,
+ 0x94, 0xe7, 0x78, 0xe9, 0x74, 0xe7, 0x88, 0xe9, 0x8c, 0xe2, 0x60, 0x9c, 0xe7, 0xc0, 0xe9, 0x88,
+ 0xe6, 0x60, 0xe1,
+}
+
+var PlacesGolfCourse = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9e, 0x98, 0xb0, 0x59,
+ 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e,
+ 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0xe3, 0x76, 0xd9, 0x67, 0x00, 0x74, 0x58, 0xe9, 0xc8,
+ 0xe7, 0x78, 0xe9, 0x8d, 0x7c, 0xb1, 0x6d, 0x7c, 0xb1, 0x80, 0x74, 0xfd, 0x81, 0x74, 0x75, 0x83,
+ 0x80, 0x35, 0x82, 0x61, 0x85, 0x88, 0x98, 0x88, 0x90, 0x98, 0x35, 0x7e, 0x98, 0x78, 0xb0, 0x80,
+ 0x05, 0x7e, 0xad, 0x7b, 0x61, 0x7c, 0x6c, 0x11, 0x7c, 0xe9, 0xe9, 0x6d, 0x20, 0x98, 0xe5, 0x79,
+ 0xe1,
+}
+
+var PlacesHotTub = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x4d, 0x7e, 0x80, 0xb0,
+ 0x65, 0x7f, 0x8d, 0x7f, 0xd5, 0x7e, 0x11, 0x7f, 0x5d, 0x7e, 0x91, 0x7e, 0x20, 0x35, 0x7d, 0xe9,
+ 0x7c, 0xb1, 0xa1, 0x7f, 0x95, 0x7f, 0x25, 0x7f, 0x3d, 0x7f, 0xa1, 0x7e, 0x7e, 0x6d, 0x7f, 0xb9,
+ 0x7f, 0xc5, 0x7e, 0x8d, 0x7f, 0x15, 0x7e, 0x8d, 0x7f, 0xe7, 0xf1, 0x7f, 0xb0, 0x85, 0x7d, 0x80,
+ 0x81, 0x7b, 0x05, 0x82, 0x81, 0x7b, 0x81, 0x84, 0xe8, 0x80, 0xe6, 0x58, 0xe9, 0xa0, 0xb0, 0x80,
+ 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e,
+ 0x88, 0x78, 0xe8, 0x80, 0xe6, 0x4d, 0x7e, 0xe2, 0x6c, 0xa0, 0xe7, 0x78, 0xe8, 0x88, 0xe7, 0x88,
+ 0xe9, 0x98, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe8, 0x88, 0xe7, 0x88, 0xe9, 0x98, 0xe3, 0x90, 0x80,
+ 0xe7, 0x78, 0xe8, 0x88, 0xe7, 0x88, 0xe9, 0x98, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe8, 0x88, 0xe7,
+ 0x88, 0xe9, 0x98, 0xe3, 0x51, 0x7f, 0xb5, 0x63, 0x20, 0xdd, 0x7f, 0xe1, 0x7f, 0xb0, 0xd9, 0x7e,
+ 0xc5, 0x7e, 0x61, 0x7e, 0x31, 0x7d, 0xa9, 0x7e, 0x9d, 0x7b, 0x00, 0x98, 0x5c, 0xe7, 0x39, 0x7c,
+ 0x20, 0xe1, 0x7f, 0xd9, 0x80, 0xb0, 0x99, 0x7f, 0xb9, 0x82, 0x8d, 0x80, 0x71, 0x85, 0x99, 0x82,
+ 0x71, 0x87, 0x20, 0x21, 0x80, 0x21, 0x80, 0xb0, 0x29, 0x81, 0x3d, 0x81, 0xa5, 0x81, 0xd5, 0x82,
+ 0x59, 0x81, 0x69, 0x84, 0x20, 0xc9, 0x7f, 0x35, 0x81, 0xe7, 0xd5, 0x83, 0x20, 0x21, 0x80, 0x29,
+ 0x7f, 0xb0, 0x69, 0x80, 0x49, 0x7d, 0x75, 0x7f, 0x91, 0x7a, 0x69, 0x7d, 0x91, 0x78, 0xe3, 0x70,
+ 0x80, 0x20, 0xdd, 0x7f, 0xe1, 0x7f, 0xb0, 0xd9, 0x7e, 0xc5, 0x7e, 0x61, 0x7e, 0x31, 0x7d, 0xa9,
+ 0x7e, 0x9d, 0x7b, 0x00, 0x88, 0x5c, 0xe7, 0x39, 0x7c, 0x20, 0xe1, 0x7f, 0xd9, 0x80, 0xb0, 0x99,
+ 0x7f, 0xb9, 0x82, 0x8d, 0x80, 0x71, 0x85, 0x99, 0x82, 0x71, 0x87, 0x20, 0x21, 0x80, 0x21, 0x80,
+ 0xb0, 0x29, 0x81, 0x3d, 0x81, 0xa5, 0x81, 0xd5, 0x82, 0x59, 0x81, 0x69, 0x84, 0x20, 0xc9, 0x7f,
+ 0x35, 0x81, 0xe7, 0xd5, 0x83, 0x20, 0x21, 0x80, 0x29, 0x7f, 0xb0, 0x69, 0x80, 0x49, 0x7d, 0x75,
+ 0x7f, 0x91, 0x7a, 0x69, 0x7d, 0x91, 0x78, 0xe2, 0x64, 0x68, 0xd1, 0x88, 0x88, 0x00, 0x04, 0x90,
+ 0x80, 0x88, 0x88, 0x00, 0x04, 0x70, 0x80, 0xe1,
+}
+
+var PlacesKitchen = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x05, 0x6c, 0x00,
+ 0x68, 0x58, 0xa0, 0xcd, 0x71, 0x58, 0x60, 0xcd, 0x6d, 0x60, 0x60, 0xe9, 0xc0, 0xb0, 0x80, 0x35,
+ 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe8, 0x60, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x05, 0x7c, 0x78, 0x05, 0x7c, 0xe2, 0x98,
+ 0xa0, 0xe6, 0x68, 0xe8, 0xf5, 0x7d, 0xe7, 0xb0, 0xe8, 0xa0, 0xe3, 0x80, 0x54, 0xe6, 0x68, 0xe8,
+ 0x60, 0xe7, 0xb0, 0xe9, 0x94, 0xe3, 0x58, 0x70, 0xe7, 0x88, 0xe9, 0x8c, 0xe7, 0x78, 0xe3, 0x80,
+ 0x9c, 0xe7, 0x88, 0xe9, 0x94, 0xe7, 0x78, 0xe1,
+}
+
+var PlacesPool = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0xa4, 0xb3, 0xc9,
+ 0x7d, 0x80, 0x8d, 0x7c, 0x45, 0x7f, 0xa5, 0x7b, 0xb5, 0x7e, 0x45, 0x7f, 0x91, 0x7f, 0xd1, 0x7e,
+ 0x4d, 0x7f, 0xb5, 0x7d, 0x4d, 0x7f, 0xe5, 0x7e, 0x80, 0x71, 0x7e, 0x45, 0x80, 0xb5, 0x7d, 0xb5,
+ 0x80, 0x19, 0x7f, 0x8d, 0x80, 0xdd, 0x7d, 0x4d, 0x81, 0xa5, 0x7b, 0x4d, 0x81, 0x90, 0x8d, 0x7c,
+ 0x45, 0x7f, 0xa5, 0x7b, 0xb5, 0x7e, 0xb4, 0x45, 0x7f, 0x8d, 0x7f, 0xd1, 0x7e, 0x4d, 0x7f, 0xb5,
+ 0x7d, 0x4d, 0x7f, 0xe5, 0x7e, 0x80, 0x71, 0x7e, 0x45, 0x80, 0xb5, 0x7d, 0xb5, 0x80, 0x15, 0x7f,
+ 0x8d, 0x80, 0xd9, 0x7d, 0x4d, 0x81, 0xa1, 0x7b, 0x4d, 0x81, 0xc9, 0x7d, 0x80, 0x8d, 0x7c, 0x45,
+ 0x7f, 0xa5, 0x7b, 0xb9, 0x7e, 0x45, 0x7f, 0x8d, 0x7f, 0xd1, 0x7e, 0x49, 0x7f, 0xb1, 0x7d, 0x49,
+ 0x7f, 0x90, 0x71, 0x7e, 0x45, 0x80, 0xb1, 0x7d, 0xb9, 0x80, 0xa0, 0x75, 0x6f, 0x45, 0x91, 0x39,
+ 0x6e, 0xa4, 0x58, 0xa4, 0xe9, 0x78, 0xb1, 0x1d, 0x81, 0x80, 0x91, 0x81, 0xbd, 0x7f, 0x51, 0x82,
+ 0x49, 0x7f, 0xe9, 0x80, 0x75, 0x7f, 0x25, 0x82, 0xb9, 0x7e, 0x5d, 0x84, 0xb9, 0x7e, 0x90, 0x75,
+ 0x83, 0xbd, 0x80, 0x5d, 0x84, 0x49, 0x81, 0xb2, 0xbd, 0x80, 0x75, 0x80, 0x31, 0x81, 0xb9, 0x80,
+ 0x51, 0x82, 0xb9, 0x80, 0x1d, 0x81, 0x80, 0x91, 0x81, 0xbd, 0x7f, 0x4d, 0x82, 0x4d, 0x7f, 0xe9,
+ 0x80, 0x75, 0x7f, 0x25, 0x82, 0xb5, 0x7e, 0x5d, 0x84, 0xb5, 0x7e, 0x90, 0x75, 0x83, 0xbd, 0x80,
+ 0x5d, 0x84, 0x4d, 0x81, 0xb0, 0xbd, 0x80, 0x75, 0x80, 0x31, 0x81, 0xb5, 0x80, 0x4d, 0x82, 0xb5,
+ 0x80, 0x90, 0x91, 0x81, 0xbd, 0x7f, 0x4d, 0x82, 0x4d, 0x7f, 0xb0, 0xe9, 0x80, 0x75, 0x7f, 0x25,
+ 0x82, 0xb5, 0x7e, 0x5d, 0x84, 0xb5, 0x7e, 0x90, 0x75, 0x83, 0xbd, 0x80, 0x5d, 0x84, 0x4d, 0x81,
+ 0xb0, 0xc5, 0x80, 0x71, 0x80, 0x35, 0x81, 0xb5, 0x80, 0x51, 0x82, 0xb5, 0x80, 0xe9, 0x88, 0xe3,
+ 0x80, 0x6e, 0xb3, 0xc9, 0x7d, 0x80, 0x8d, 0x7c, 0x45, 0x7f, 0xa5, 0x7b, 0xb5, 0x7e, 0x45, 0x7f,
+ 0x91, 0x7f, 0xd1, 0x7e, 0x4d, 0x7f, 0xb5, 0x7d, 0x4d, 0x7f, 0xe5, 0x7e, 0x80, 0x71, 0x7e, 0x45,
+ 0x80, 0xb5, 0x7d, 0xb5, 0x80, 0x19, 0x7f, 0x8d, 0x80, 0xdd, 0x7d, 0x4d, 0x81, 0xa5, 0x7b, 0x4d,
+ 0x81, 0x90, 0x8d, 0x7c, 0x45, 0x7f, 0xa5, 0x7b, 0xb5, 0x7e, 0xb4, 0x45, 0x7f, 0x8d, 0x7f, 0xd1,
+ 0x7e, 0x4d, 0x7f, 0xb5, 0x7d, 0x4d, 0x7f, 0xe5, 0x7e, 0x80, 0x71, 0x7e, 0x45, 0x80, 0xb5, 0x7d,
+ 0xb5, 0x80, 0x15, 0x7f, 0x8d, 0x80, 0xd9, 0x7d, 0x4d, 0x81, 0xa1, 0x7b, 0x4d, 0x81, 0xc9, 0x7d,
+ 0x80, 0x8d, 0x7c, 0x45, 0x7f, 0xa5, 0x7b, 0xb9, 0x7e, 0x45, 0x7f, 0x8d, 0x7f, 0xd1, 0x7e, 0x49,
+ 0x7f, 0xb1, 0x7d, 0x49, 0x7f, 0x90, 0x71, 0x7e, 0x45, 0x80, 0xb1, 0x7d, 0xb9, 0x80, 0xa0, 0x75,
+ 0x6f, 0x45, 0x88, 0x39, 0x6e, 0x92, 0x58, 0x92, 0xe9, 0x78, 0xb1, 0x1d, 0x81, 0x80, 0x91, 0x81,
+ 0xbd, 0x7f, 0x51, 0x82, 0x49, 0x7f, 0xe9, 0x80, 0x75, 0x7f, 0x25, 0x82, 0xb9, 0x7e, 0x5d, 0x84,
+ 0xb9, 0x7e, 0x90, 0x75, 0x83, 0xbd, 0x80, 0x5d, 0x84, 0x49, 0x81, 0xb2, 0xbd, 0x80, 0x75, 0x80,
+ 0x31, 0x81, 0xb9, 0x80, 0x51, 0x82, 0xb9, 0x80, 0x1d, 0x81, 0x80, 0x91, 0x81, 0xbd, 0x7f, 0x4d,
+ 0x82, 0x4d, 0x7f, 0xe9, 0x80, 0x75, 0x7f, 0x25, 0x82, 0xb5, 0x7e, 0x5d, 0x84, 0xb5, 0x7e, 0x90,
+ 0x75, 0x83, 0xbd, 0x80, 0x5d, 0x84, 0x4d, 0x81, 0xb0, 0xbd, 0x80, 0x75, 0x80, 0x31, 0x81, 0xb5,
+ 0x80, 0x4d, 0x82, 0xb5, 0x80, 0x90, 0x91, 0x81, 0xbd, 0x7f, 0x4d, 0x82, 0x4d, 0x7f, 0xb0, 0xe9,
+ 0x80, 0x75, 0x7f, 0x25, 0x82, 0xb5, 0x7e, 0x5d, 0x84, 0xb5, 0x7e, 0x90, 0x75, 0x83, 0xbd, 0x80,
+ 0x5d, 0x84, 0x4d, 0x81, 0xb0, 0xc5, 0x80, 0x71, 0x80, 0x35, 0x81, 0xb5, 0x80, 0x51, 0x82, 0xb5,
+ 0x80, 0xe9, 0x88, 0xe3, 0x59, 0x65, 0x6e, 0xb1, 0x1d, 0x81, 0x80, 0x91, 0x81, 0xbd, 0x7f, 0x4d,
+ 0x82, 0x4d, 0x7f, 0xe9, 0x80, 0x75, 0x7f, 0x25, 0x82, 0xb5, 0x7e, 0x5d, 0x84, 0xb5, 0x7e, 0x90,
+ 0x75, 0x83, 0xbd, 0x80, 0x5d, 0x84, 0x4d, 0x81, 0xb0, 0xbd, 0x80, 0x75, 0x80, 0x31, 0x81, 0xb5,
+ 0x80, 0x4d, 0x82, 0xb5, 0x80, 0x90, 0x91, 0x81, 0xbd, 0x7f, 0x4d, 0x82, 0x4d, 0x7f, 0xb0, 0x3d,
+ 0x80, 0xd9, 0x7f, 0x85, 0x80, 0xb5, 0x7f, 0xd1, 0x80, 0x8d, 0x7f, 0x00, 0xf5, 0x7c, 0x64, 0xa0,
+ 0xd9, 0x79, 0xe5, 0x6e, 0x6e, 0xfd, 0x6d, 0x64, 0x5c, 0xe9, 0x8a, 0xb0, 0xa5, 0x83, 0xfd, 0x7f,
+ 0xc5, 0x85, 0xc9, 0x80, 0x90, 0x86, 0x21, 0x84, 0x84, 0x81, 0x79, 0x81, 0x86, 0xb1, 0xa1, 0x80,
+ 0x3d, 0x80, 0x21, 0x81, 0x89, 0x80, 0x8d, 0x81, 0xc9, 0x80, 0xbd, 0x80, 0x75, 0x80, 0x31, 0x81,
+ 0xb9, 0x80, 0x4d, 0x82, 0xb9, 0x80, 0xe2, 0x88, 0x66, 0xd1, 0x8a, 0x8a, 0x00, 0x04, 0x94, 0x80,
+ 0x8a, 0x8a, 0x00, 0x04, 0x6c, 0x80, 0xe1,
+}
+
+var PlacesRoomService = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x94, 0xe7, 0xd0,
+ 0xe9, 0x88, 0xe6, 0x58, 0xe3, 0xad, 0x97, 0x95, 0x6d, 0xb1, 0x35, 0x80, 0x85, 0x7f, 0x55, 0x80,
+ 0xfd, 0x7e, 0x55, 0x80, 0x6d, 0x7e, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0x90, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xb0, 0x80, 0x91, 0x80, 0x21, 0x80, 0x19, 0x81, 0x55, 0x80, 0x95, 0x81,
+ 0xa0, 0x81, 0x74, 0x35, 0x79, 0x8d, 0x6e, 0xdd, 0x7f, 0x5c, 0x90, 0xe7, 0xc8, 0xb0, 0x75, 0x7f,
+ 0xdd, 0x77, 0x81, 0x79, 0x35, 0x71, 0xad, 0x71, 0x95, 0x6f, 0xe1,
+}
+
+var PlacesRVHookup = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x94, 0xe8, 0x7c,
+ 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe6, 0x6c, 0xe9, 0x78, 0x21, 0x74, 0x8c,
+ 0x8c, 0x8c, 0xe9, 0x78, 0xe7, 0x90, 0xe9, 0x8c, 0xe6, 0x60, 0xe9, 0x8c, 0xb0, 0x80, 0x35, 0x82,
+ 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7, 0x88, 0xb0, 0x80, 0x51, 0x83, 0xb1, 0x82, 0x8c, 0x8c, 0x8c,
+ 0x90, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0xe7, 0xa0, 0xe9, 0x78, 0xe7, 0x78, 0xe3, 0x5c, 0x8c, 0xb1,
+ 0xe5, 0x7e, 0x80, 0x7c, 0x1d, 0x7f, 0x7c, 0x7c, 0x80, 0xe5, 0x7e, 0xe5, 0x80, 0x7c, 0x84, 0x7c,
+ 0x90, 0x84, 0xe5, 0x80, 0x84, 0x84, 0xb0, 0x80, 0x1d, 0x81, 0x1d, 0x7f, 0x84, 0x7c, 0x84, 0xe3,
+ 0x9c, 0x68, 0xe7, 0x70, 0xe9, 0x74, 0xe7, 0x90, 0xe9, 0x8c, 0xe2, 0x94, 0x58, 0xe9, 0x88, 0xe6,
+ 0x74, 0xe9, 0x88, 0xe7, 0xa0, 0xe9, 0x88, 0x20, 0x8c, 0x74, 0xe1,
+}
+
+var PlacesSmokeFree = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x68, 0x20, 0xfd,
+ 0x8d, 0x9c, 0xe6, 0x58, 0xe9, 0x8c, 0xe7, 0xfd, 0x93, 0x22, 0xfd, 0x8d, 0x9c, 0x85, 0x82, 0x81,
+ 0x7d, 0x3c, 0x3c, 0xe3, 0xca, 0x9c, 0xe7, 0x86, 0xe9, 0x8c, 0xe7, 0x7a, 0xe3, 0x76, 0x80, 0xe7,
+ 0x86, 0xe9, 0x8c, 0xe7, 0x7a, 0xe3, 0xb5, 0x81, 0xc5, 0x6f, 0xb0, 0x3d, 0x81, 0xcd, 0x7e, 0x84,
+ 0x19, 0x7d, 0x84, 0x3d, 0x7b, 0xe7, 0x7a, 0xb0, 0x80, 0x0d, 0x82, 0x59, 0x7e, 0xb5, 0x83, 0x4d,
+ 0x7c, 0xb5, 0x83, 0xe9, 0x86, 0xb0, 0x7d, 0x84, 0x80, 0x90, 0xa9, 0x83, 0x90, 0x25, 0x88, 0xe8,
+ 0x80, 0xe7, 0x86, 0xe9, 0xd5, 0x7b, 0xb0, 0x80, 0x8d, 0x7b, 0x71, 0x7d, 0xb5, 0x77, 0xb5, 0x79,
+ 0xf1, 0x75, 0xe2, 0x8a, 0x65, 0x79, 0xe7, 0x11, 0x83, 0xb0, 0x19, 0x82, 0x80, 0xf1, 0x83, 0x7d,
+ 0x81, 0xf1, 0x83, 0x1d, 0x84, 0xe8, 0x80, 0xe7, 0x86, 0xe9, 0xd1, 0x7c, 0xb0, 0x80, 0x65, 0x7c,
+ 0xcd, 0x7c, 0xb1, 0x79, 0x11, 0x79, 0xb1, 0x79, 0xe6, 0x8a, 0xb0, 0xf5, 0x7d, 0x80, 0x4d, 0x7c,
+ 0x0d, 0x7e, 0x4d, 0x7c, 0x78, 0x80, 0xf5, 0x82, 0x5e, 0x8a, 0x5e, 0xe8, 0x58, 0xb0, 0x51, 0x7c,
+ 0x80, 0x4d, 0x79, 0x86, 0x4d, 0x79, 0xb5, 0x86, 0x90, 0x05, 0x83, 0xb1, 0x86, 0xb5, 0x86, 0xb1,
+ 0x86, 0xe3, 0x8a, 0x75, 0x8e, 0xe8, 0x84, 0xe7, 0x29, 0x7a, 0xe1,
+}
+
+var PlacesSmokingRooms = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x58, 0x90, 0xe7, 0xbc,
+ 0xe9, 0x8c, 0xe6, 0x58, 0xe3, 0xca, 0x80, 0xe7, 0x86, 0xe9, 0x8c, 0xe7, 0x7a, 0xe3, 0x76, 0x80,
+ 0xe7, 0x86, 0xe9, 0x8c, 0xe7, 0x7a, 0xe3, 0xb5, 0x81, 0x75, 0x6f, 0xb0, 0x3d, 0x81, 0xcd, 0x7e,
+ 0x84, 0x19, 0x7d, 0x84, 0x3d, 0x7b, 0xa0, 0xb5, 0x8f, 0x5e, 0xb1, 0x8c, 0x58, 0x92, 0x58, 0xe9,
+ 0x86, 0xb0, 0x0d, 0x82, 0x80, 0xb5, 0x83, 0xa9, 0x81, 0xb5, 0x83, 0xb5, 0x83, 0x90, 0x59, 0x7e,
+ 0xb5, 0x83, 0x4d, 0x7c, 0xb5, 0x83, 0xe9, 0x86, 0xb0, 0x7d, 0x84, 0x80, 0x90, 0xa9, 0x83, 0x90,
+ 0x25, 0x88, 0xe8, 0x8c, 0xe7, 0x86, 0xe9, 0x89, 0x7b, 0xb0, 0x80, 0x8d, 0x7b, 0x71, 0x7d, 0xb5,
+ 0x77, 0xb5, 0x79, 0xf1, 0x75, 0xe3, 0x5d, 0x7a, 0xf1, 0x84, 0xe6, 0x8a, 0xe9, 0x05, 0x80, 0xb0,
+ 0xf5, 0x7d, 0x80, 0x4d, 0x7c, 0x0d, 0x7e, 0x4d, 0x7c, 0x78, 0x90, 0xa9, 0x81, 0x81, 0x7c, 0xb5,
+ 0x83, 0x81, 0x7c, 0xe9, 0x7a, 0xb0, 0x51, 0x7c, 0x80, 0x4d, 0x79, 0x86, 0x4d, 0x79, 0xb5, 0x86,
+ 0x90, 0x86, 0xb5, 0x86, 0xb5, 0x86, 0xb5, 0x86, 0xe9, 0xfd, 0x7f, 0xe7, 0x11, 0x83, 0xb0, 0x19,
+ 0x82, 0x80, 0xf1, 0x83, 0x7d, 0x81, 0xf1, 0x83, 0x1d, 0x84, 0xe8, 0x8c, 0xe7, 0x86, 0xe9, 0xb5,
+ 0x7c, 0xb0, 0x80, 0x69, 0x7c, 0xcd, 0x7c, 0xb1, 0x79, 0x11, 0x79, 0xb1, 0x79, 0xe1,
+}
+
+var PlacesSpa = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x19, 0x79, 0x80, 0xb1,
+ 0xdd, 0x7d, 0x91, 0x7e, 0x81, 0x7b, 0x75, 0x7d, 0xf5, 0x78, 0xc9, 0x7c, 0x8d, 0x82, 0xad, 0x80,
+ 0xe9, 0x84, 0xcd, 0x81, 0x0d, 0x87, 0x39, 0x83, 0xe3, 0xe1, 0x94, 0xc9, 0x7c, 0xb1, 0x6d, 0x7d,
+ 0xb1, 0x80, 0x09, 0x7b, 0xd1, 0x81, 0xdd, 0x78, 0x45, 0x83, 0x29, 0x82, 0x8d, 0x7e, 0x8d, 0x84,
+ 0x69, 0x7d, 0x25, 0x87, 0xbd, 0x7c, 0xe1, 0xc0, 0xfd, 0x86, 0x45, 0x7b, 0xa0, 0xa1, 0x86, 0xb1,
+ 0x75, 0x5d, 0x84, 0x3d, 0x70, 0x21, 0x80, 0x58, 0xb2, 0xb9, 0x7b, 0x49, 0x84, 0x5d, 0x79, 0xb9,
+ 0x89, 0xe9, 0x78, 0x45, 0x8f, 0x91, 0x82, 0x61, 0x81, 0xf1, 0x84, 0x21, 0x83, 0xfd, 0x86, 0x41,
+ 0x85, 0x0d, 0x82, 0xe1, 0x7d, 0x6d, 0x84, 0x1d, 0x7c, 0xfd, 0x86, 0xc1, 0x7a, 0xe3, 0x66, 0x4d,
+ 0x85, 0xb1, 0xb9, 0x7f, 0xcd, 0x7f, 0x69, 0x7f, 0xa1, 0x7f, 0x1d, 0x7f, 0x71, 0x7f, 0x4d, 0x80,
+ 0x31, 0x80, 0x99, 0x80, 0x5d, 0x80, 0xe5, 0x80, 0x91, 0x80, 0xe3, 0xd5, 0x8c, 0x7d, 0x7f, 0xb1,
+ 0xbd, 0x7f, 0x31, 0x80, 0x75, 0x7f, 0x55, 0x80, 0x35, 0x7f, 0x85, 0x80, 0x41, 0x80, 0xd1, 0x7f,
+ 0x89, 0x80, 0xad, 0x7f, 0xcd, 0x80, 0x7d, 0x7f, 0xe2, 0x80, 0xe9, 0x86, 0xa0, 0xb5, 0x7b, 0x59,
+ 0x80, 0x59, 0x74, 0x78, 0x58, 0x78, 0xb2, 0x80, 0xa1, 0x8a, 0xb9, 0x86, 0xa9, 0x93, 0x11, 0x90,
+ 0xf9, 0x96, 0x45, 0x81, 0x75, 0x80, 0x95, 0x82, 0xcd, 0x80, 0xf1, 0x83, 0x09, 0x81, 0x59, 0x81,
+ 0xc5, 0x7f, 0xa9, 0x82, 0x71, 0x7f, 0xf1, 0x83, 0xf9, 0x7e, 0xa0, 0x49, 0x8d, 0xa9, 0x8f, 0xa8,
+ 0xa1, 0x86, 0xa8, 0x78, 0xb0, 0xa9, 0x77, 0x80, 0x4d, 0x70, 0x59, 0x84, 0x58, 0xe9, 0x8a, 0xe1,
+}
+
+var SocialCake = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x68, 0xb1, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0x80, 0x41, 0x7f, 0xcd, 0x7f, 0x8d, 0x7e, 0x71, 0x7f,
+ 0xf1, 0x7d, 0x00, 0x80, 0x50, 0x20, 0x91, 0x7c, 0xf1, 0x85, 0xa0, 0x35, 0x7c, 0x8d, 0x6e, 0x78,
+ 0x41, 0x6f, 0x78, 0x60, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x31, 0x89,
+ 0xf9, 0x93, 0x21, 0xd9, 0x7d, 0xd9, 0x7d, 0xd9, 0x7d, 0x29, 0x82, 0xb0, 0x65, 0x7d, 0x9d, 0x82,
+ 0xd5, 0x78, 0x9d, 0x82, 0x39, 0x76, 0x80, 0x21, 0xd9, 0x7d, 0xd9, 0x7d, 0xd9, 0x7d, 0x29, 0x82,
+ 0xa0, 0x81, 0x75, 0x49, 0x89, 0xc5, 0x73, 0x94, 0xed, 0x71, 0x94, 0xb0, 0x8d, 0x7e, 0x80, 0x35,
+ 0x7d, 0x8d, 0x7f, 0x15, 0x7c, 0xc5, 0x7e, 0xe8, 0xa4, 0xb0, 0x80, 0x19, 0x81, 0xe9, 0x80, 0x84,
+ 0x84, 0x84, 0xe7, 0xc0, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0xc5, 0x76,
+ 0xb1, 0xe1, 0x7e, 0xc5, 0x80, 0x8d, 0x7d, 0x3d, 0x81, 0x15, 0x7c, 0x3d, 0x81, 0x29, 0x7e, 0x80,
+ 0x6d, 0x7c, 0x49, 0x7f, 0x1d, 0x7b, 0xf9, 0x7d, 0xe2, 0x98, 0x74, 0xe6, 0x84, 0xe9, 0x78, 0xe7,
+ 0x78, 0xe9, 0x88, 0xe6, 0x68, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xe9, 0x15,
+ 0x83, 0xa0, 0x5c, 0x3d, 0x85, 0xc5, 0x6f, 0x8e, 0xed, 0x71, 0x8e, 0xb0, 0x0d, 0x81, 0x80, 0x09,
+ 0x82, 0x99, 0x7f, 0xc5, 0x82, 0xd9, 0x7e, 0x21, 0x49, 0x84, 0xbd, 0x7b, 0x45, 0x84, 0x45, 0x84,
+ 0xb0, 0x7d, 0x81, 0x7d, 0x81, 0x11, 0x84, 0x7d, 0x81, 0x8d, 0x85, 0x80, 0x21, 0x49, 0x84, 0xbd,
+ 0x7b, 0x45, 0x84, 0x45, 0x84, 0xb1, 0xbd, 0x80, 0xbd, 0x80, 0xb9, 0x81, 0x29, 0x81, 0xc5, 0x82,
+ 0x29, 0x81, 0x29, 0x82, 0x80, 0xed, 0x83, 0x3d, 0x7e, 0xed, 0x83, 0x15, 0x7c, 0xe8, 0x80, 0xb0,
+ 0xfd, 0x7f, 0xb1, 0x7c, 0x4d, 0x7d, 0x74, 0xfd, 0x79, 0x74, 0xe1,
+}
+
+var SocialDomain = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x6c, 0xe8, 0x5c,
+ 0xe6, 0x58, 0xe9, 0xc8, 0xe7, 0xd0, 0xe8, 0x6c, 0xe6, 0x80, 0xe2, 0x68, 0x9c, 0xe6, 0x60, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88,
+ 0xe3, 0x80, 0x70, 0xe6, 0x60, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe6, 0x60,
+ 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x90, 0xb0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9,
+ 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0xa8, 0xb0, 0xe6, 0x80, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9,
+ 0x78, 0xe7, 0x88, 0xe9, 0x78, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0xa0, 0xe9, 0xa8, 0xe3, 0x78, 0x60,
+ 0xe7, 0x78, 0xe9, 0x88, 0xe7, 0x88, 0xe9, 0x78, 0xe3, 0x80, 0x90, 0xe7, 0x78, 0xe9, 0x88, 0xe7,
+ 0x88, 0xe9, 0x78, 0xe1,
+}
+
+var SocialGroup = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x7c, 0xb0, 0x51,
+ 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d, 0x74, 0x05, 0x7a, 0x74,
+ 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3,
+ 0x60, 0x80, 0xb0, 0x51, 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d,
+ 0x74, 0x05, 0x7a, 0x74, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90, 0xb1, 0x82,
+ 0x8c, 0x8c, 0x8c, 0xe3, 0x80, 0x88, 0xb0, 0x55, 0x7b, 0x80, 0x64, 0x59, 0x82, 0x64, 0x8e, 0xe9,
+ 0x8a, 0xe7, 0xb8, 0xe9, 0x76, 0xb0, 0x80, 0x59, 0x7b, 0xad, 0x76, 0x72, 0x64, 0x72, 0xe3, 0xa0,
+ 0x80, 0xb0, 0x6d, 0x7f, 0x80, 0xc5, 0x7e, 0x0d, 0x80, 0x11, 0x7e, 0x1d, 0x80, 0xa0, 0x65, 0x88,
+ 0xc9, 0x83, 0x94, 0x09, 0x86, 0x94, 0x92, 0xe9, 0x8a, 0xe7, 0x98, 0xe9, 0x76, 0xb0, 0x80, 0x59,
+ 0x7b, 0xad, 0x76, 0x72, 0x64, 0x72, 0xe1,
+}
+
+var SocialGroupAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x70, 0x78, 0xe7, 0x74,
+ 0xe9, 0x74, 0xe6, 0x5c, 0xe9, 0x8c, 0xe6, 0x50, 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x8c, 0xe7, 0x88,
+ 0xe9, 0x74, 0xe7, 0x8c, 0xe9, 0x78, 0xe3, 0xa8, 0x84, 0xb0, 0x51, 0x83, 0x80, 0xfd, 0x85, 0x51,
+ 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d, 0x74, 0x05, 0x7a, 0x74, 0xb1, 0x5d, 0x7f, 0x80, 0xc1,
+ 0x7e, 0x19, 0x80, 0x2d, 0x7e, 0x4d, 0x80, 0x21, 0x81, 0xa1, 0x81, 0xd1, 0x81, 0x99, 0x83, 0xd1,
+ 0x81, 0xb5, 0x85, 0x90, 0x51, 0x7f, 0x19, 0x84, 0x31, 0x7e, 0xb5, 0x85, 0xb0, 0x95, 0x80, 0x31,
+ 0x80, 0x31, 0x81, 0x4d, 0x80, 0xd5, 0x81, 0x4d, 0x80, 0xe3, 0x6c, 0x80, 0xb0, 0x51, 0x83, 0x80,
+ 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d, 0x74, 0x05, 0x7a, 0x74, 0xb0, 0xb1,
+ 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3, 0x3d, 0x8d,
+ 0x51, 0x84, 0xa0, 0xe9, 0x90, 0xc5, 0x83, 0xa4, 0xa5, 0x85, 0xa4, 0x90, 0xe9, 0x88, 0xe7, 0x8c,
+ 0xe9, 0x78, 0xb0, 0x80, 0xed, 0x7c, 0x41, 0x7b, 0x09, 0x7b, 0x3d, 0x77, 0x51, 0x7a, 0xe2, 0x84,
+ 0x84, 0xb0, 0x78, 0x80, 0x68, 0x84, 0x68, 0x8c, 0xe9, 0x88, 0xe7, 0xb0, 0xe9, 0x78, 0xb0, 0x80,
+ 0x78, 0x70, 0x74, 0x68, 0x74, 0xe1,
+}
+
+var SocialLocationCity = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x7c, 0xe8, 0x64,
+ 0x21, 0x74, 0x74, 0x74, 0x8c, 0xe9, 0x88, 0xe6, 0x5c, 0xe9, 0xb8, 0xe7, 0xc8, 0xe8, 0x7c, 0xe6,
+ 0x8c, 0xe2, 0x6c, 0x9c, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7,
+ 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88,
+ 0xe9, 0x88, 0xe3, 0x98, 0xa0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7,
+ 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x98,
+ 0xb0, 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe3, 0x80, 0x70, 0xe7, 0x78, 0xe9, 0x78,
+ 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+var SocialMood = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0,
+ 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2,
+ 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80,
+ 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x8e, 0x5c, 0xb0,
+ 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x64, 0x80, 0xb0, 0xa9, 0x81, 0x80, 0x86,
+ 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0xe3, 0x8e, 0x9a, 0xb0, 0xa9, 0x84, 0x80, 0x9d, 0x88, 0x19, 0x7d, 0x35,
+ 0x8a, 0x72, 0xe6, 0xcd, 0x75, 0xb0, 0x99, 0x81, 0x19, 0x84, 0x8d, 0x85, 0x8e, 0x35, 0x8a, 0x8e,
+ 0xe1,
+}
+
+var SocialMoodBad = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0x81,
+ 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0xa0, 0xb0, 0x29,
+ 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29,
+ 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x8e, 0x5c, 0xb0, 0xa9, 0x81, 0x80, 0x86,
+ 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59,
+ 0x81, 0x86, 0x86, 0x86, 0xe3, 0x64, 0x80, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a,
+ 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86,
+ 0xe3, 0x35, 0x91, 0x9a, 0xb0, 0x69, 0x7e, 0xe9, 0x7b, 0x75, 0x7a, 0x72, 0xcd, 0x75, 0x72, 0x90,
+ 0x65, 0x77, 0xe9, 0x82, 0xcd, 0x75, 0x8e, 0xe1,
+}
+
+var SocialNotifications = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xa8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe7, 0x70, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe3, 0x98, 0x68, 0xe8, 0x7c, 0xb0, 0x80, 0xd9, 0x79, 0xbd, 0x7c, 0xb9, 0x74, 0x6e,
+ 0x5d, 0x73, 0xe8, 0x60, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0xe9, 0x5d, 0x81, 0xb0, 0x45, 0x7a, 0x5d, 0x81, 0x6e, 0x7d, 0x86, 0x6e, 0xa5,
+ 0x8c, 0xe9, 0x94, 0x20, 0x78, 0x88, 0xe9, 0x84, 0xe7, 0xc0, 0xe9, 0x7c, 0x20, 0x78, 0x78, 0xe1,
+}
+
+var SocialNotificationsActive = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x29, 0x77, 0x29, 0x70,
+ 0x00, 0x4d, 0x74, 0x4d, 0x6d, 0xa0, 0x85, 0x6f, 0xf5, 0x70, 0x59, 0x6c, 0x99, 0x76, 0x0d, 0x6c,
+ 0x7a, 0xe7, 0x88, 0xb0, 0x51, 0x80, 0xb5, 0x7a, 0x0d, 0x83, 0x11, 0x76, 0x1d, 0x87, 0x29, 0x73,
+ 0xe2, 0xf5, 0x8f, 0x7a, 0xe7, 0x88, 0xb0, 0xb5, 0x7f, 0x99, 0x79, 0x8d, 0x7c, 0xf5, 0x73, 0xc1,
+ 0x77, 0x4d, 0x70, 0x20, 0x29, 0x7d, 0xd9, 0x82, 0xb0, 0x11, 0x84, 0xe9, 0x82, 0xcd, 0x86, 0x8d,
+ 0x87, 0x19, 0x87, 0xd9, 0x8c, 0xe2, 0x98, 0x7c, 0xb0, 0x80, 0xd9, 0x79, 0xbd, 0x7c, 0xb9, 0x74,
+ 0x6e, 0x5d, 0x73, 0xe8, 0x60, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a,
+ 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x5d, 0x81, 0xb0, 0x45, 0x7a, 0x5d, 0x81, 0x6e, 0x7d, 0x86, 0x6e,
+ 0xa5, 0x8c, 0xe9, 0x94, 0x20, 0x78, 0x88, 0xe9, 0x84, 0xe7, 0xc0, 0xe9, 0x7c, 0x20, 0x78, 0x78,
+ 0xe8, 0x7c, 0xe2, 0x80, 0xa8, 0xb2, 0x49, 0x80, 0x80, 0x8d, 0x80, 0xf9, 0x7f, 0xd1, 0x80, 0xed,
+ 0x7f, 0x4d, 0x81, 0xbd, 0x7f, 0x61, 0x82, 0xd5, 0x7e, 0xe1, 0x82, 0xa5, 0x7d, 0x35, 0x80, 0x85,
+ 0x7f, 0x51, 0x80, 0x7e, 0x51, 0x80, 0x71, 0x7e, 0xe7, 0x70, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81,
+ 0x88, 0x88, 0x88, 0xe1,
+}
+
+var SocialNotificationsNone = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xa8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe7, 0x70, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe3, 0x98, 0x68, 0xe8, 0x7c, 0xb0, 0x80, 0xd9, 0x79, 0xbd, 0x7c, 0xb9, 0x74, 0x6e,
+ 0x5d, 0x73, 0xe8, 0x60, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0xe9, 0x5d, 0x81, 0xb0, 0x45, 0x7a, 0x5d, 0x81, 0x6e, 0x7d, 0x86, 0x6e, 0xa5,
+ 0x8c, 0xe9, 0x94, 0x20, 0x78, 0x88, 0xe9, 0x84, 0xe7, 0xc0, 0xe9, 0x7c, 0x20, 0x78, 0x78, 0xe3,
+ 0x78, 0x84, 0xe6, 0x70, 0xe8, 0x7c, 0xb0, 0x80, 0x09, 0x7b, 0x09, 0x83, 0x6e, 0x90, 0x6e, 0x90,
+ 0x90, 0x09, 0x84, 0x90, 0x92, 0xe9, 0x98, 0xe1,
+}
+
+var SocialNotificationsOff = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x65, 0x8d, 0x00,
+ 0xb1, 0x77, 0x4d, 0x74, 0x20, 0xe1, 0x7a, 0xb5, 0x7a, 0x00, 0x60, 0x8d, 0x71, 0x21, 0x99, 0x85,
+ 0x99, 0x85, 0x05, 0x80, 0x05, 0x80, 0xa0, 0x91, 0x74, 0x25, 0x79, 0x68, 0x7d, 0x7b, 0x68, 0x7c,
+ 0xe9, 0x94, 0x20, 0x78, 0x88, 0xe9, 0x84, 0xe7, 0x75, 0x9b, 0x20, 0x88, 0x88, 0x00, 0xa4, 0x75,
+ 0x8f, 0x20, 0x7c, 0xf1, 0x7d, 0xe2, 0x80, 0xa8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88,
+ 0x78, 0xe7, 0x70, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe3, 0x98, 0x5d, 0x71,
+ 0xe8, 0x7c, 0xb0, 0x80, 0xd9, 0x79, 0xbd, 0x7c, 0xb9, 0x74, 0x6e, 0x5d, 0x73, 0xe8, 0x60, 0xb0,
+ 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0xe9, 0x5d,
+ 0x81, 0xb5, 0xb5, 0x7f, 0x11, 0x80, 0x71, 0x7f, 0x29, 0x80, 0x29, 0x7f, 0x3d, 0x80, 0xcd, 0x7f,
+ 0x11, 0x80, 0x99, 0x7f, 0x25, 0x80, 0x65, 0x7f, 0x39, 0x80, 0x80, 0x80, 0xfd, 0x7f, 0x80, 0xfd,
+ 0x7f, 0x05, 0x80, 0xfd, 0x7f, 0x80, 0xfd, 0x7f, 0x05, 0x80, 0xf9, 0x7f, 0x05, 0x80, 0x8d, 0x7f,
+ 0x31, 0x80, 0x19, 0x7f, 0x65, 0x80, 0xa9, 0x7e, 0xa1, 0x80, 0xfd, 0x7f, 0x80, 0xfd, 0x7f, 0x05,
+ 0x80, 0xf9, 0x7f, 0x05, 0x80, 0x00, 0x98, 0x5d, 0x85, 0xe1,
+}
+
+var SocialNotificationsPaused = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xa8, 0xb0, 0x35,
+ 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe7, 0x70, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88,
+ 0x88, 0x88, 0xe3, 0x98, 0x68, 0xe8, 0x7c, 0xb0, 0x80, 0xd9, 0x79, 0xbd, 0x7c, 0xb9, 0x74, 0x6e,
+ 0x5d, 0x73, 0xe8, 0x60, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x90, 0x7a, 0x59,
+ 0x81, 0x7a, 0x86, 0xe9, 0x5d, 0x81, 0xb0, 0x45, 0x7a, 0x5d, 0x81, 0x6e, 0x7d, 0x86, 0x6e, 0xa5,
+ 0x8c, 0xe9, 0x94, 0x20, 0x78, 0x88, 0xe9, 0x84, 0xe7, 0xc0, 0xe9, 0x7c, 0x20, 0x78, 0x78, 0xe3,
+ 0x72, 0x99, 0x73, 0x20, 0x69, 0x7a, 0xcd, 0x86, 0xe6, 0x8a, 0xe8, 0x8c, 0xe6, 0x76, 0xe9, 0x69,
+ 0x7c, 0x20, 0x99, 0x85, 0x35, 0x79, 0xe6, 0x76, 0xe8, 0x70, 0xe7, 0x94, 0xe9, 0x99, 0x83, 0xe1,
+}
+
+var SocialPages = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x5c, 0x64, 0xe9, 0x98,
+ 0xe7, 0x94, 0x21, 0x7c, 0x70, 0x90, 0x84, 0xe8, 0x5c, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe3, 0x94, 0xa0, 0xe6, 0x5c, 0xe9, 0x98, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0x98, 0xe8, 0x90, 0x21, 0x70, 0x84, 0x84, 0x70, 0xe3, 0xa4, 0x90,
+ 0x20, 0x70, 0x7c, 0xe9, 0x94, 0xe7, 0x98, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78,
+ 0xe8, 0x84, 0xe6, 0x90, 0x20, 0x84, 0x90, 0xe3, 0x88, 0x48, 0xe6, 0x84, 0xe9, 0x94, 0x21, 0x90,
+ 0x7c, 0x7c, 0x90, 0xe7, 0x94, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78,
+ 0xe1,
+}
+
+var SocialPartyMode = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa0, 0x60, 0xe7, 0xa9,
+ 0x79, 0x00, 0x8c, 0x58, 0xe6, 0x74, 0x20, 0x59, 0x7c, 0x88, 0xe6, 0x60, 0xb0, 0xcd, 0x7d, 0x80,
+ 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb0, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe7, 0xc0, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x68, 0xb0, 0x80, 0xcd,
+ 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x60, 0x8c, 0xb0, 0x45, 0x83, 0x80, 0x21, 0x86, 0x99,
+ 0x81, 0xf5, 0x87, 0x88, 0xe6, 0x80, 0xb1, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x80,
+ 0xb5, 0x80, 0x25, 0x80, 0x61, 0x81, 0x61, 0x80, 0x84, 0xe6, 0x35, 0x76, 0xb1, 0xe1, 0x7f, 0x59,
+ 0x7f, 0xcd, 0x7f, 0xb1, 0x7e, 0xcd, 0x7f, 0x7c, 0x80, 0x7d, 0x7a, 0x7d, 0x84, 0x6c, 0x94, 0x6c,
+ 0xe3, 0x80, 0xa8, 0xb0, 0xbd, 0x7c, 0x80, 0xe1, 0x79, 0x6d, 0x7e, 0x0d, 0x78, 0x78, 0xe6, 0x80,
+ 0xb1, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x80, 0x4d, 0x7f, 0xdd, 0x7f, 0xa1, 0x7e,
+ 0xa1, 0x7f, 0x7c, 0xe7, 0x2d, 0x84, 0xb1, 0x21, 0x80, 0xa9, 0x80, 0x35, 0x80, 0x51, 0x81, 0x35,
+ 0x80, 0x84, 0x80, 0x85, 0x85, 0x85, 0x7b, 0x94, 0x6c, 0x94, 0xe1,
+}
+
+var SocialPeople = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x90, 0x7c, 0xb0, 0x51,
+ 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d, 0x74, 0x05, 0x7a, 0x74,
+ 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90, 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xe3,
+ 0x60, 0x80, 0xb0, 0x51, 0x83, 0x80, 0xfd, 0x85, 0x51, 0x7d, 0xfd, 0x85, 0x74, 0x90, 0x55, 0x7d,
+ 0x74, 0x05, 0x7a, 0x74, 0xb0, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90, 0xb1, 0x82,
+ 0x8c, 0x8c, 0x8c, 0xe3, 0x80, 0x88, 0xb0, 0x55, 0x7b, 0x80, 0x64, 0x59, 0x82, 0x64, 0x8e, 0xe9,
+ 0x8a, 0xe7, 0xb8, 0xe9, 0x76, 0xb0, 0x80, 0x59, 0x7b, 0xad, 0x76, 0x72, 0x64, 0x72, 0xe3, 0xa0,
+ 0x80, 0xb0, 0x6d, 0x7f, 0x80, 0xc5, 0x7e, 0x0d, 0x80, 0x11, 0x7e, 0x1d, 0x80, 0xa0, 0x65, 0x88,
+ 0xc9, 0x83, 0x94, 0x09, 0x86, 0x94, 0x92, 0xe9, 0x8a, 0xe7, 0x98, 0xe9, 0x76, 0xb0, 0x80, 0x59,
+ 0x7b, 0xad, 0x76, 0x72, 0x64, 0x72, 0xe1,
+}
+
+var SocialPeopleOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x92, 0x84, 0xb2, 0x99,
+ 0x7d, 0x80, 0xd9, 0x79, 0xad, 0x80, 0x6e, 0x05, 0x82, 0x29, 0x7d, 0xa9, 0x7e, 0x69, 0x79, 0xfd,
+ 0x7d, 0x6e, 0xfd, 0x7d, 0xad, 0x7b, 0x80, 0x66, 0x2d, 0x82, 0x66, 0x81, 0x86, 0xe8, 0x9c, 0xe7,
+ 0xd8, 0xe9, 0x81, 0x7a, 0xb0, 0x80, 0xad, 0x7b, 0x55, 0x77, 0x81, 0x79, 0x66, 0x81, 0x79, 0xe3,
+ 0x70, 0x92, 0xe6, 0x5a, 0xe9, 0x81, 0x7d, 0xb0, 0x80, 0xf1, 0x7e, 0x21, 0x85, 0x81, 0x7c, 0x94,
+ 0x81, 0x7c, 0x90, 0x94, 0x71, 0x82, 0x94, 0x81, 0x83, 0xe8, 0x96, 0xe3, 0xa4, 0x80, 0xe6, 0x88,
+ 0xe9, 0x81, 0x7d, 0xb0, 0x80, 0x19, 0x7f, 0x99, 0x7f, 0x49, 0x7e, 0xf5, 0x7e, 0x91, 0x7d, 0xa0,
+ 0xbd, 0x84, 0x75, 0x85, 0xe5, 0x86, 0x8a, 0x92, 0x8a, 0xb0, 0xe1, 0x84, 0x80, 0x94, 0x71, 0x82,
+ 0x94, 0x81, 0x83, 0xe8, 0x96, 0xe2, 0x6e, 0x80, 0xb0, 0xe1, 0x83, 0x80, 0x8e, 0xdd, 0x7c, 0x8e,
+ 0x72, 0x90, 0xe1, 0x7c, 0x72, 0x72, 0x72, 0xb0, 0x25, 0x7c, 0x80, 0x72, 0x25, 0x83, 0x72, 0x8e,
+ 0x90, 0x25, 0x83, 0x8e, 0x8e, 0x8e, 0xe3, 0x80, 0x6a, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81,
+ 0x88, 0x88, 0x92, 0x35, 0x7e, 0x88, 0x78, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xcd, 0x81, 0x78,
+ 0x88, 0x78, 0xe3, 0xa4, 0x96, 0xb0, 0xe1, 0x83, 0x80, 0x8e, 0xdd, 0x7c, 0x8e, 0x72, 0x90, 0xe1,
+ 0x7c, 0x72, 0x72, 0x72, 0xb0, 0x25, 0x7c, 0x80, 0x72, 0x25, 0x83, 0x72, 0x8e, 0x90, 0x25, 0x83,
+ 0x8e, 0x8e, 0x8e, 0xe3, 0x80, 0x6a, 0xb0, 0x35, 0x82, 0x80, 0x88, 0xcd, 0x81, 0x88, 0x88, 0x92,
+ 0x35, 0x7e, 0x88, 0x78, 0x88, 0x78, 0x35, 0x7e, 0x78, 0x78, 0xcd, 0x81, 0x78, 0x88, 0x78, 0xe1,
+}
+
+var SocialPerson = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x80, 0xb1, 0x6d,
+ 0x84, 0x80, 0x90, 0x69, 0x7c, 0x90, 0x70, 0x80, 0x95, 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0x90,
+ 0x70, 0x95, 0x83, 0x70, 0x90, 0xb0, 0x80, 0x69, 0x84, 0x95, 0x83, 0x90, 0x90, 0x90, 0xe3, 0x80,
+ 0x88, 0xb0, 0xad, 0x7a, 0x80, 0x60, 0xad, 0x82, 0x60, 0x90, 0xe9, 0x88, 0xe7, 0xc0, 0xe9, 0x78,
+ 0xb0, 0x80, 0xad, 0x7a, 0x55, 0x75, 0x70, 0x60, 0x70, 0xe1,
+}
+
+var SocialPersonAdd = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8c, 0x80, 0xb1, 0x6d,
+ 0x84, 0x80, 0x90, 0x69, 0x7c, 0x90, 0x70, 0x80, 0x95, 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0x90,
+ 0x70, 0x95, 0x83, 0x70, 0x90, 0xb0, 0x80, 0x69, 0x84, 0x95, 0x83, 0x90, 0x90, 0x90, 0xe3, 0x5c,
+ 0x78, 0xe9, 0x74, 0xe6, 0x60, 0xe9, 0x8c, 0xe6, 0x54, 0xe9, 0x88, 0xe7, 0x8c, 0xe9, 0x8c, 0xe7,
+ 0x88, 0xe9, 0x74, 0xe7, 0x8c, 0xe9, 0x78, 0xe7, 0x74, 0xe3, 0xa4, 0x90, 0xb0, 0xad, 0x7a, 0x80,
+ 0x60, 0xad, 0x82, 0x60, 0x90, 0xe9, 0x88, 0xe7, 0xc0, 0xe9, 0x78, 0xb0, 0x80, 0xad, 0x7a, 0x55,
+ 0x75, 0x70, 0x60, 0x70, 0xe1,
+}
+
+var SocialPersonOutline = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0xcd, 0x73, 0xb0,
+ 0x51, 0x82, 0x80, 0x35, 0x84, 0xe1, 0x81, 0x35, 0x84, 0x35, 0x84, 0x92, 0x21, 0x7e, 0x35, 0x84,
+ 0xcd, 0x7b, 0x35, 0x84, 0xcd, 0x7b, 0x21, 0x7e, 0xcd, 0x7b, 0xcd, 0x7b, 0xe1, 0x81, 0xcd, 0x7b,
+ 0x35, 0x84, 0xcd, 0x7b, 0xe3, 0x80, 0xa4, 0xb0, 0xf5, 0x85, 0x80, 0x35, 0x8c, 0xe9, 0x82, 0x35,
+ 0x8c, 0x35, 0x84, 0xe9, 0x35, 0x82, 0xe6, 0xcd, 0x73, 0xe8, 0x94, 0xb0, 0x80, 0xb5, 0x7e, 0x41,
+ 0x86, 0xcd, 0x7b, 0x35, 0x8c, 0xcd, 0x7b, 0xe2, 0x80, 0x60, 0xb1, 0x95, 0x7b, 0x80, 0x70, 0x95,
+ 0x83, 0x70, 0x90, 0x80, 0x69, 0x84, 0x95, 0x83, 0x90, 0x90, 0x90, 0x90, 0x90, 0x69, 0x7c, 0x90,
+ 0x70, 0xb0, 0x80, 0x95, 0x7b, 0x6d, 0x7c, 0x70, 0x70, 0x70, 0xe3, 0x80, 0xa4, 0xb0, 0xad, 0x7a,
+ 0x80, 0x60, 0xad, 0x82, 0x60, 0x90, 0xe9, 0x8c, 0xe7, 0xc0, 0xe9, 0x74, 0xb0, 0x80, 0xad, 0x7a,
+ 0x55, 0x75, 0x70, 0x60, 0x70, 0xe1,
+}
+
+var SocialPlusOne = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x78, 0x70, 0xe7, 0x78,
+ 0xe9, 0x90, 0xe6, 0x60, 0xe9, 0x88, 0xe7, 0x90, 0xe9, 0x90, 0xe7, 0x88, 0xe9, 0x70, 0xe7, 0x90,
+ 0xe9, 0x78, 0xe7, 0x70, 0xe3, 0x92, 0x29, 0x7c, 0xe9, 0xa5, 0x83, 0x20, 0x8a, 0x7e, 0xe8, 0x98,
+ 0xe7, 0x88, 0xe8, 0x64, 0xe1,
+}
+
+var SocialPoll = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x74, 0x94, 0xe7, 0x78, 0xe8,
+ 0x78, 0xe7, 0x88, 0xe9, 0x9c, 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe8, 0x6c, 0xe7, 0x88, 0xe9, 0xa8,
+ 0xe3, 0x90, 0x80, 0xe7, 0x78, 0xe9, 0x70, 0xe7, 0x88, 0xe9, 0x90, 0xe1,
+}
+
+var SocialPublic = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x7c, 0xdd, 0xa3, 0xa0, 0x1d, 0x76, 0xe1,
+ 0x8e, 0x60, 0x29, 0x88, 0x60, 0x80, 0xb0, 0x80, 0xc5, 0x7e, 0x29, 0x80, 0x91, 0x7d, 0x6d, 0x80,
+ 0x6d, 0x7c, 0x00, 0x74, 0x8c, 0xe9, 0x84, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88,
+ 0xe9, 0xdd, 0x83, 0xe3, 0xcd, 0x8d, 0xf1, 0x7a, 0xa0, 0x49, 0x8b, 0x2d, 0x89, 0xc9, 0x89, 0x90,
+ 0x90, 0x90, 0xe7, 0x7c, 0xe9, 0x74, 0xb0, 0x80, 0xe9, 0x7e, 0x19, 0x7f, 0x7c, 0x7c, 0x7c, 0xe6,
+ 0x70, 0xe9, 0x78, 0xe7, 0x88, 0xb0, 0x19, 0x81, 0x80, 0x84, 0x19, 0x7f, 0x84, 0x7c, 0xe9, 0x78,
+ 0xe7, 0x88, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe9, 0x2d, 0x7f, 0xb1, 0xdd,
+ 0x85, 0x61, 0x82, 0x94, 0x1d, 0x88, 0x94, 0xd5, 0x8e, 0x80, 0x29, 0x84, 0x69, 0x7e, 0xf1, 0x87,
+ 0xcd, 0x7b, 0xcd, 0x8a, 0xe1,
+}
+
+var SocialSchool = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x64, 0x5d, 0x82, 0xe9,
+ 0x90, 0x00, 0x80, 0xa4, 0x20, 0x9c, 0x5d, 0x78, 0xe9, 0x70, 0x00, 0x80, 0x94, 0x20, 0x64, 0x5d,
+ 0x78, 0xe2, 0x80, 0x5c, 0x00, 0x54, 0x74, 0x21, 0xac, 0x98, 0xa4, 0x31, 0x76, 0xe8, 0x94, 0xe7,
+ 0x88, 0xe8, 0x74, 0x00, 0x80, 0x5c, 0xe1,
+}
+
+var SocialSentimentDissatisfied = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x7c, 0xb0, 0xa9,
+ 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x64, 0x80, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81,
+ 0x86, 0x86, 0x86, 0xe3, 0xfd, 0x86, 0x5c, 0xa0, 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80,
+ 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0, 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80,
+ 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9,
+ 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9,
+ 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x80, 0x68, 0xb0, 0x59, 0x7b, 0x80, 0x5d, 0x77, 0xe9, 0x82, 0xc5,
+ 0x75, 0x8e, 0xe7, 0x59, 0x83, 0xb0, 0x65, 0x81, 0xa1, 0x7d, 0xf1, 0x83, 0x78, 0xe5, 0x86, 0x78,
+ 0x90, 0x81, 0x85, 0xa1, 0x81, 0xe5, 0x86, 0x88, 0xe7, 0x59, 0x83, 0xb0, 0x69, 0x7e, 0xe9, 0x7b,
+ 0x6d, 0x7a, 0x72, 0xc5, 0x75, 0x72, 0xe1,
+}
+
+var SocialSentimentNeutral = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x74, 0x88, 0xe7, 0x98,
+ 0xe9, 0x86, 0xe6, 0x74, 0xe3, 0x9a, 0x68, 0xb0, 0x59, 0x7e, 0x80, 0x7a, 0x59, 0x81, 0x7a, 0x86,
+ 0x92, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a,
+ 0xe3, 0x6a, 0x86, 0xb0, 0x80, 0x59, 0x7e, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x92, 0x7a, 0x59, 0x81,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0xe3, 0xfd, 0x83, 0x62,
+ 0xa0, 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8,
+ 0xa0, 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58,
+ 0xe2, 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60,
+ 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var SocialSentimentSatisfied = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x8e, 0x7c, 0xb0, 0xa9,
+ 0x81, 0x80, 0x86, 0xa9, 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81,
+ 0x7a, 0x86, 0x59, 0x81, 0x86, 0x86, 0x86, 0xe3, 0x64, 0x80, 0xb0, 0xa9, 0x81, 0x80, 0x86, 0xa9,
+ 0x7e, 0x86, 0x7a, 0x92, 0xa9, 0x7e, 0x7a, 0x7a, 0x7a, 0x7a, 0x59, 0x81, 0x7a, 0x86, 0x59, 0x81,
+ 0x86, 0x86, 0x86, 0xe3, 0xfd, 0x86, 0x5c, 0xa0, 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80,
+ 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0xa0, 0x0d, 0x8b, 0xa8, 0xa8, 0x0d, 0x8b, 0xa8, 0x80,
+ 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0xa0, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9,
+ 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9,
+ 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x80, 0x70, 0xb0, 0x0d, 0x7d, 0x80, 0x81, 0x7a, 0x61, 0x7e, 0x1d,
+ 0x79, 0x78, 0xe7, 0xa9, 0x7c, 0xb0, 0x99, 0x81, 0x19, 0x84, 0x95, 0x85, 0x8e, 0x3d, 0x8a, 0x8e,
+ 0x90, 0xa5, 0x88, 0x19, 0x7d, 0x3d, 0x8a, 0x72, 0xe7, 0xa9, 0x7c, 0xb0, 0x9d, 0x7e, 0x61, 0x82,
+ 0x11, 0x7c, 0x88, 0x1d, 0x79, 0x88, 0xe1,
+}
+
+var SocialSentimentVeryDissatisfied = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0x81,
+ 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0xa0, 0xb0, 0x29,
+ 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29,
+ 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x5d, 0x88, 0x85, 0x67, 0x21, 0xe1, 0x7d,
+ 0x21, 0x82, 0xe1, 0x7d, 0xe1, 0x7d, 0x00, 0x84, 0xa5, 0x79, 0x20, 0x21, 0x82, 0x21, 0x82, 0x01,
+ 0x84, 0xe1, 0x7d, 0x21, 0x84, 0x80, 0x20, 0x21, 0x82, 0xe1, 0x7d, 0x00, 0x5d, 0x88, 0x80, 0x22,
+ 0x21, 0x82, 0xe1, 0x7d, 0xe1, 0x7d, 0xe1, 0x7d, 0x21, 0x82, 0xe1, 0x7d, 0xe2, 0xa5, 0x77, 0x80,
+ 0x20, 0x21, 0x82, 0xe1, 0x7d, 0x01, 0xe1, 0x7b, 0x80, 0x7c, 0xe1, 0x7d, 0x20, 0xe1, 0x7d, 0xe1,
+ 0x7d, 0x00, 0x7c, 0xa5, 0x79, 0x25, 0xe1, 0x7d, 0xe1, 0x7d, 0xe1, 0x7d, 0x21, 0x82, 0xe1, 0x7d,
+ 0xe1, 0x7d, 0xe1, 0x7d, 0x21, 0x82, 0x21, 0x82, 0x21, 0x82, 0xe1, 0x7d, 0x21, 0x82, 0xe2, 0x80,
+ 0x88, 0xb0, 0x59, 0x7b, 0x80, 0x61, 0x77, 0xed, 0x82, 0xc9, 0x75, 0x8e, 0xe7, 0x71, 0x94, 0xb0,
+ 0x69, 0x7e, 0xed, 0x7b, 0x71, 0x7a, 0x72, 0xc9, 0x75, 0x72, 0xe1,
+}
+
+var SocialSentimentVerySatisfied = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xfd, 0x7f, 0x58, 0xa0,
+ 0xf1, 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x90, 0xf1, 0x88, 0xa8, 0xfd, 0x93, 0xa8, 0x81,
+ 0xa8, 0x0d, 0x8b, 0xa8, 0x80, 0x0d, 0x8b, 0x58, 0xfd, 0x7f, 0x58, 0xe2, 0x80, 0xa0, 0xb0, 0x29,
+ 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29,
+ 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe3, 0x84, 0xe1, 0x6b, 0x00, 0x21, 0x84, 0x7c,
+ 0x20, 0x21, 0x82, 0xe1, 0x7d, 0x00, 0x5d, 0x88, 0x7c, 0x21, 0x21, 0x82, 0xe1, 0x7d, 0xc1, 0x7b,
+ 0xc5, 0x7b, 0xe3, 0xc5, 0x77, 0x80, 0x01, 0xe1, 0x7b, 0x7c, 0x7c, 0xe1, 0x7b, 0x21, 0xc5, 0x7b,
+ 0xc5, 0x7b, 0xc1, 0x7b, 0x3d, 0x84, 0x00, 0xa5, 0x77, 0x7c, 0xe2, 0x80, 0x96, 0xb0, 0xa9, 0x84,
+ 0x80, 0xa1, 0x88, 0x15, 0x7d, 0x39, 0x8a, 0x72, 0xe6, 0xc9, 0x75, 0xb0, 0x99, 0x81, 0x15, 0x84,
+ 0x91, 0x85, 0x8e, 0x39, 0x8a, 0x8e, 0xe1,
+}
+
+var SocialShare = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x98, 0x2d, 0x88, 0xb0,
+ 0x7d, 0x7e, 0x80, 0x1d, 0x7d, 0x99, 0x80, 0x11, 0x7c, 0x8d, 0x81, 0x00, 0xd1, 0x79, 0x69, 0x81,
+ 0xb0, 0x1d, 0x80, 0x8d, 0x7f, 0x31, 0x80, 0x15, 0x7f, 0x31, 0x80, 0x99, 0x7e, 0x90, 0xf1, 0x7f,
+ 0x0d, 0x7f, 0xd1, 0x7f, 0x99, 0x7e, 0x20, 0x19, 0x8e, 0xc5, 0x77, 0xb1, 0x11, 0x81, 0x82, 0x81,
+ 0x82, 0xa1, 0x81, 0x15, 0x84, 0xa1, 0x81, 0x51, 0x83, 0x80, 0x8c, 0x51, 0x7d, 0x8c, 0x74, 0x91,
+ 0x51, 0x7d, 0x74, 0x74, 0x74, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0xb0, 0x80, 0x7d, 0x80, 0x11, 0x80,
+ 0xf5, 0x80, 0x31, 0x80, 0x69, 0x81, 0x20, 0xe9, 0x71, 0x3d, 0x88, 0xb1, 0xf1, 0x7e, 0x7e, 0x81,
+ 0x7d, 0x61, 0x7e, 0xed, 0x7b, 0x61, 0x7e, 0xb1, 0x7c, 0x80, 0x74, 0xb1, 0x82, 0x74, 0x8c, 0x90,
+ 0xb1, 0x82, 0x8c, 0x8c, 0x8c, 0xb0, 0x95, 0x81, 0x80, 0x05, 0x83, 0x61, 0x7f, 0x15, 0x84, 0x61,
+ 0x7e, 0x20, 0x41, 0x8e, 0x51, 0x88, 0xb1, 0xe9, 0x7f, 0x6d, 0x80, 0xd9, 0x7f, 0xdd, 0x80, 0xd9,
+ 0x7f, 0x51, 0x81, 0x80, 0x39, 0x83, 0x9d, 0x82, 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, 0x91, 0xd5,
+ 0x85, 0x65, 0x7d, 0xd5, 0x85, 0x2d, 0x7a, 0x65, 0x7d, 0x2d, 0x7a, 0x2d, 0x7a, 0x2d, 0x7a, 0xe1,
+}
+
+var SocialWhatsHot = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x86, 0x59, 0x69, 0x90,
+ 0x7d, 0x81, 0x4d, 0x85, 0x7d, 0x81, 0x99, 0x89, 0xb0, 0x80, 0x21, 0x84, 0x4d, 0x7d, 0x79, 0x87,
+ 0x2d, 0x79, 0x79, 0x87, 0x90, 0xc1, 0x78, 0xa9, 0x7c, 0xc1, 0x78, 0x89, 0x78, 0x20, 0x0d, 0x80,
+ 0x49, 0x7f, 0xa0, 0x71, 0x72, 0x09, 0x77, 0x60, 0x3d, 0x7d, 0x60, 0x88, 0xb0, 0x80, 0xd9, 0x88,
+ 0x29, 0x87, 0xa0, 0xa0, 0xa0, 0x90, 0xa0, 0xd9, 0x78, 0xa0, 0x60, 0xb0, 0x80, 0x35, 0x75, 0xd1,
+ 0x7a, 0x99, 0x6b, 0x66, 0x59, 0x65, 0xe2, 0x6d, 0x7f, 0x9c, 0xb1, 0x71, 0x7c, 0x80, 0x8d, 0x79,
+ 0x31, 0x7d, 0x8d, 0x79, 0xb9, 0x79, 0x80, 0xc1, 0x7c, 0x19, 0x82, 0x79, 0x7a, 0xa1, 0x85, 0xc5,
+ 0x79, 0x90, 0x35, 0x87, 0x99, 0x7d, 0x3d, 0x89, 0xd9, 0x7a, 0xb1, 0xc9, 0x80, 0x95, 0x82, 0x31,
+ 0x81, 0x4d, 0x85, 0x31, 0x81, 0x11, 0x88, 0x80, 0x4d, 0x85, 0xb5, 0x7b, 0x99, 0x89, 0x69, 0x76,
+ 0x99, 0x89, 0xe1,
+}
+
+var ToggleCheckBox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe2, 0x78, 0x94, 0x00, 0x64, 0x80,
+ 0x20, 0xd5, 0x82, 0x2d, 0x7d, 0x00, 0x78, 0x59, 0x84, 0x20, 0x2d, 0x8f, 0xd5, 0x70, 0x01, 0x9c,
+ 0x70, 0x78, 0x94, 0xe1,
+}
+
+var ToggleCheckBoxOutlineBlank = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x64, 0xe9, 0xb8,
+ 0xe6, 0x64, 0xe8, 0x64, 0xe7, 0xb8, 0xe3, 0x80, 0x78, 0xe6, 0x64, 0xb0, 0xcd, 0x7d, 0x80, 0x78,
+ 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd, 0x81, 0x88, 0x88, 0x88, 0xe7,
+ 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8, 0x64, 0xb0, 0x80, 0xcd, 0x7d,
+ 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe1,
+}
+
+var ToggleIndeterminateCheckBox = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x9c, 0x5c, 0xe6, 0x64,
+ 0xb0, 0xcd, 0x7d, 0x80, 0x78, 0xcd, 0x81, 0x78, 0x88, 0xe9, 0xb8, 0xb0, 0x80, 0x35, 0x82, 0xcd,
+ 0x81, 0x88, 0x88, 0x88, 0xe7, 0xb8, 0xb0, 0x35, 0x82, 0x80, 0x88, 0x35, 0x7e, 0x88, 0x78, 0xe8,
+ 0x64, 0xb0, 0x80, 0xcd, 0x7d, 0x35, 0x7e, 0x78, 0x78, 0x78, 0xe3, 0x78, 0xa8, 0xe6, 0x6c, 0xe9,
+ 0x78, 0xe7, 0xa8, 0xe9, 0x88, 0xe1,
+}
+
+var ToggleRadioButtonChecked = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x6c, 0xb0, 0x7d,
+ 0x7a, 0x80, 0x6c, 0x7d, 0x84, 0x6c, 0x94, 0x92, 0x7d, 0x84, 0x94, 0x94, 0x94, 0x94, 0x85, 0x7b,
+ 0x94, 0x6c, 0x85, 0x7b, 0x6c, 0x6c, 0x6c, 0xe3, 0x80, 0x6c, 0xa0, 0xf5, 0x74, 0x58, 0x58, 0xf5,
+ 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77, 0xa8, 0x58, 0x80, 0x0d,
+ 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x29, 0x77, 0x80, 0x60, 0xd9, 0x78, 0x60, 0x60,
+ 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0, 0xd9, 0x78, 0xa0, 0x60,
+ 0xa0, 0xe1,
+}
+
+var ToggleRadioButtonUnchecked = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x58, 0xa0, 0xf5,
+ 0x74, 0x58, 0x58, 0xf5, 0x74, 0x58, 0x80, 0x91, 0xf5, 0x88, 0xa8, 0xa8, 0xa8, 0xa8, 0x0d, 0x77,
+ 0xa8, 0x58, 0x80, 0x0d, 0x8b, 0x58, 0x80, 0x58, 0xe3, 0x80, 0xc8, 0xb0, 0x29, 0x77, 0x80, 0x60,
+ 0xd9, 0x78, 0x60, 0x60, 0x80, 0x29, 0x77, 0x60, 0x80, 0x60, 0x91, 0xa0, 0x29, 0x87, 0xa0, 0xa0,
+ 0xd9, 0x78, 0xa0, 0x60, 0xa0, 0xe1,
+}
+
+var ToggleStar = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0x80, 0x8d, 0x8a, 0x00,
+ 0x5d, 0x8c, 0xa4, 0x20, 0xb9, 0x7c, 0xf1, 0x71, 0x00, 0xa8, 0x7d, 0x7a, 0x20, 0xa1, 0x71, 0xc9,
+ 0x7e, 0x02, 0x80, 0x58, 0x61, 0x7a, 0x45, 0x79, 0x58, 0x7d, 0x7a, 0x20, 0xed, 0x8a, 0x75, 0x89,
+ 0x00, 0xa5, 0x73, 0xa4, 0xe1,
+}
+
+var ToggleStarBorder = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x7d, 0x7a, 0x20,
+ 0xa1, 0x71, 0xc5, 0x7e, 0x02, 0x80, 0x58, 0x61, 0x7a, 0x45, 0x79, 0x58, 0x7d, 0x7a, 0x20, 0xed,
+ 0x8a, 0x75, 0x89, 0x02, 0xa5, 0x73, 0xa4, 0x80, 0x8d, 0x8a, 0x5d, 0x8c, 0xa4, 0x20, 0xbd, 0x7c,
+ 0xf1, 0x71, 0x00, 0xa8, 0x7d, 0x7a, 0xe2, 0x80, 0xcd, 0x86, 0x23, 0x7d, 0x78, 0x8d, 0x84, 0x84,
+ 0x71, 0x77, 0x5d, 0x79, 0x3d, 0x7a, 0xc5, 0x88, 0x3d, 0x7f, 0x00, 0x80, 0x35, 0x74, 0x23, 0x6d,
+ 0x83, 0x15, 0x88, 0xc5, 0x88, 0xc5, 0x80, 0x5d, 0x79, 0xc5, 0x85, 0x84, 0x91, 0x88, 0x00, 0x80,
+ 0xcd, 0x86, 0xe1,
+}
+
+var ToggleStarHalf = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xc0, 0xa8, 0x7d, 0x7a, 0x20,
+ 0xa1, 0x71, 0xc5, 0x7e, 0x00, 0x80, 0x58, 0x20, 0x61, 0x7a, 0x45, 0x8d, 0x00, 0x58, 0x7d, 0x7a,
+ 0x20, 0xed, 0x8a, 0x75, 0x89, 0x04, 0xa5, 0x73, 0xa4, 0x80, 0x8d, 0x8a, 0x5d, 0x8c, 0xa4, 0x19,
+ 0x89, 0xf1, 0x83, 0xa8, 0x7d, 0x7a, 0xe2, 0x80, 0xcd, 0x86, 0xe8, 0x35, 0x74, 0x23, 0x6d, 0x83,
+ 0x15, 0x88, 0xc5, 0x88, 0xc5, 0x80, 0x5d, 0x79, 0xc5, 0x85, 0x84, 0x91, 0x88, 0x00, 0x80, 0xcd,
+ 0x86, 0xe1,
+}
+
+// In total, 312887 SVG bytes in 961 files (190840 PNG bytes at 24px * 24px,
+// 318219 PNG bytes at 48px * 48px) converted to 122012 IconVG bytes.
diff --git a/shiny/materialdesign/icons/data_test.go b/shiny/materialdesign/icons/data_test.go
new file mode 100644
index 0000000..f9e3807
--- /dev/null
+++ b/shiny/materialdesign/icons/data_test.go
@@ -0,0 +1,970 @@
+// generated by go run gen.go; DO NOT EDIT
+
+package icons
+
+var list = []struct {
+ name string
+ data []byte
+}{
+ {"Action3DRotation", Action3DRotation},
+ {"ActionAccessibility", ActionAccessibility},
+ {"ActionAccessible", ActionAccessible},
+ {"ActionAccountBalance", ActionAccountBalance},
+ {"ActionAccountBalanceWallet", ActionAccountBalanceWallet},
+ {"ActionAccountBox", ActionAccountBox},
+ {"ActionAccountCircle", ActionAccountCircle},
+ {"ActionAddShoppingCart", ActionAddShoppingCart},
+ {"ActionAlarm", ActionAlarm},
+ {"ActionAlarmAdd", ActionAlarmAdd},
+ {"ActionAlarmOff", ActionAlarmOff},
+ {"ActionAlarmOn", ActionAlarmOn},
+ {"ActionAllOut", ActionAllOut},
+ {"ActionAndroid", ActionAndroid},
+ {"ActionAnnouncement", ActionAnnouncement},
+ {"ActionAspectRatio", ActionAspectRatio},
+ {"ActionAssessment", ActionAssessment},
+ {"ActionAssignment", ActionAssignment},
+ {"ActionAssignmentInd", ActionAssignmentInd},
+ {"ActionAssignmentLate", ActionAssignmentLate},
+ {"ActionAssignmentReturn", ActionAssignmentReturn},
+ {"ActionAssignmentReturned", ActionAssignmentReturned},
+ {"ActionAssignmentTurnedIn", ActionAssignmentTurnedIn},
+ {"ActionAutorenew", ActionAutorenew},
+ {"ActionBackup", ActionBackup},
+ {"ActionBook", ActionBook},
+ {"ActionBookmark", ActionBookmark},
+ {"ActionBookmarkBorder", ActionBookmarkBorder},
+ {"ActionBugReport", ActionBugReport},
+ {"ActionBuild", ActionBuild},
+ {"ActionCached", ActionCached},
+ {"ActionCameraEnhance", ActionCameraEnhance},
+ {"ActionCardGiftcard", ActionCardGiftcard},
+ {"ActionCardMembership", ActionCardMembership},
+ {"ActionCardTravel", ActionCardTravel},
+ {"ActionChangeHistory", ActionChangeHistory},
+ {"ActionCheckCircle", ActionCheckCircle},
+ {"ActionChromeReaderMode", ActionChromeReaderMode},
+ {"ActionClass", ActionClass},
+ {"ActionCode", ActionCode},
+ {"ActionCompareArrows", ActionCompareArrows},
+ {"ActionCopyright", ActionCopyright},
+ {"ActionCreditCard", ActionCreditCard},
+ {"ActionDashboard", ActionDashboard},
+ {"ActionDateRange", ActionDateRange},
+ {"ActionDelete", ActionDelete},
+ {"ActionDeleteForever", ActionDeleteForever},
+ {"ActionDescription", ActionDescription},
+ {"ActionDNS", ActionDNS},
+ {"ActionDone", ActionDone},
+ {"ActionDoneAll", ActionDoneAll},
+ {"ActionDonutLarge", ActionDonutLarge},
+ {"ActionDonutSmall", ActionDonutSmall},
+ {"ActionEject", ActionEject},
+ {"ActionEuroSymbol", ActionEuroSymbol},
+ {"ActionEvent", ActionEvent},
+ {"ActionEventSeat", ActionEventSeat},
+ {"ActionExitToApp", ActionExitToApp},
+ {"ActionExplore", ActionExplore},
+ {"ActionExtension", ActionExtension},
+ {"ActionFace", ActionFace},
+ {"ActionFavorite", ActionFavorite},
+ {"ActionFavoriteBorder", ActionFavoriteBorder},
+ {"ActionFeedback", ActionFeedback},
+ {"ActionFindInPage", ActionFindInPage},
+ {"ActionFindReplace", ActionFindReplace},
+ {"ActionFingerprint", ActionFingerprint},
+ {"ActionFlightLand", ActionFlightLand},
+ {"ActionFlightTakeoff", ActionFlightTakeoff},
+ {"ActionFlipToBack", ActionFlipToBack},
+ {"ActionFlipToFront", ActionFlipToFront},
+ {"ActionGTranslate", ActionGTranslate},
+ {"ActionGavel", ActionGavel},
+ {"ActionGetApp", ActionGetApp},
+ {"ActionGIF", ActionGIF},
+ {"ActionGrade", ActionGrade},
+ {"ActionGroupWork", ActionGroupWork},
+ {"ActionHelp", ActionHelp},
+ {"ActionHelpOutline", ActionHelpOutline},
+ {"ActionHighlightOff", ActionHighlightOff},
+ {"ActionHistory", ActionHistory},
+ {"ActionHome", ActionHome},
+ {"ActionHourglassEmpty", ActionHourglassEmpty},
+ {"ActionHourglassFull", ActionHourglassFull},
+ {"ActionHTTP", ActionHTTP},
+ {"ActionHTTPS", ActionHTTPS},
+ {"ActionImportantDevices", ActionImportantDevices},
+ {"ActionInfo", ActionInfo},
+ {"ActionInfoOutline", ActionInfoOutline},
+ {"ActionInput", ActionInput},
+ {"ActionInvertColors", ActionInvertColors},
+ {"ActionLabel", ActionLabel},
+ {"ActionLabelOutline", ActionLabelOutline},
+ {"ActionLanguage", ActionLanguage},
+ {"ActionLaunch", ActionLaunch},
+ {"ActionLightbulbOutline", ActionLightbulbOutline},
+ {"ActionLineStyle", ActionLineStyle},
+ {"ActionLineWeight", ActionLineWeight},
+ {"ActionList", ActionList},
+ {"ActionLock", ActionLock},
+ {"ActionLockOpen", ActionLockOpen},
+ {"ActionLockOutline", ActionLockOutline},
+ {"ActionLoyalty", ActionLoyalty},
+ {"ActionMarkUnreadMailbox", ActionMarkUnreadMailbox},
+ {"ActionMotorcycle", ActionMotorcycle},
+ {"ActionNoteAdd", ActionNoteAdd},
+ {"ActionOfflinePin", ActionOfflinePin},
+ {"ActionOpacity", ActionOpacity},
+ {"ActionOpenInBrowser", ActionOpenInBrowser},
+ {"ActionOpenInNew", ActionOpenInNew},
+ {"ActionOpenWith", ActionOpenWith},
+ {"ActionPageview", ActionPageview},
+ {"ActionPanTool", ActionPanTool},
+ {"ActionPayment", ActionPayment},
+ {"ActionPermCameraMic", ActionPermCameraMic},
+ {"ActionPermContactCalendar", ActionPermContactCalendar},
+ {"ActionPermDataSetting", ActionPermDataSetting},
+ {"ActionPermDeviceInformation", ActionPermDeviceInformation},
+ {"ActionPermIdentity", ActionPermIdentity},
+ {"ActionPermMedia", ActionPermMedia},
+ {"ActionPermPhoneMsg", ActionPermPhoneMsg},
+ {"ActionPermScanWiFi", ActionPermScanWiFi},
+ {"ActionPets", ActionPets},
+ {"ActionPictureInPicture", ActionPictureInPicture},
+ {"ActionPictureInPictureAlt", ActionPictureInPictureAlt},
+ {"ActionPlayForWork", ActionPlayForWork},
+ {"ActionPolymer", ActionPolymer},
+ {"ActionPowerSettingsNew", ActionPowerSettingsNew},
+ {"ActionPregnantWoman", ActionPregnantWoman},
+ {"ActionPrint", ActionPrint},
+ {"ActionQueryBuilder", ActionQueryBuilder},
+ {"ActionQuestionAnswer", ActionQuestionAnswer},
+ {"ActionReceipt", ActionReceipt},
+ {"ActionRecordVoiceOver", ActionRecordVoiceOver},
+ {"ActionRedeem", ActionRedeem},
+ {"ActionRemoveShoppingCart", ActionRemoveShoppingCart},
+ {"ActionReorder", ActionReorder},
+ {"ActionReportProblem", ActionReportProblem},
+ {"ActionRestore", ActionRestore},
+ {"ActionRestorePage", ActionRestorePage},
+ {"ActionRoom", ActionRoom},
+ {"ActionRoundedCorner", ActionRoundedCorner},
+ {"ActionRowing", ActionRowing},
+ {"ActionSchedule", ActionSchedule},
+ {"ActionSearch", ActionSearch},
+ {"ActionSettings", ActionSettings},
+ {"ActionSettingsApplications", ActionSettingsApplications},
+ {"ActionSettingsBackupRestore", ActionSettingsBackupRestore},
+ {"ActionSettingsBluetooth", ActionSettingsBluetooth},
+ {"ActionSettingsBrightness", ActionSettingsBrightness},
+ {"ActionSettingsCell", ActionSettingsCell},
+ {"ActionSettingsEthernet", ActionSettingsEthernet},
+ {"ActionSettingsInputAntenna", ActionSettingsInputAntenna},
+ {"ActionSettingsInputComponent", ActionSettingsInputComponent},
+ {"ActionSettingsInputComposite", ActionSettingsInputComposite},
+ {"ActionSettingsInputHDMI", ActionSettingsInputHDMI},
+ {"ActionSettingsInputSVideo", ActionSettingsInputSVideo},
+ {"ActionSettingsOverscan", ActionSettingsOverscan},
+ {"ActionSettingsPhone", ActionSettingsPhone},
+ {"ActionSettingsPower", ActionSettingsPower},
+ {"ActionSettingsRemote", ActionSettingsRemote},
+ {"ActionSettingsVoice", ActionSettingsVoice},
+ {"ActionShop", ActionShop},
+ {"ActionShopTwo", ActionShopTwo},
+ {"ActionShoppingBasket", ActionShoppingBasket},
+ {"ActionShoppingCart", ActionShoppingCart},
+ {"ActionSpeakerNotes", ActionSpeakerNotes},
+ {"ActionSpeakerNotesOff", ActionSpeakerNotesOff},
+ {"ActionSpellcheck", ActionSpellcheck},
+ {"ActionStarRate", ActionStarRate},
+ {"ActionStars", ActionStars},
+ {"ActionStore", ActionStore},
+ {"ActionSubject", ActionSubject},
+ {"ActionSupervisorAccount", ActionSupervisorAccount},
+ {"ActionSwapHoriz", ActionSwapHoriz},
+ {"ActionSwapVert", ActionSwapVert},
+ {"ActionSwapVerticalCircle", ActionSwapVerticalCircle},
+ {"ActionSystemUpdateAlt", ActionSystemUpdateAlt},
+ {"ActionTab", ActionTab},
+ {"ActionTabUnselected", ActionTabUnselected},
+ {"ActionTheaters", ActionTheaters},
+ {"ActionThumbDown", ActionThumbDown},
+ {"ActionThumbUp", ActionThumbUp},
+ {"ActionThumbsUpDown", ActionThumbsUpDown},
+ {"ActionTimeline", ActionTimeline},
+ {"ActionTOC", ActionTOC},
+ {"ActionToday", ActionToday},
+ {"ActionToll", ActionToll},
+ {"ActionTouchApp", ActionTouchApp},
+ {"ActionTrackChanges", ActionTrackChanges},
+ {"ActionTranslate", ActionTranslate},
+ {"ActionTrendingDown", ActionTrendingDown},
+ {"ActionTrendingFlat", ActionTrendingFlat},
+ {"ActionTrendingUp", ActionTrendingUp},
+ {"ActionTurnedIn", ActionTurnedIn},
+ {"ActionTurnedInNot", ActionTurnedInNot},
+ {"ActionUpdate", ActionUpdate},
+ {"ActionVerifiedUser", ActionVerifiedUser},
+ {"ActionViewAgenda", ActionViewAgenda},
+ {"ActionViewArray", ActionViewArray},
+ {"ActionViewCarousel", ActionViewCarousel},
+ {"ActionViewColumn", ActionViewColumn},
+ {"ActionViewDay", ActionViewDay},
+ {"ActionViewHeadline", ActionViewHeadline},
+ {"ActionViewList", ActionViewList},
+ {"ActionViewModule", ActionViewModule},
+ {"ActionViewQuilt", ActionViewQuilt},
+ {"ActionViewStream", ActionViewStream},
+ {"ActionViewWeek", ActionViewWeek},
+ {"ActionVisibility", ActionVisibility},
+ {"ActionVisibilityOff", ActionVisibilityOff},
+ {"ActionWatchLater", ActionWatchLater},
+ {"ActionWork", ActionWork},
+ {"ActionYoutubeSearchedFor", ActionYoutubeSearchedFor},
+ {"ActionZoomIn", ActionZoomIn},
+ {"ActionZoomOut", ActionZoomOut},
+ {"AlertAddAlert", AlertAddAlert},
+ {"AlertError", AlertError},
+ {"AlertErrorOutline", AlertErrorOutline},
+ {"AlertWarning", AlertWarning},
+ {"AVAddToQueue", AVAddToQueue},
+ {"AVAirplay", AVAirplay},
+ {"AVAlbum", AVAlbum},
+ {"AVArtTrack", AVArtTrack},
+ {"AVAVTimer", AVAVTimer},
+ {"AVBrandingWatermark", AVBrandingWatermark},
+ {"AVCallToAction", AVCallToAction},
+ {"AVClosedCaption", AVClosedCaption},
+ {"AVEqualizer", AVEqualizer},
+ {"AVExplicit", AVExplicit},
+ {"AVFastForward", AVFastForward},
+ {"AVFastRewind", AVFastRewind},
+ {"AVFeaturedPlayList", AVFeaturedPlayList},
+ {"AVFeaturedVideo", AVFeaturedVideo},
+ {"AVFiberDVR", AVFiberDVR},
+ {"AVFiberManualRecord", AVFiberManualRecord},
+ {"AVFiberNew", AVFiberNew},
+ {"AVFiberPin", AVFiberPin},
+ {"AVFiberSmartRecord", AVFiberSmartRecord},
+ {"AVForward10", AVForward10},
+ {"AVForward30", AVForward30},
+ {"AVForward5", AVForward5},
+ {"AVGames", AVGames},
+ {"AVHD", AVHD},
+ {"AVHearing", AVHearing},
+ {"AVHighQuality", AVHighQuality},
+ {"AVLibraryAdd", AVLibraryAdd},
+ {"AVLibraryBooks", AVLibraryBooks},
+ {"AVLibraryMusic", AVLibraryMusic},
+ {"AVLoop", AVLoop},
+ {"AVMic", AVMic},
+ {"AVMicNone", AVMicNone},
+ {"AVMicOff", AVMicOff},
+ {"AVMovie", AVMovie},
+ {"AVMusicVideo", AVMusicVideo},
+ {"AVNewReleases", AVNewReleases},
+ {"AVNotInterested", AVNotInterested},
+ {"AVNote", AVNote},
+ {"AVPause", AVPause},
+ {"AVPauseCircleFilled", AVPauseCircleFilled},
+ {"AVPauseCircleOutline", AVPauseCircleOutline},
+ {"AVPlayArrow", AVPlayArrow},
+ {"AVPlayCircleFilled", AVPlayCircleFilled},
+ {"AVPlayCircleOutline", AVPlayCircleOutline},
+ {"AVPlaylistAdd", AVPlaylistAdd},
+ {"AVPlaylistAddCheck", AVPlaylistAddCheck},
+ {"AVPlaylistPlay", AVPlaylistPlay},
+ {"AVQueue", AVQueue},
+ {"AVQueueMusic", AVQueueMusic},
+ {"AVQueuePlayNext", AVQueuePlayNext},
+ {"AVRadio", AVRadio},
+ {"AVRecentActors", AVRecentActors},
+ {"AVRemoveFromQueue", AVRemoveFromQueue},
+ {"AVRepeat", AVRepeat},
+ {"AVRepeatOne", AVRepeatOne},
+ {"AVReplay", AVReplay},
+ {"AVReplay10", AVReplay10},
+ {"AVReplay30", AVReplay30},
+ {"AVReplay5", AVReplay5},
+ {"AVShuffle", AVShuffle},
+ {"AVSkipNext", AVSkipNext},
+ {"AVSkipPrevious", AVSkipPrevious},
+ {"AVSlowMotionVideo", AVSlowMotionVideo},
+ {"AVSnooze", AVSnooze},
+ {"AVSortByAlpha", AVSortByAlpha},
+ {"AVStop", AVStop},
+ {"AVSubscriptions", AVSubscriptions},
+ {"AVSubtitles", AVSubtitles},
+ {"AVSurroundSound", AVSurroundSound},
+ {"AVVideoCall", AVVideoCall},
+ {"AVVideoLabel", AVVideoLabel},
+ {"AVVideoLibrary", AVVideoLibrary},
+ {"AVVideocam", AVVideocam},
+ {"AVVideocamOff", AVVideocamOff},
+ {"AVVolumeDown", AVVolumeDown},
+ {"AVVolumeMute", AVVolumeMute},
+ {"AVVolumeOff", AVVolumeOff},
+ {"AVVolumeUp", AVVolumeUp},
+ {"AVWeb", AVWeb},
+ {"AVWebAsset", AVWebAsset},
+ {"CommunicationBusiness", CommunicationBusiness},
+ {"CommunicationCall", CommunicationCall},
+ {"CommunicationCallEnd", CommunicationCallEnd},
+ {"CommunicationCallMade", CommunicationCallMade},
+ {"CommunicationCallMerge", CommunicationCallMerge},
+ {"CommunicationCallMissed", CommunicationCallMissed},
+ {"CommunicationCallMissedOutgoing", CommunicationCallMissedOutgoing},
+ {"CommunicationCallReceived", CommunicationCallReceived},
+ {"CommunicationCallSplit", CommunicationCallSplit},
+ {"CommunicationChat", CommunicationChat},
+ {"CommunicationChatBubble", CommunicationChatBubble},
+ {"CommunicationChatBubbleOutline", CommunicationChatBubbleOutline},
+ {"CommunicationClearAll", CommunicationClearAll},
+ {"CommunicationComment", CommunicationComment},
+ {"CommunicationContactMail", CommunicationContactMail},
+ {"CommunicationContactPhone", CommunicationContactPhone},
+ {"CommunicationContacts", CommunicationContacts},
+ {"CommunicationDialerSIP", CommunicationDialerSIP},
+ {"CommunicationDialpad", CommunicationDialpad},
+ {"CommunicationEmail", CommunicationEmail},
+ {"CommunicationForum", CommunicationForum},
+ {"CommunicationImportContacts", CommunicationImportContacts},
+ {"CommunicationImportExport", CommunicationImportExport},
+ {"CommunicationInvertColorsOff", CommunicationInvertColorsOff},
+ {"CommunicationLiveHelp", CommunicationLiveHelp},
+ {"CommunicationLocationOff", CommunicationLocationOff},
+ {"CommunicationLocationOn", CommunicationLocationOn},
+ {"CommunicationMailOutline", CommunicationMailOutline},
+ {"CommunicationMessage", CommunicationMessage},
+ {"CommunicationNoSIM", CommunicationNoSIM},
+ {"CommunicationPhone", CommunicationPhone},
+ {"CommunicationPhoneLinkErase", CommunicationPhoneLinkErase},
+ {"CommunicationPhoneLinkLock", CommunicationPhoneLinkLock},
+ {"CommunicationPhoneLinkRing", CommunicationPhoneLinkRing},
+ {"CommunicationPhoneLinkSetup", CommunicationPhoneLinkSetup},
+ {"CommunicationPortableWiFiOff", CommunicationPortableWiFiOff},
+ {"CommunicationPresentToAll", CommunicationPresentToAll},
+ {"CommunicationRingVolume", CommunicationRingVolume},
+ {"CommunicationRSSFeed", CommunicationRSSFeed},
+ {"CommunicationScreenShare", CommunicationScreenShare},
+ {"CommunicationSpeakerPhone", CommunicationSpeakerPhone},
+ {"CommunicationStayCurrentLandscape", CommunicationStayCurrentLandscape},
+ {"CommunicationStayCurrentPortrait", CommunicationStayCurrentPortrait},
+ {"CommunicationStayPrimaryLandscape", CommunicationStayPrimaryLandscape},
+ {"CommunicationStayPrimaryPortrait", CommunicationStayPrimaryPortrait},
+ {"CommunicationStopScreenShare", CommunicationStopScreenShare},
+ {"CommunicationSwapCalls", CommunicationSwapCalls},
+ {"CommunicationTextSMS", CommunicationTextSMS},
+ {"CommunicationVoicemail", CommunicationVoicemail},
+ {"CommunicationVPNKey", CommunicationVPNKey},
+ {"ContentAdd", ContentAdd},
+ {"ContentAddBox", ContentAddBox},
+ {"ContentAddCircle", ContentAddCircle},
+ {"ContentAddCircleOutline", ContentAddCircleOutline},
+ {"ContentArchive", ContentArchive},
+ {"ContentBackspace", ContentBackspace},
+ {"ContentBlock", ContentBlock},
+ {"ContentClear", ContentClear},
+ {"ContentContentCopy", ContentContentCopy},
+ {"ContentContentCut", ContentContentCut},
+ {"ContentContentPaste", ContentContentPaste},
+ {"ContentCreate", ContentCreate},
+ {"ContentDeleteSweep", ContentDeleteSweep},
+ {"ContentDrafts", ContentDrafts},
+ {"ContentFilterList", ContentFilterList},
+ {"ContentFlag", ContentFlag},
+ {"ContentFontDownload", ContentFontDownload},
+ {"ContentForward", ContentForward},
+ {"ContentGesture", ContentGesture},
+ {"ContentInbox", ContentInbox},
+ {"ContentLink", ContentLink},
+ {"ContentLowPriority", ContentLowPriority},
+ {"ContentMail", ContentMail},
+ {"ContentMarkUnread", ContentMarkUnread},
+ {"ContentMoveToInbox", ContentMoveToInbox},
+ {"ContentNextWeek", ContentNextWeek},
+ {"ContentRedo", ContentRedo},
+ {"ContentRemove", ContentRemove},
+ {"ContentRemoveCircle", ContentRemoveCircle},
+ {"ContentRemoveCircleOutline", ContentRemoveCircleOutline},
+ {"ContentReply", ContentReply},
+ {"ContentReplyAll", ContentReplyAll},
+ {"ContentReport", ContentReport},
+ {"ContentSave", ContentSave},
+ {"ContentSelectAll", ContentSelectAll},
+ {"ContentSend", ContentSend},
+ {"ContentSort", ContentSort},
+ {"ContentTextFormat", ContentTextFormat},
+ {"ContentUnarchive", ContentUnarchive},
+ {"ContentUndo", ContentUndo},
+ {"ContentWeekend", ContentWeekend},
+ {"DeviceAccessAlarm", DeviceAccessAlarm},
+ {"DeviceAccessAlarms", DeviceAccessAlarms},
+ {"DeviceAccessTime", DeviceAccessTime},
+ {"DeviceAddAlarm", DeviceAddAlarm},
+ {"DeviceAirplaneModeActive", DeviceAirplaneModeActive},
+ {"DeviceAirplaneModeInactive", DeviceAirplaneModeInactive},
+ {"DeviceBattery20", DeviceBattery20},
+ {"DeviceBattery30", DeviceBattery30},
+ {"DeviceBattery50", DeviceBattery50},
+ {"DeviceBattery60", DeviceBattery60},
+ {"DeviceBattery80", DeviceBattery80},
+ {"DeviceBattery90", DeviceBattery90},
+ {"DeviceBatteryAlert", DeviceBatteryAlert},
+ {"DeviceBatteryCharging20", DeviceBatteryCharging20},
+ {"DeviceBatteryCharging30", DeviceBatteryCharging30},
+ {"DeviceBatteryCharging50", DeviceBatteryCharging50},
+ {"DeviceBatteryCharging60", DeviceBatteryCharging60},
+ {"DeviceBatteryCharging80", DeviceBatteryCharging80},
+ {"DeviceBatteryCharging90", DeviceBatteryCharging90},
+ {"DeviceBatteryChargingFull", DeviceBatteryChargingFull},
+ {"DeviceBatteryFull", DeviceBatteryFull},
+ {"DeviceBatteryStd", DeviceBatteryStd},
+ {"DeviceBatteryUnknown", DeviceBatteryUnknown},
+ {"DeviceBluetooth", DeviceBluetooth},
+ {"DeviceBluetoothConnected", DeviceBluetoothConnected},
+ {"DeviceBluetoothDisabled", DeviceBluetoothDisabled},
+ {"DeviceBluetoothSearching", DeviceBluetoothSearching},
+ {"DeviceBrightnessAuto", DeviceBrightnessAuto},
+ {"DeviceBrightnessHigh", DeviceBrightnessHigh},
+ {"DeviceBrightnessLow", DeviceBrightnessLow},
+ {"DeviceBrightnessMedium", DeviceBrightnessMedium},
+ {"DeviceDataUsage", DeviceDataUsage},
+ {"DeviceDeveloperMode", DeviceDeveloperMode},
+ {"DeviceDevices", DeviceDevices},
+ {"DeviceDVR", DeviceDVR},
+ {"DeviceGPSFixed", DeviceGPSFixed},
+ {"DeviceGPSNotFixed", DeviceGPSNotFixed},
+ {"DeviceGPSOff", DeviceGPSOff},
+ {"DeviceGraphicEq", DeviceGraphicEq},
+ {"DeviceLocationDisabled", DeviceLocationDisabled},
+ {"DeviceLocationSearching", DeviceLocationSearching},
+ {"DeviceNetworkCell", DeviceNetworkCell},
+ {"DeviceNetworkWiFi", DeviceNetworkWiFi},
+ {"DeviceNFC", DeviceNFC},
+ {"DeviceScreenLockLandscape", DeviceScreenLockLandscape},
+ {"DeviceScreenLockPortrait", DeviceScreenLockPortrait},
+ {"DeviceScreenLockRotation", DeviceScreenLockRotation},
+ {"DeviceScreenRotation", DeviceScreenRotation},
+ {"DeviceSDStorage", DeviceSDStorage},
+ {"DeviceSettingsSystemDaydream", DeviceSettingsSystemDaydream},
+ {"DeviceSignalCellular0Bar", DeviceSignalCellular0Bar},
+ {"DeviceSignalCellular1Bar", DeviceSignalCellular1Bar},
+ {"DeviceSignalCellular2Bar", DeviceSignalCellular2Bar},
+ {"DeviceSignalCellular3Bar", DeviceSignalCellular3Bar},
+ {"DeviceSignalCellular4Bar", DeviceSignalCellular4Bar},
+ {"DeviceSignalCellularConnectedNoInternet0Bar", DeviceSignalCellularConnectedNoInternet0Bar},
+ {"DeviceSignalCellularConnectedNoInternet1Bar", DeviceSignalCellularConnectedNoInternet1Bar},
+ {"DeviceSignalCellularConnectedNoInternet2Bar", DeviceSignalCellularConnectedNoInternet2Bar},
+ {"DeviceSignalCellularConnectedNoInternet3Bar", DeviceSignalCellularConnectedNoInternet3Bar},
+ {"DeviceSignalCellularConnectedNoInternet4Bar", DeviceSignalCellularConnectedNoInternet4Bar},
+ {"DeviceSignalCellularNoSIM", DeviceSignalCellularNoSIM},
+ {"DeviceSignalCellularNull", DeviceSignalCellularNull},
+ {"DeviceSignalCellularOff", DeviceSignalCellularOff},
+ {"DeviceSignalWiFi0Bar", DeviceSignalWiFi0Bar},
+ {"DeviceSignalWiFi1Bar", DeviceSignalWiFi1Bar},
+ {"DeviceSignalWiFi1BarLock", DeviceSignalWiFi1BarLock},
+ {"DeviceSignalWiFi2Bar", DeviceSignalWiFi2Bar},
+ {"DeviceSignalWiFi2BarLock", DeviceSignalWiFi2BarLock},
+ {"DeviceSignalWiFi3Bar", DeviceSignalWiFi3Bar},
+ {"DeviceSignalWiFi3BarLock", DeviceSignalWiFi3BarLock},
+ {"DeviceSignalWiFi4Bar", DeviceSignalWiFi4Bar},
+ {"DeviceSignalWiFi4BarLock", DeviceSignalWiFi4BarLock},
+ {"DeviceSignalWiFiOff", DeviceSignalWiFiOff},
+ {"DeviceStorage", DeviceStorage},
+ {"DeviceUSB", DeviceUSB},
+ {"DeviceWallpaper", DeviceWallpaper},
+ {"DeviceWidgets", DeviceWidgets},
+ {"DeviceWiFiLock", DeviceWiFiLock},
+ {"DeviceWiFiTethering", DeviceWiFiTethering},
+ {"EditorAttachFile", EditorAttachFile},
+ {"EditorAttachMoney", EditorAttachMoney},
+ {"EditorBorderAll", EditorBorderAll},
+ {"EditorBorderBottom", EditorBorderBottom},
+ {"EditorBorderClear", EditorBorderClear},
+ {"EditorBorderColor", EditorBorderColor},
+ {"EditorBorderHorizontal", EditorBorderHorizontal},
+ {"EditorBorderInner", EditorBorderInner},
+ {"EditorBorderLeft", EditorBorderLeft},
+ {"EditorBorderOuter", EditorBorderOuter},
+ {"EditorBorderRight", EditorBorderRight},
+ {"EditorBorderStyle", EditorBorderStyle},
+ {"EditorBorderTop", EditorBorderTop},
+ {"EditorBorderVertical", EditorBorderVertical},
+ {"EditorBubbleChart", EditorBubbleChart},
+ {"EditorDragHandle", EditorDragHandle},
+ {"EditorFormatAlignCenter", EditorFormatAlignCenter},
+ {"EditorFormatAlignJustify", EditorFormatAlignJustify},
+ {"EditorFormatAlignLeft", EditorFormatAlignLeft},
+ {"EditorFormatAlignRight", EditorFormatAlignRight},
+ {"EditorFormatBold", EditorFormatBold},
+ {"EditorFormatClear", EditorFormatClear},
+ {"EditorFormatColorFill", EditorFormatColorFill},
+ {"EditorFormatColorReset", EditorFormatColorReset},
+ {"EditorFormatColorText", EditorFormatColorText},
+ {"EditorFormatIndentDecrease", EditorFormatIndentDecrease},
+ {"EditorFormatIndentIncrease", EditorFormatIndentIncrease},
+ {"EditorFormatItalic", EditorFormatItalic},
+ {"EditorFormatLineSpacing", EditorFormatLineSpacing},
+ {"EditorFormatListBulleted", EditorFormatListBulleted},
+ {"EditorFormatListNumbered", EditorFormatListNumbered},
+ {"EditorFormatPaint", EditorFormatPaint},
+ {"EditorFormatQuote", EditorFormatQuote},
+ {"EditorFormatShapes", EditorFormatShapes},
+ {"EditorFormatSize", EditorFormatSize},
+ {"EditorFormatStrikethrough", EditorFormatStrikethrough},
+ {"EditorFormatTextDirectionLToR", EditorFormatTextDirectionLToR},
+ {"EditorFormatTextDirectionRToL", EditorFormatTextDirectionRToL},
+ {"EditorFormatUnderlined", EditorFormatUnderlined},
+ {"EditorFunctions", EditorFunctions},
+ {"EditorHighlight", EditorHighlight},
+ {"EditorInsertChart", EditorInsertChart},
+ {"EditorInsertComment", EditorInsertComment},
+ {"EditorInsertDriveFile", EditorInsertDriveFile},
+ {"EditorInsertEmoticon", EditorInsertEmoticon},
+ {"EditorInsertInvitation", EditorInsertInvitation},
+ {"EditorInsertLink", EditorInsertLink},
+ {"EditorInsertPhoto", EditorInsertPhoto},
+ {"EditorLinearScale", EditorLinearScale},
+ {"EditorMergeType", EditorMergeType},
+ {"EditorModeComment", EditorModeComment},
+ {"EditorModeEdit", EditorModeEdit},
+ {"EditorMonetizationOn", EditorMonetizationOn},
+ {"EditorMoneyOff", EditorMoneyOff},
+ {"EditorMultilineChart", EditorMultilineChart},
+ {"EditorPieChart", EditorPieChart},
+ {"EditorPieChartOutlined", EditorPieChartOutlined},
+ {"EditorPublish", EditorPublish},
+ {"EditorShortText", EditorShortText},
+ {"EditorShowChart", EditorShowChart},
+ {"EditorSpaceBar", EditorSpaceBar},
+ {"EditorStrikethroughS", EditorStrikethroughS},
+ {"EditorTextFields", EditorTextFields},
+ {"EditorTitle", EditorTitle},
+ {"EditorVerticalAlignBottom", EditorVerticalAlignBottom},
+ {"EditorVerticalAlignCenter", EditorVerticalAlignCenter},
+ {"EditorVerticalAlignTop", EditorVerticalAlignTop},
+ {"EditorWrapText", EditorWrapText},
+ {"FileAttachment", FileAttachment},
+ {"FileCloud", FileCloud},
+ {"FileCloudCircle", FileCloudCircle},
+ {"FileCloudDone", FileCloudDone},
+ {"FileCloudDownload", FileCloudDownload},
+ {"FileCloudOff", FileCloudOff},
+ {"FileCloudQueue", FileCloudQueue},
+ {"FileCloudUpload", FileCloudUpload},
+ {"FileCreateNewFolder", FileCreateNewFolder},
+ {"FileFileDownload", FileFileDownload},
+ {"FileFileUpload", FileFileUpload},
+ {"FileFolder", FileFolder},
+ {"FileFolderOpen", FileFolderOpen},
+ {"FileFolderShared", FileFolderShared},
+ {"HardwareCast", HardwareCast},
+ {"HardwareCastConnected", HardwareCastConnected},
+ {"HardwareComputer", HardwareComputer},
+ {"HardwareDesktopMac", HardwareDesktopMac},
+ {"HardwareDesktopWindows", HardwareDesktopWindows},
+ {"HardwareDeveloperBoard", HardwareDeveloperBoard},
+ {"HardwareDeviceHub", HardwareDeviceHub},
+ {"HardwareDevicesOther", HardwareDevicesOther},
+ {"HardwareDock", HardwareDock},
+ {"HardwareGamepad", HardwareGamepad},
+ {"HardwareHeadset", HardwareHeadset},
+ {"HardwareHeadsetMic", HardwareHeadsetMic},
+ {"HardwareKeyboard", HardwareKeyboard},
+ {"HardwareKeyboardArrowDown", HardwareKeyboardArrowDown},
+ {"HardwareKeyboardArrowLeft", HardwareKeyboardArrowLeft},
+ {"HardwareKeyboardArrowRight", HardwareKeyboardArrowRight},
+ {"HardwareKeyboardArrowUp", HardwareKeyboardArrowUp},
+ {"HardwareKeyboardBackspace", HardwareKeyboardBackspace},
+ {"HardwareKeyboardCapslock", HardwareKeyboardCapslock},
+ {"HardwareKeyboardHide", HardwareKeyboardHide},
+ {"HardwareKeyboardReturn", HardwareKeyboardReturn},
+ {"HardwareKeyboardTab", HardwareKeyboardTab},
+ {"HardwareKeyboardVoice", HardwareKeyboardVoice},
+ {"HardwareLaptop", HardwareLaptop},
+ {"HardwareLaptopChromebook", HardwareLaptopChromebook},
+ {"HardwareLaptopMac", HardwareLaptopMac},
+ {"HardwareLaptopWindows", HardwareLaptopWindows},
+ {"HardwareMemory", HardwareMemory},
+ {"HardwareMouse", HardwareMouse},
+ {"HardwarePhoneAndroid", HardwarePhoneAndroid},
+ {"HardwarePhoneIPhone", HardwarePhoneIPhone},
+ {"HardwarePhoneLink", HardwarePhoneLink},
+ {"HardwarePhoneLinkOff", HardwarePhoneLinkOff},
+ {"HardwarePowerInput", HardwarePowerInput},
+ {"HardwareRouter", HardwareRouter},
+ {"HardwareScanner", HardwareScanner},
+ {"HardwareSecurity", HardwareSecurity},
+ {"HardwareSIMCard", HardwareSIMCard},
+ {"HardwareSmartphone", HardwareSmartphone},
+ {"HardwareSpeaker", HardwareSpeaker},
+ {"HardwareSpeakerGroup", HardwareSpeakerGroup},
+ {"HardwareTablet", HardwareTablet},
+ {"HardwareTabletAndroid", HardwareTabletAndroid},
+ {"HardwareTabletMac", HardwareTabletMac},
+ {"HardwareToys", HardwareToys},
+ {"HardwareTV", HardwareTV},
+ {"HardwareVideogameAsset", HardwareVideogameAsset},
+ {"HardwareWatch", HardwareWatch},
+ {"ImageAddAPhoto", ImageAddAPhoto},
+ {"ImageAddToPhotos", ImageAddToPhotos},
+ {"ImageAdjust", ImageAdjust},
+ {"ImageAssistant", ImageAssistant},
+ {"ImageAssistantPhoto", ImageAssistantPhoto},
+ {"ImageAudiotrack", ImageAudiotrack},
+ {"ImageBlurCircular", ImageBlurCircular},
+ {"ImageBlurLinear", ImageBlurLinear},
+ {"ImageBlurOff", ImageBlurOff},
+ {"ImageBlurOn", ImageBlurOn},
+ {"ImageBrightness1", ImageBrightness1},
+ {"ImageBrightness2", ImageBrightness2},
+ {"ImageBrightness3", ImageBrightness3},
+ {"ImageBrightness4", ImageBrightness4},
+ {"ImageBrightness5", ImageBrightness5},
+ {"ImageBrightness6", ImageBrightness6},
+ {"ImageBrightness7", ImageBrightness7},
+ {"ImageBrokenImage", ImageBrokenImage},
+ {"ImageBrush", ImageBrush},
+ {"ImageBurstMode", ImageBurstMode},
+ {"ImageCamera", ImageCamera},
+ {"ImageCameraAlt", ImageCameraAlt},
+ {"ImageCameraFront", ImageCameraFront},
+ {"ImageCameraRear", ImageCameraRear},
+ {"ImageCameraRoll", ImageCameraRoll},
+ {"ImageCenterFocusStrong", ImageCenterFocusStrong},
+ {"ImageCenterFocusWeak", ImageCenterFocusWeak},
+ {"ImageCollections", ImageCollections},
+ {"ImageCollectionsBookmark", ImageCollectionsBookmark},
+ {"ImageColorLens", ImageColorLens},
+ {"ImageColorize", ImageColorize},
+ {"ImageCompare", ImageCompare},
+ {"ImageControlPoint", ImageControlPoint},
+ {"ImageControlPointDuplicate", ImageControlPointDuplicate},
+ {"ImageCrop", ImageCrop},
+ {"ImageCrop169", ImageCrop169},
+ {"ImageCrop32", ImageCrop32},
+ {"ImageCrop54", ImageCrop54},
+ {"ImageCrop75", ImageCrop75},
+ {"ImageCropDIN", ImageCropDIN},
+ {"ImageCropFree", ImageCropFree},
+ {"ImageCropLandscape", ImageCropLandscape},
+ {"ImageCropOriginal", ImageCropOriginal},
+ {"ImageCropPortrait", ImageCropPortrait},
+ {"ImageCropRotate", ImageCropRotate},
+ {"ImageCropSquare", ImageCropSquare},
+ {"ImageDehaze", ImageDehaze},
+ {"ImageDetails", ImageDetails},
+ {"ImageEdit", ImageEdit},
+ {"ImageExposure", ImageExposure},
+ {"ImageExposureNeg1", ImageExposureNeg1},
+ {"ImageExposureNeg2", ImageExposureNeg2},
+ {"ImageExposurePlus1", ImageExposurePlus1},
+ {"ImageExposurePlus2", ImageExposurePlus2},
+ {"ImageExposureZero", ImageExposureZero},
+ {"ImageFilter", ImageFilter},
+ {"ImageFilter1", ImageFilter1},
+ {"ImageFilter2", ImageFilter2},
+ {"ImageFilter3", ImageFilter3},
+ {"ImageFilter4", ImageFilter4},
+ {"ImageFilter5", ImageFilter5},
+ {"ImageFilter6", ImageFilter6},
+ {"ImageFilter7", ImageFilter7},
+ {"ImageFilter8", ImageFilter8},
+ {"ImageFilter9", ImageFilter9},
+ {"ImageFilter9Plus", ImageFilter9Plus},
+ {"ImageFilterBAndW", ImageFilterBAndW},
+ {"ImageFilterCenterFocus", ImageFilterCenterFocus},
+ {"ImageFilterDrama", ImageFilterDrama},
+ {"ImageFilterFrames", ImageFilterFrames},
+ {"ImageFilterHDR", ImageFilterHDR},
+ {"ImageFilterNone", ImageFilterNone},
+ {"ImageFilterTiltShift", ImageFilterTiltShift},
+ {"ImageFilterVintage", ImageFilterVintage},
+ {"ImageFlare", ImageFlare},
+ {"ImageFlashAuto", ImageFlashAuto},
+ {"ImageFlashOff", ImageFlashOff},
+ {"ImageFlashOn", ImageFlashOn},
+ {"ImageFlip", ImageFlip},
+ {"ImageGradient", ImageGradient},
+ {"ImageGrain", ImageGrain},
+ {"ImageGridOff", ImageGridOff},
+ {"ImageGridOn", ImageGridOn},
+ {"ImageHDROff", ImageHDROff},
+ {"ImageHDROn", ImageHDROn},
+ {"ImageHDRStrong", ImageHDRStrong},
+ {"ImageHDRWeak", ImageHDRWeak},
+ {"ImageHealing", ImageHealing},
+ {"ImageImage", ImageImage},
+ {"ImageImageAspectRatio", ImageImageAspectRatio},
+ {"ImageISO", ImageISO},
+ {"ImageLandscape", ImageLandscape},
+ {"ImageLeakAdd", ImageLeakAdd},
+ {"ImageLeakRemove", ImageLeakRemove},
+ {"ImageLens", ImageLens},
+ {"ImageLinkedCamera", ImageLinkedCamera},
+ {"ImageLooks", ImageLooks},
+ {"ImageLooks3", ImageLooks3},
+ {"ImageLooks4", ImageLooks4},
+ {"ImageLooks5", ImageLooks5},
+ {"ImageLooks6", ImageLooks6},
+ {"ImageLooksOne", ImageLooksOne},
+ {"ImageLooksTwo", ImageLooksTwo},
+ {"ImageLoupe", ImageLoupe},
+ {"ImageMonochromePhotos", ImageMonochromePhotos},
+ {"ImageMovieCreation", ImageMovieCreation},
+ {"ImageMovieFilter", ImageMovieFilter},
+ {"ImageMusicNote", ImageMusicNote},
+ {"ImageNature", ImageNature},
+ {"ImageNaturePeople", ImageNaturePeople},
+ {"ImageNavigateBefore", ImageNavigateBefore},
+ {"ImageNavigateNext", ImageNavigateNext},
+ {"ImagePalette", ImagePalette},
+ {"ImagePanorama", ImagePanorama},
+ {"ImagePanoramaFishEye", ImagePanoramaFishEye},
+ {"ImagePanoramaHorizontal", ImagePanoramaHorizontal},
+ {"ImagePanoramaVertical", ImagePanoramaVertical},
+ {"ImagePanoramaWideAngle", ImagePanoramaWideAngle},
+ {"ImagePhoto", ImagePhoto},
+ {"ImagePhotoAlbum", ImagePhotoAlbum},
+ {"ImagePhotoCamera", ImagePhotoCamera},
+ {"ImagePhotoFilter", ImagePhotoFilter},
+ {"ImagePhotoLibrary", ImagePhotoLibrary},
+ {"ImagePhotoSizeSelectActual", ImagePhotoSizeSelectActual},
+ {"ImagePhotoSizeSelectLarge", ImagePhotoSizeSelectLarge},
+ {"ImagePhotoSizeSelectSmall", ImagePhotoSizeSelectSmall},
+ {"ImagePictureAsPDF", ImagePictureAsPDF},
+ {"ImagePortrait", ImagePortrait},
+ {"ImageRemoveRedEye", ImageRemoveRedEye},
+ {"ImageRotate90DegreesCCW", ImageRotate90DegreesCCW},
+ {"ImageRotateLeft", ImageRotateLeft},
+ {"ImageRotateRight", ImageRotateRight},
+ {"ImageSlideshow", ImageSlideshow},
+ {"ImageStraighten", ImageStraighten},
+ {"ImageStyle", ImageStyle},
+ {"ImageSwitchCamera", ImageSwitchCamera},
+ {"ImageSwitchVideo", ImageSwitchVideo},
+ {"ImageTagFaces", ImageTagFaces},
+ {"ImageTexture", ImageTexture},
+ {"ImageTimeLapse", ImageTimeLapse},
+ {"ImageTimer", ImageTimer},
+ {"ImageTimer10", ImageTimer10},
+ {"ImageTimer3", ImageTimer3},
+ {"ImageTimerOff", ImageTimerOff},
+ {"ImageTonality", ImageTonality},
+ {"ImageTransform", ImageTransform},
+ {"ImageTune", ImageTune},
+ {"ImageViewComfy", ImageViewComfy},
+ {"ImageViewCompact", ImageViewCompact},
+ {"ImageVignette", ImageVignette},
+ {"ImageWBAuto", ImageWBAuto},
+ {"ImageWBCloudy", ImageWBCloudy},
+ {"ImageWBIncandescent", ImageWBIncandescent},
+ {"ImageWBIridescent", ImageWBIridescent},
+ {"ImageWBSunny", ImageWBSunny},
+ {"MapsAddLocation", MapsAddLocation},
+ {"MapsBeenhere", MapsBeenhere},
+ {"MapsDirections", MapsDirections},
+ {"MapsDirectionsBike", MapsDirectionsBike},
+ {"MapsDirectionsBoat", MapsDirectionsBoat},
+ {"MapsDirectionsBus", MapsDirectionsBus},
+ {"MapsDirectionsCar", MapsDirectionsCar},
+ {"MapsDirectionsRailway", MapsDirectionsRailway},
+ {"MapsDirectionsRun", MapsDirectionsRun},
+ {"MapsDirectionsSubway", MapsDirectionsSubway},
+ {"MapsDirectionsTransit", MapsDirectionsTransit},
+ {"MapsDirectionsWalk", MapsDirectionsWalk},
+ {"MapsEditLocation", MapsEditLocation},
+ {"MapsEVStation", MapsEVStation},
+ {"MapsFlight", MapsFlight},
+ {"MapsHotel", MapsHotel},
+ {"MapsLayers", MapsLayers},
+ {"MapsLayersClear", MapsLayersClear},
+ {"MapsLocalActivity", MapsLocalActivity},
+ {"MapsLocalAirport", MapsLocalAirport},
+ {"MapsLocalATM", MapsLocalATM},
+ {"MapsLocalBar", MapsLocalBar},
+ {"MapsLocalCafe", MapsLocalCafe},
+ {"MapsLocalCarWash", MapsLocalCarWash},
+ {"MapsLocalConvenienceStore", MapsLocalConvenienceStore},
+ {"MapsLocalDining", MapsLocalDining},
+ {"MapsLocalDrink", MapsLocalDrink},
+ {"MapsLocalFlorist", MapsLocalFlorist},
+ {"MapsLocalGasStation", MapsLocalGasStation},
+ {"MapsLocalGroceryStore", MapsLocalGroceryStore},
+ {"MapsLocalHospital", MapsLocalHospital},
+ {"MapsLocalHotel", MapsLocalHotel},
+ {"MapsLocalLaundryService", MapsLocalLaundryService},
+ {"MapsLocalLibrary", MapsLocalLibrary},
+ {"MapsLocalMall", MapsLocalMall},
+ {"MapsLocalMovies", MapsLocalMovies},
+ {"MapsLocalOffer", MapsLocalOffer},
+ {"MapsLocalParking", MapsLocalParking},
+ {"MapsLocalPharmacy", MapsLocalPharmacy},
+ {"MapsLocalPhone", MapsLocalPhone},
+ {"MapsLocalPizza", MapsLocalPizza},
+ {"MapsLocalPlay", MapsLocalPlay},
+ {"MapsLocalPostOffice", MapsLocalPostOffice},
+ {"MapsLocalPrintshop", MapsLocalPrintshop},
+ {"MapsLocalSee", MapsLocalSee},
+ {"MapsLocalShipping", MapsLocalShipping},
+ {"MapsLocalTaxi", MapsLocalTaxi},
+ {"MapsMap", MapsMap},
+ {"MapsMyLocation", MapsMyLocation},
+ {"MapsNavigation", MapsNavigation},
+ {"MapsNearMe", MapsNearMe},
+ {"MapsPersonPin", MapsPersonPin},
+ {"MapsPersonPinCircle", MapsPersonPinCircle},
+ {"MapsPinDrop", MapsPinDrop},
+ {"MapsPlace", MapsPlace},
+ {"MapsRateReview", MapsRateReview},
+ {"MapsRestaurant", MapsRestaurant},
+ {"MapsRestaurantMenu", MapsRestaurantMenu},
+ {"MapsSatellite", MapsSatellite},
+ {"MapsStoreMallDirectory", MapsStoreMallDirectory},
+ {"MapsStreetView", MapsStreetView},
+ {"MapsSubway", MapsSubway},
+ {"MapsTerrain", MapsTerrain},
+ {"MapsTraffic", MapsTraffic},
+ {"MapsTrain", MapsTrain},
+ {"MapsTram", MapsTram},
+ {"MapsTransferWithinAStation", MapsTransferWithinAStation},
+ {"MapsZoomOutMap", MapsZoomOutMap},
+ {"NavigationApps", NavigationApps},
+ {"NavigationArrowBack", NavigationArrowBack},
+ {"NavigationArrowDownward", NavigationArrowDownward},
+ {"NavigationArrowDropDown", NavigationArrowDropDown},
+ {"NavigationArrowDropDownCircle", NavigationArrowDropDownCircle},
+ {"NavigationArrowDropUp", NavigationArrowDropUp},
+ {"NavigationArrowForward", NavigationArrowForward},
+ {"NavigationArrowUpward", NavigationArrowUpward},
+ {"NavigationCancel", NavigationCancel},
+ {"NavigationCheck", NavigationCheck},
+ {"NavigationChevronLeft", NavigationChevronLeft},
+ {"NavigationChevronRight", NavigationChevronRight},
+ {"NavigationClose", NavigationClose},
+ {"NavigationExpandLess", NavigationExpandLess},
+ {"NavigationExpandMore", NavigationExpandMore},
+ {"NavigationFirstPage", NavigationFirstPage},
+ {"NavigationFullscreen", NavigationFullscreen},
+ {"NavigationFullscreenExit", NavigationFullscreenExit},
+ {"NavigationLastPage", NavigationLastPage},
+ {"NavigationMenu", NavigationMenu},
+ {"NavigationMoreHoriz", NavigationMoreHoriz},
+ {"NavigationMoreVert", NavigationMoreVert},
+ {"NavigationRefresh", NavigationRefresh},
+ {"NavigationSubdirectoryArrowLeft", NavigationSubdirectoryArrowLeft},
+ {"NavigationSubdirectoryArrowRight", NavigationSubdirectoryArrowRight},
+ {"NavigationUnfoldLess", NavigationUnfoldLess},
+ {"NavigationUnfoldMore", NavigationUnfoldMore},
+ {"NotificationADB", NotificationADB},
+ {"NotificationAirlineSeatFlat", NotificationAirlineSeatFlat},
+ {"NotificationAirlineSeatFlatAngled", NotificationAirlineSeatFlatAngled},
+ {"NotificationAirlineSeatIndividualSuite", NotificationAirlineSeatIndividualSuite},
+ {"NotificationAirlineSeatLegroomExtra", NotificationAirlineSeatLegroomExtra},
+ {"NotificationAirlineSeatLegroomNormal", NotificationAirlineSeatLegroomNormal},
+ {"NotificationAirlineSeatLegroomReduced", NotificationAirlineSeatLegroomReduced},
+ {"NotificationAirlineSeatReclineExtra", NotificationAirlineSeatReclineExtra},
+ {"NotificationAirlineSeatReclineNormal", NotificationAirlineSeatReclineNormal},
+ {"NotificationBluetoothAudio", NotificationBluetoothAudio},
+ {"NotificationConfirmationNumber", NotificationConfirmationNumber},
+ {"NotificationDiscFull", NotificationDiscFull},
+ {"NotificationDoNotDisturb", NotificationDoNotDisturb},
+ {"NotificationDoNotDisturbAlt", NotificationDoNotDisturbAlt},
+ {"NotificationDoNotDisturbOff", NotificationDoNotDisturbOff},
+ {"NotificationDoNotDisturbOn", NotificationDoNotDisturbOn},
+ {"NotificationDriveETA", NotificationDriveETA},
+ {"NotificationEnhancedEncryption", NotificationEnhancedEncryption},
+ {"NotificationEventAvailable", NotificationEventAvailable},
+ {"NotificationEventBusy", NotificationEventBusy},
+ {"NotificationEventNote", NotificationEventNote},
+ {"NotificationFolderSpecial", NotificationFolderSpecial},
+ {"NotificationLiveTV", NotificationLiveTV},
+ {"NotificationMMS", NotificationMMS},
+ {"NotificationMore", NotificationMore},
+ {"NotificationNetworkCheck", NotificationNetworkCheck},
+ {"NotificationNetworkLocked", NotificationNetworkLocked},
+ {"NotificationNoEncryption", NotificationNoEncryption},
+ {"NotificationOnDemandVideo", NotificationOnDemandVideo},
+ {"NotificationPersonalVideo", NotificationPersonalVideo},
+ {"NotificationPhoneBluetoothSpeaker", NotificationPhoneBluetoothSpeaker},
+ {"NotificationPhoneForwarded", NotificationPhoneForwarded},
+ {"NotificationPhoneInTalk", NotificationPhoneInTalk},
+ {"NotificationPhoneLocked", NotificationPhoneLocked},
+ {"NotificationPhoneMissed", NotificationPhoneMissed},
+ {"NotificationPhonePaused", NotificationPhonePaused},
+ {"NotificationPower", NotificationPower},
+ {"NotificationPriorityHigh", NotificationPriorityHigh},
+ {"NotificationRVHookup", NotificationRVHookup},
+ {"NotificationSDCard", NotificationSDCard},
+ {"NotificationSIMCardAlert", NotificationSIMCardAlert},
+ {"NotificationSMS", NotificationSMS},
+ {"NotificationSMSFailed", NotificationSMSFailed},
+ {"NotificationSync", NotificationSync},
+ {"NotificationSyncDisabled", NotificationSyncDisabled},
+ {"NotificationSyncProblem", NotificationSyncProblem},
+ {"NotificationSystemUpdate", NotificationSystemUpdate},
+ {"NotificationTapAndPlay", NotificationTapAndPlay},
+ {"NotificationTimeToLeave", NotificationTimeToLeave},
+ {"NotificationVibration", NotificationVibration},
+ {"NotificationVoiceChat", NotificationVoiceChat},
+ {"NotificationVPNLock", NotificationVPNLock},
+ {"NotificationWC", NotificationWC},
+ {"NotificationWiFi", NotificationWiFi},
+ {"PlacesACUnit", PlacesACUnit},
+ {"PlacesAirportShuttle", PlacesAirportShuttle},
+ {"PlacesAllInclusive", PlacesAllInclusive},
+ {"PlacesBeachAccess", PlacesBeachAccess},
+ {"PlacesBusinessCenter", PlacesBusinessCenter},
+ {"PlacesCasino", PlacesCasino},
+ {"PlacesChildCare", PlacesChildCare},
+ {"PlacesChildFriendly", PlacesChildFriendly},
+ {"PlacesFitnessCenter", PlacesFitnessCenter},
+ {"PlacesFreeBreakfast", PlacesFreeBreakfast},
+ {"PlacesGolfCourse", PlacesGolfCourse},
+ {"PlacesHotTub", PlacesHotTub},
+ {"PlacesKitchen", PlacesKitchen},
+ {"PlacesPool", PlacesPool},
+ {"PlacesRoomService", PlacesRoomService},
+ {"PlacesRVHookup", PlacesRVHookup},
+ {"PlacesSmokeFree", PlacesSmokeFree},
+ {"PlacesSmokingRooms", PlacesSmokingRooms},
+ {"PlacesSpa", PlacesSpa},
+ {"SocialCake", SocialCake},
+ {"SocialDomain", SocialDomain},
+ {"SocialGroup", SocialGroup},
+ {"SocialGroupAdd", SocialGroupAdd},
+ {"SocialLocationCity", SocialLocationCity},
+ {"SocialMood", SocialMood},
+ {"SocialMoodBad", SocialMoodBad},
+ {"SocialNotifications", SocialNotifications},
+ {"SocialNotificationsActive", SocialNotificationsActive},
+ {"SocialNotificationsNone", SocialNotificationsNone},
+ {"SocialNotificationsOff", SocialNotificationsOff},
+ {"SocialNotificationsPaused", SocialNotificationsPaused},
+ {"SocialPages", SocialPages},
+ {"SocialPartyMode", SocialPartyMode},
+ {"SocialPeople", SocialPeople},
+ {"SocialPeopleOutline", SocialPeopleOutline},
+ {"SocialPerson", SocialPerson},
+ {"SocialPersonAdd", SocialPersonAdd},
+ {"SocialPersonOutline", SocialPersonOutline},
+ {"SocialPlusOne", SocialPlusOne},
+ {"SocialPoll", SocialPoll},
+ {"SocialPublic", SocialPublic},
+ {"SocialSchool", SocialSchool},
+ {"SocialSentimentDissatisfied", SocialSentimentDissatisfied},
+ {"SocialSentimentNeutral", SocialSentimentNeutral},
+ {"SocialSentimentSatisfied", SocialSentimentSatisfied},
+ {"SocialSentimentVeryDissatisfied", SocialSentimentVeryDissatisfied},
+ {"SocialSentimentVerySatisfied", SocialSentimentVerySatisfied},
+ {"SocialShare", SocialShare},
+ {"SocialWhatsHot", SocialWhatsHot},
+ {"ToggleCheckBox", ToggleCheckBox},
+ {"ToggleCheckBoxOutlineBlank", ToggleCheckBoxOutlineBlank},
+ {"ToggleIndeterminateCheckBox", ToggleIndeterminateCheckBox},
+ {"ToggleRadioButtonChecked", ToggleRadioButtonChecked},
+ {"ToggleRadioButtonUnchecked", ToggleRadioButtonUnchecked},
+ {"ToggleStar", ToggleStar},
+ {"ToggleStarBorder", ToggleStarBorder},
+ {"ToggleStarHalf", ToggleStarHalf},
+}
diff --git a/shiny/materialdesign/icons/gen.go b/shiny/materialdesign/icons/gen.go
new file mode 100644
index 0000000..150318b
--- /dev/null
+++ b/shiny/materialdesign/icons/gen.go
@@ -0,0 +1,592 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+package main
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "flag"
+ "fmt"
+ "go/format"
+ "io"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+
+ "golang.org/x/exp/shiny/iconvg"
+ "golang.org/x/image/math/f32"
+)
+
+var mdicons = flag.String("mdicons", "", "The directory on the local file system where "+
+ "https://github.com/google/material-design-icons was checked out",
+)
+
+// outSize is the width and height (in ideal vector space) of the generated
+// IconVG graphic, regardless of the size of the input SVG.
+const outSize = 48
+
+// errSkip deliberately skips generating an icon.
+//
+// When manually debugging one particular icon, it can be useful to add
+// something like:
+//
+// if baseName != "check_box" { return errSkip }
+//
+// at the top of func genFile.
+var errSkip = errors.New("skipping SVG to IconVG conversion")
+
+var (
+ out = new(bytes.Buffer)
+ failures = []string{}
+ varNames = []string{}
+
+ totalFiles int
+ totalIVGBytes int
+ totalPNG24Bytes int
+ totalPNG48Bytes int
+ totalSVGBytes int
+)
+
+var acronyms = map[string]string{
+ "3d": "3D",
+ "ac": "AC",
+ "adb": "ADB",
+ "airplanemode": "AirplaneMode",
+ "atm": "ATM",
+ "av": "AV",
+ "ccw": "CCW",
+ "cw": "CW",
+ "din": "DIN",
+ "dns": "DNS",
+ "dvr": "DVR",
+ "eta": "ETA",
+ "ev": "EV",
+ "gif": "GIF",
+ "gps": "GPS",
+ "hd": "HD",
+ "hdmi": "HDMI",
+ "hdr": "HDR",
+ "http": "HTTP",
+ "https": "HTTPS",
+ "iphone": "IPhone",
+ "iso": "ISO",
+ "jpeg": "JPEG",
+ "markunread": "MarkUnread",
+ "mms": "MMS",
+ "nfc": "NFC",
+ "ondemand": "OnDemand",
+ "pdf": "PDF",
+ "phonelink": "PhoneLink",
+ "png": "PNG",
+ "rss": "RSS",
+ "rv": "RV",
+ "sd": "SD",
+ "sim": "SIM",
+ "sip": "SIP",
+ "sms": "SMS",
+ "streetview": "StreetView",
+ "svideo": "SVideo",
+ "textdirection": "TextDirection",
+ "textsms": "TextSMS",
+ "timelapse": "TimeLapse",
+ "toc": "TOC",
+ "tv": "TV",
+ "usb": "USB",
+ "vpn": "VPN",
+ "wb": "WB",
+ "wc": "WC",
+ "whatshot": "WhatsHot",
+ "wifi": "WiFi",
+}
+
+func upperCase(s string) string {
+ if a, ok := acronyms[s]; ok {
+ return a
+ }
+ if c := s[0]; 'a' <= c && c <= 'z' {
+ return string(c-0x20) + s[1:]
+ }
+ return s
+}
+
+func main() {
+ flag.Parse()
+
+ out.WriteString("// generated by go run gen.go; DO NOT EDIT\n\npackage icons\n\n")
+
+ f, err := os.Open(*mdicons)
+ if err != nil {
+ log.Fatalf("%v\n\nDid you override the -mdicons flag in icons.go?\n\n", err)
+ }
+ defer f.Close()
+ infos, err := f.Readdir(-1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ names := []string{}
+ for _, info := range infos {
+ if !info.IsDir() {
+ continue
+ }
+ name := info.Name()
+ if name[0] == '.' {
+ continue
+ }
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ for _, name := range names {
+ genDir(name)
+ }
+
+ fmt.Fprintf(out,
+ "// In total, %d SVG bytes in %d files (%d PNG bytes at 24px * 24px,\n"+
+ "// %d PNG bytes at 48px * 48px) converted to %d IconVG bytes.\n",
+ totalSVGBytes, totalFiles, totalPNG24Bytes, totalPNG48Bytes, totalIVGBytes)
+
+ if len(failures) != 0 {
+ out.WriteString("\n/*\nFAILURES:\n\n")
+ for _, failure := range failures {
+ out.WriteString(failure)
+ out.WriteByte('\n')
+ }
+ out.WriteString("\n*/")
+ }
+
+ raw := out.Bytes()
+ formatted, err := format.Source(raw)
+ if err != nil {
+ log.Fatalf("gofmt failed: %v\n\nGenerated code:\n%s", err, raw)
+ }
+ if err := os.WriteFile("data.go", formatted, 0644); err != nil {
+ log.Fatalf("WriteFile failed: %s\n", err)
+ }
+
+ // Generate data_test.go. The code immediately above generates data.go.
+ {
+ b := new(bytes.Buffer)
+ b.WriteString("// generated by go run gen.go; DO NOT EDIT\n\npackage icons\n\n")
+ b.WriteString("var list = []struct{ name string; data []byte } {\n")
+ for _, v := range varNames {
+ fmt.Fprintf(b, "{%q, %s},\n", v, v)
+ }
+ b.WriteString("}\n\n")
+ raw := b.Bytes()
+ formatted, err := format.Source(raw)
+ if err != nil {
+ log.Fatalf("gofmt failed: %v\n\nGenerated code:\n%s", err, raw)
+ }
+ if err := os.WriteFile("data_test.go", formatted, 0644); err != nil {
+ log.Fatalf("WriteFile failed: %s\n", err)
+ }
+ }
+}
+
+func genDir(dirName string) {
+ fqPNGDirName := filepath.FromSlash(path.Join(*mdicons, dirName, "1x_web"))
+ fqSVGDirName := filepath.FromSlash(path.Join(*mdicons, dirName, "svg/production"))
+ f, err := os.Open(fqSVGDirName)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+
+ infos, err := f.Readdir(-1)
+ if err != nil {
+ log.Fatal(err)
+ }
+ baseNames, fileNames, sizes := []string{}, map[string]string{}, map[string]int{}
+ for _, info := range infos {
+ name := info.Name()
+
+ if !strings.HasPrefix(name, "ic_") || skippedFiles[[2]string{dirName, name}] {
+ continue
+ }
+ size := 0
+ switch {
+ case strings.HasSuffix(name, "_12px.svg"):
+ size = 12
+ case strings.HasSuffix(name, "_18px.svg"):
+ size = 18
+ case strings.HasSuffix(name, "_24px.svg"):
+ size = 24
+ case strings.HasSuffix(name, "_36px.svg"):
+ size = 36
+ case strings.HasSuffix(name, "_48px.svg"):
+ size = 48
+ default:
+ continue
+ }
+
+ baseName := name[3 : len(name)-9]
+ if prevSize, ok := sizes[baseName]; ok {
+ if size > prevSize {
+ fileNames[baseName] = name
+ sizes[baseName] = size
+ }
+ } else {
+ fileNames[baseName] = name
+ sizes[baseName] = size
+ baseNames = append(baseNames, baseName)
+ }
+ }
+
+ sort.Strings(baseNames)
+ for _, baseName := range baseNames {
+ fileName := fileNames[baseName]
+ err := genFile(fqSVGDirName, dirName, baseName, fileName, float32(sizes[baseName]))
+ if err == errSkip {
+ continue
+ }
+ if err != nil {
+ failures = append(failures, fmt.Sprintf("%v/svg/production/%v: %v", dirName, fileName, err))
+ continue
+ }
+ totalPNG24Bytes += pngSize(fqPNGDirName, dirName, baseName, 24)
+ totalPNG48Bytes += pngSize(fqPNGDirName, dirName, baseName, 48)
+ }
+}
+
+func pngSize(fqPNGDirName, dirName, baseName string, targetSize int) int {
+ for _, size := range [...]int{48, 24, 18} {
+ if size > targetSize {
+ continue
+ }
+ fInfo, err := os.Stat(filepath.Join(fqPNGDirName,
+ fmt.Sprintf("ic_%s_black_%ddp.png", baseName, size)))
+ if err != nil {
+ continue
+ }
+ return int(fInfo.Size())
+ }
+ failures = append(failures,
+ fmt.Sprintf("no PNG found for %s/1x_web/ic_%s_black_{48,24,18}dp.png", dirName, baseName))
+ return 0
+}
+
+type SVG struct {
+ Width float32 `xml:"where,attr"`
+ Height float32 `xml:"height,attr"`
+ ViewBox string `xml:"viewBox,attr"`
+ Paths []Path `xml:"path"`
+ // Some of the SVG files contain <circle> elements, not just <path>
+ // elements. IconVG doesn't have circles per se. Instead, we convert such
+ // circles to be paired arcTo commands, tacked on to the first path.
+ //
+ // In general, this isn't correct if the circles and the path overlap, but
+ // that doesn't happen in the specific case of the Material Design icons.
+ Circles []Circle `xml:"circle"`
+}
+
+type Path struct {
+ D string `xml:"d,attr"`
+ Fill string `xml:"fill,attr"`
+ FillOpacity *float32 `xml:"fill-opacity,attr"`
+ Opacity *float32 `xml:"opacity,attr"`
+}
+
+type Circle struct {
+ Cx float32 `xml:"cx,attr"`
+ Cy float32 `xml:"cy,attr"`
+ R float32 `xml:"r,attr"`
+}
+
+var skippedPaths = map[string]string{
+ // hardware/svg/production/ic_scanner_48px.svg contains a filled white
+ // rectangle that is overwritten by the subsequent path.
+ //
+ // See https://github.com/google/material-design-icons/issues/490
+ //
+ // Matches <path fill="#fff" d="M16 34h22v4H16z"/>
+ "M16 34h22v4H16z": "#fff",
+
+ // device/svg/production/ic_airplanemode_active_48px.svg and
+ // maps/svg/production/ic_flight_48px.svg contain a degenerate path that
+ // contains only one moveTo op.
+ //
+ // See https://github.com/google/material-design-icons/issues/491
+ //
+ // Matches <path d="M20.36 18"/>
+ "M20.36 18": "",
+}
+
+var skippedFiles = map[[2]string]bool{
+ // ic_play_circle_filled_white_48px.svg is just the same as
+ // ic_play_circle_filled_48px.svg with an explicit fill="#fff".
+ {"av", "ic_play_circle_filled_white_48px.svg"}: true,
+}
+
+func genFile(fqSVGDirName, dirName, baseName, fileName string, size float32) error {
+ fqFileName := filepath.Join(fqSVGDirName, fileName)
+ svgData, err := os.ReadFile(fqFileName)
+ if err != nil {
+ return err
+ }
+
+ varName := upperCase(dirName)
+ for _, s := range strings.Split(baseName, "_") {
+ varName += upperCase(s)
+ }
+ fmt.Fprintf(out, "var %s = []byte{", varName)
+ defer fmt.Fprintf(out, "\n}\n\n")
+ varNames = append(varNames, varName)
+
+ var enc iconvg.Encoder
+ enc.Reset(iconvg.Metadata{
+ ViewBox: iconvg.Rectangle{
+ Min: f32.Vec2{-24, -24},
+ Max: f32.Vec2{+24, +24},
+ },
+ Palette: iconvg.DefaultPalette,
+ })
+
+ g := &SVG{}
+ if err := xml.Unmarshal(svgData, g); err != nil {
+ return err
+ }
+
+ var vbx, vby float32
+ for i, v := range strings.Split(g.ViewBox, " ") {
+ f, err := strconv.ParseFloat(v, 32)
+ if err != nil {
+ return err
+ }
+ switch i {
+ case 0:
+ vbx = float32(f)
+ case 1:
+ vby = float32(f)
+ }
+ }
+ offset := f32.Vec2{
+ vbx * outSize / size,
+ vby * outSize / size,
+ }
+
+ // adjs maps from opacity to a cReg adj value.
+ adjs := map[float32]uint8{}
+
+ for _, p := range g.Paths {
+ if fill, ok := skippedPaths[p.D]; ok && fill == p.Fill {
+ continue
+ }
+ if err := genPath(&enc, &p, adjs, size, offset, g.Circles); err != nil {
+ return err
+ }
+ g.Circles = nil
+ }
+
+ if len(g.Circles) != 0 {
+ if err := genPath(&enc, &Path{}, adjs, size, offset, g.Circles); err != nil {
+ return err
+ }
+ g.Circles = nil
+ }
+
+ ivgData, err := enc.Bytes()
+ if err != nil {
+ return err
+ }
+ for i, x := range ivgData {
+ if i&0x0f == 0x00 {
+ out.WriteByte('\n')
+ }
+ fmt.Fprintf(out, "%#02x, ", x)
+ }
+
+ totalFiles++
+ totalSVGBytes += len(svgData)
+ totalIVGBytes += len(ivgData)
+ return nil
+}
+
+func genPath(enc *iconvg.Encoder, p *Path, adjs map[float32]uint8, size float32, offset f32.Vec2, circles []Circle) error {
+ adj := uint8(0)
+ opacity := float32(1)
+ if p.Opacity != nil {
+ opacity = *p.Opacity
+ } else if p.FillOpacity != nil {
+ opacity = *p.FillOpacity
+ }
+ if opacity != 1 {
+ var ok bool
+ if adj, ok = adjs[opacity]; !ok {
+ adj = uint8(len(adjs) + 1)
+ adjs[opacity] = adj
+ // Set CREG[0-adj] to be a blend of transparent (0x7f) and the
+ // first custom palette color (0x80).
+ enc.SetCReg(adj, false, iconvg.BlendColor(uint8(opacity*0xff), 0x7f, 0x80))
+ }
+ }
+
+ needStartPath := true
+ if p.D != "" {
+ needStartPath = false
+ if err := genPathData(enc, adj, p.D, size, offset); err != nil {
+ return err
+ }
+ }
+
+ for _, c := range circles {
+ // Normalize.
+ cx := c.Cx * outSize / size
+ cx -= outSize/2 + offset[0]
+ cy := c.Cy * outSize / size
+ cy -= outSize/2 + offset[1]
+ r := c.R * outSize / size
+
+ if needStartPath {
+ needStartPath = false
+ enc.StartPath(adj, cx-r, cy)
+ } else {
+ enc.ClosePathAbsMoveTo(cx-r, cy)
+ }
+
+ // Convert a circle to two relative arcTo ops, each of 180 degrees.
+ // We can't use one 360 degree arcTo as the start and end point
+ // would be coincident and the computation is degenerate.
+ enc.RelArcTo(r, r, 0, false, true, +2*r, 0)
+ enc.RelArcTo(r, r, 0, false, true, -2*r, 0)
+ }
+
+ enc.ClosePathEndPath()
+ return nil
+}
+
+func genPathData(enc *iconvg.Encoder, adj uint8, pathData string, size float32, offset f32.Vec2) error {
+ if strings.HasSuffix(pathData, "z") {
+ pathData = pathData[:len(pathData)-1]
+ }
+ r := strings.NewReader(pathData)
+
+ var args [6]float32
+ op, relative, started := byte(0), false, false
+ for {
+ b, err := r.ReadByte()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return err
+ }
+
+ switch {
+ case b == ' ':
+ continue
+ case 'A' <= b && b <= 'Z':
+ op, relative = b, false
+ case 'a' <= b && b <= 'z':
+ op, relative = b, true
+ default:
+ r.UnreadByte()
+ }
+
+ n := 0
+ switch op {
+ case 'L', 'l', 'T', 't':
+ n = 2
+ case 'Q', 'q', 'S', 's':
+ n = 4
+ case 'C', 'c':
+ n = 6
+ case 'H', 'h', 'V', 'v':
+ n = 1
+ case 'M', 'm':
+ n = 2
+ case 'Z', 'z':
+ default:
+ return fmt.Errorf("unknown opcode %c\n", b)
+ }
+
+ scan(&args, r, n)
+ normalize(&args, n, op, size, offset, relative)
+
+ switch op {
+ case 'L':
+ enc.AbsLineTo(args[0], args[1])
+ case 'l':
+ enc.RelLineTo(args[0], args[1])
+ case 'T':
+ enc.AbsSmoothQuadTo(args[0], args[1])
+ case 't':
+ enc.RelSmoothQuadTo(args[0], args[1])
+ case 'Q':
+ enc.AbsQuadTo(args[0], args[1], args[2], args[3])
+ case 'q':
+ enc.RelQuadTo(args[0], args[1], args[2], args[3])
+ case 'S':
+ enc.AbsSmoothCubeTo(args[0], args[1], args[2], args[3])
+ case 's':
+ enc.RelSmoothCubeTo(args[0], args[1], args[2], args[3])
+ case 'C':
+ enc.AbsCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ case 'c':
+ enc.RelCubeTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ case 'H':
+ enc.AbsHLineTo(args[0])
+ case 'h':
+ enc.RelHLineTo(args[0])
+ case 'V':
+ enc.AbsVLineTo(args[0])
+ case 'v':
+ enc.RelVLineTo(args[0])
+ case 'M':
+ if !started {
+ started = true
+ enc.StartPath(adj, args[0], args[1])
+ } else {
+ enc.ClosePathAbsMoveTo(args[0], args[1])
+ }
+ case 'm':
+ enc.ClosePathRelMoveTo(args[0], args[1])
+ }
+ }
+ return nil
+}
+
+func scan(args *[6]float32, r *strings.Reader, n int) {
+ for i := 0; i < n; i++ {
+ for {
+ if b, _ := r.ReadByte(); b != ' ' {
+ r.UnreadByte()
+ break
+ }
+ }
+ fmt.Fscanf(r, "%f", &args[i])
+ }
+}
+
+func atof(s []byte) (float32, error) {
+ f, err := strconv.ParseFloat(string(s), 32)
+ if err != nil {
+ return 0, fmt.Errorf("could not parse %q as a float32: %v", s, err)
+ }
+ return float32(f), err
+}
+
+func normalize(args *[6]float32, n int, op byte, size float32, offset f32.Vec2, relative bool) {
+ for i := 0; i < n; i++ {
+ args[i] *= outSize / size
+ if relative {
+ continue
+ }
+ args[i] -= outSize / 2
+ switch {
+ case n != 1:
+ args[i] -= offset[i&0x01]
+ case op == 'H':
+ args[i] -= offset[0]
+ case op == 'V':
+ args[i] -= offset[1]
+ }
+ }
+}
diff --git a/shiny/materialdesign/icons/icons.go b/shiny/materialdesign/icons/icons.go
new file mode 100644
index 0000000..ac7464f
--- /dev/null
+++ b/shiny/materialdesign/icons/icons.go
@@ -0,0 +1,12 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run gen.go -mdicons=/path/to/the/material-design-icons
+
+// Package icons contains the Material Design icon set, in the IconVG vector
+// graphic format.
+//
+// See https://design.google.com/icons/ and
+// https://godoc.org/golang.org/x/exp/shiny/iconvg
+package icons
diff --git a/shiny/materialdesign/icons/icons_test.go b/shiny/materialdesign/icons/icons_test.go
new file mode 100644
index 0000000..feb3df1
--- /dev/null
+++ b/shiny/materialdesign/icons/icons_test.go
@@ -0,0 +1,64 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package icons
+
+import (
+ "image"
+ "image/draw"
+ "image/png"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/exp/shiny/iconvg"
+)
+
+func encodePNG(dstFilename string, src image.Image) error {
+ f, err := os.Create(dstFilename)
+ if err != nil {
+ return err
+ }
+ encErr := png.Encode(f, src)
+ closeErr := f.Close()
+ if encErr != nil {
+ return encErr
+ }
+ return closeErr
+}
+
+func TestManualInspection(t *testing.T) {
+ // Set this to a non-empty string such as "/tmp/mdicons" to manually
+ // inspect the icons.
+ const tmpDir = ""
+
+ if tmpDir == "" {
+ t.Skip("no tmpDir specified")
+ }
+ t.Errorf("tmpDir %q is a non-empty string; do not commit code changes", tmpDir)
+
+ dst := image.NewAlpha(image.Rect(0, 0, 256, 256))
+ z := &iconvg.Rasterizer{}
+ z.SetDstImage(dst, dst.Bounds(), draw.Src)
+ for _, v := range list {
+ if err := iconvg.Decode(z, v.data, nil); err != nil {
+ t.Errorf("%q: %v", v.name, err)
+ continue
+ }
+ filename := filepath.Join(tmpDir, v.name+".png")
+ if err := encodePNG(filename, dst); err != nil {
+ t.Error(err)
+ }
+ t.Logf("wrote %s", filename)
+ }
+}
+
+func TestDecodeAll(t *testing.T) {
+ for _, v := range list {
+ if err := iconvg.Decode(nil, v.data, nil); err != nil {
+ t.Errorf("%q: %v", v.name, err)
+ continue
+ }
+ }
+}
diff --git a/shiny/screen/screen.go b/shiny/screen/screen.go
new file mode 100644
index 0000000..5d89fe8
--- /dev/null
+++ b/shiny/screen/screen.go
@@ -0,0 +1,354 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package screen provides interfaces for portable two-dimensional graphics and
+// input events.
+//
+// Screens are not created directly. Instead, driver packages provide access to
+// the screen through a Main function that is designed to be called by the
+// program's main function. The golang.org/x/exp/shiny/driver package provides
+// the default driver for the system, such as the X11 driver for desktop Linux,
+// but other drivers, such as the OpenGL driver, can be explicitly invoked by
+// calling that driver's Main function. To use the default driver:
+//
+// package main
+//
+// import (
+// "golang.org/x/exp/shiny/driver"
+// "golang.org/x/exp/shiny/screen"
+// "golang.org/x/mobile/event/lifecycle"
+// )
+//
+// func main() {
+// driver.Main(func(s screen.Screen) {
+// w, err := s.NewWindow(nil)
+// if err != nil {
+// handleError(err)
+// return
+// }
+// defer w.Release()
+//
+// for {
+// switch e := w.NextEvent().(type) {
+// case lifecycle.Event:
+// if e.To == lifecycle.StageDead {
+// return
+// }
+// etc
+// case etc:
+// etc
+// }
+// }
+// })
+// }
+//
+// Complete examples can be found in the shiny/example directory.
+//
+// Each driver package provides Screen, Buffer, Texture and Window
+// implementations that work together. Such types are interface types because
+// this package is driver-independent, but those interfaces aren't expected to
+// be implemented outside of drivers. For example, a driver's Window
+// implementation will generally work only with that driver's Buffer
+// implementation, and will not work with an arbitrary type that happens to
+// implement the Buffer methods.
+package screen // import "golang.org/x/exp/shiny/screen"
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "unicode/utf8"
+
+ "golang.org/x/image/math/f64"
+)
+
+// TODO: specify image format (Alpha or Gray, not just RGBA) for NewBuffer
+// and/or NewTexture?
+
+// Screen creates Buffers, Textures and Windows.
+type Screen interface {
+ // NewBuffer returns a new Buffer for this screen.
+ NewBuffer(size image.Point) (Buffer, error)
+
+ // NewTexture returns a new Texture for this screen.
+ NewTexture(size image.Point) (Texture, error)
+
+ // NewWindow returns a new Window for this screen.
+ //
+ // A nil opts is valid and means to use the default option values.
+ NewWindow(opts *NewWindowOptions) (Window, error)
+}
+
+// TODO: rename Buffer to Image, to be less confusing with a Window's back and
+// front buffers.
+
+// Buffer is an in-memory pixel buffer. Its pixels can be modified by any Go
+// code that takes an *image.RGBA, such as the standard library's image/draw
+// package. A Buffer is essentially an *image.RGBA, but not all *image.RGBA
+// values (including those returned by image.NewRGBA) are valid Buffers, as a
+// driver may assume that the memory backing a Buffer's pixels are specially
+// allocated.
+//
+// To see a Buffer's contents on a screen, upload it to a Texture (and then
+// draw the Texture on a Window) or upload it directly to a Window.
+//
+// When specifying a sub-Buffer via Upload, a Buffer's top-left pixel is always
+// (0, 0) in its own coordinate space.
+type Buffer interface {
+ // Release releases the Buffer's resources, after all pending uploads and
+ // draws resolve.
+ //
+ // The behavior of the Buffer after Release, whether calling its methods or
+ // passing it as an argument, is undefined.
+ Release()
+
+ // Size returns the size of the Buffer's image.
+ Size() image.Point
+
+ // Bounds returns the bounds of the Buffer's image. It is equal to
+ // image.Rectangle{Max: b.Size()}.
+ Bounds() image.Rectangle
+
+ // RGBA returns the pixel buffer as an *image.RGBA.
+ //
+ // Its contents should not be accessed while the Buffer is uploading.
+ //
+ // The contents of the returned *image.RGBA's Pix field (of type []byte)
+ // can be modified at other times, but that Pix slice itself (i.e. its
+ // underlying pointer, length and capacity) should not be modified at any
+ // time.
+ //
+ // The following is valid:
+ // m := buffer.RGBA()
+ // if len(m.Pix) >= 4 {
+ // m.Pix[0] = 0xff
+ // m.Pix[1] = 0x00
+ // m.Pix[2] = 0x00
+ // m.Pix[3] = 0xff
+ // }
+ // or, equivalently:
+ // m := buffer.RGBA()
+ // m.SetRGBA(m.Rect.Min.X, m.Rect.Min.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
+ // and using the standard library's image/draw package is also valid:
+ // dst := buffer.RGBA()
+ // draw.Draw(dst, dst.Bounds(), etc)
+ // but the following is invalid:
+ // m := buffer.RGBA()
+ // m.Pix = anotherByteSlice
+ // and so is this:
+ // *buffer.RGBA() = anotherImageRGBA
+ RGBA() *image.RGBA
+}
+
+// Texture is a pixel buffer, but not one that is directly accessible as a
+// []byte. Conceptually, it could live on a GPU, in another process or even be
+// across a network, instead of on a CPU in this process.
+//
+// Buffers can be uploaded to Textures, and Textures can be drawn on Windows.
+//
+// When specifying a sub-Texture via Draw, a Texture's top-left pixel is always
+// (0, 0) in its own coordinate space.
+type Texture interface {
+ // Release releases the Texture's resources, after all pending uploads and
+ // draws resolve.
+ //
+ // The behavior of the Texture after Release, whether calling its methods
+ // or passing it as an argument, is undefined.
+ Release()
+
+ // Size returns the size of the Texture's image.
+ Size() image.Point
+
+ // Bounds returns the bounds of the Texture's image. It is equal to
+ // image.Rectangle{Max: t.Size()}.
+ Bounds() image.Rectangle
+
+ Uploader
+
+ // TODO: also implement Drawer? If so, merge the Uploader and Drawer
+ // interfaces??
+}
+
+// EventDeque is an infinitely buffered double-ended queue of events.
+type EventDeque interface {
+ // Send adds an event to the end of the deque. They are returned by
+ // NextEvent in FIFO order.
+ Send(event interface{})
+
+ // SendFirst adds an event to the start of the deque. They are returned by
+ // NextEvent in LIFO order, and have priority over events sent via Send.
+ SendFirst(event interface{})
+
+ // NextEvent returns the next event in the deque. It blocks until such an
+ // event has been sent.
+ //
+ // Typical event types include:
+ // - lifecycle.Event
+ // - size.Event
+ // - paint.Event
+ // - key.Event
+ // - mouse.Event
+ // - touch.Event
+ // from the golang.org/x/mobile/event/... packages. Other packages may send
+ // events, of those types above or of other types, via Send or SendFirst.
+ NextEvent() interface{}
+
+ // TODO: LatestLifecycleEvent? Is that still worth it if the
+ // lifecycle.Event struct type loses its DrawContext field?
+
+ // TODO: LatestSizeEvent?
+}
+
+// Window is a top-level, double-buffered GUI window.
+type Window interface {
+ // Release closes the window.
+ //
+ // The behavior of the Window after Release, whether calling its methods or
+ // passing it as an argument, is undefined.
+ Release()
+
+ EventDeque
+
+ Uploader
+
+ Drawer
+
+ // Publish flushes any pending Upload and Draw calls to the window, and
+ // swaps the back buffer to the front.
+ Publish() PublishResult
+}
+
+// PublishResult is the result of an Window.Publish call.
+type PublishResult struct {
+ // BackBufferPreserved is whether the contents of the back buffer was
+ // preserved. If false, the contents are undefined.
+ BackBufferPreserved bool
+}
+
+// NewWindowOptions are optional arguments to NewWindow.
+type NewWindowOptions struct {
+ // Width and Height specify the dimensions of the new window. If Width
+ // or Height are zero, a driver-dependent default will be used for each
+ // zero value dimension.
+ Width, Height int
+
+ // Title specifies the window title.
+ Title string
+
+ // TODO: fullscreen, icon, cursorHidden?
+}
+
+// GetTitle returns a sanitized form of o.Title. In particular, its length will
+// not exceed 4096, and it may be further truncated so that it is valid UTF-8
+// and will not contain the NUL byte.
+//
+// o may be nil, in which case "" is returned.
+func (o *NewWindowOptions) GetTitle() string {
+ if o == nil {
+ return ""
+ }
+ return sanitizeUTF8(o.Title, 4096)
+}
+
+func sanitizeUTF8(s string, n int) string {
+ if n < len(s) {
+ s = s[:n]
+ }
+ i := 0
+ for i < len(s) {
+ r, n := utf8.DecodeRuneInString(s[i:])
+ if r == 0 || (r == utf8.RuneError && n == 1) {
+ break
+ }
+ i += n
+ }
+ return s[:i]
+}
+
+// Uploader is something you can upload a Buffer to.
+type Uploader interface {
+ // Upload uploads the sub-Buffer defined by src and sr to the destination
+ // (the method receiver), such that sr.Min in src-space aligns with dp in
+ // dst-space. The destination's contents are overwritten; the draw operator
+ // is implicitly draw.Src.
+ //
+ // It is valid to upload a Buffer while another upload of the same Buffer
+ // is in progress, but a Buffer's image.RGBA pixel contents should not be
+ // accessed while it is uploading. A Buffer is re-usable, in that its pixel
+ // contents can be further modified, once all outstanding calls to Upload
+ // have returned.
+ //
+ // TODO: make it optional that a Buffer's contents is preserved after
+ // Upload? Undoing a swizzle is a non-trivial amount of work, and can be
+ // redundant if the next paint cycle starts by clearing the buffer.
+ //
+ // When uploading to a Window, there will not be any visible effect until
+ // Publish is called.
+ Upload(dp image.Point, src Buffer, sr image.Rectangle)
+
+ // Fill fills that part of the destination (the method receiver) defined by
+ // dr with the given color.
+ //
+ // When filling a Window, there will not be any visible effect until
+ // Publish is called.
+ Fill(dr image.Rectangle, src color.Color, op draw.Op)
+}
+
+// TODO: have a Downloader interface? Not every graphical app needs to be
+// interactive or involve a window. You could use the GPU for hardware-
+// accelerated image manipulation: upload a buffer, do some texture ops, then
+// download the result.
+
+// Drawer is something you can draw Textures on.
+//
+// Draw is the most general purpose of this interface's methods. It supports
+// arbitrary affine transformations, such as translations, scales and
+// rotations.
+//
+// Copy and Scale are more specific versions of Draw. The affected dst pixels
+// are an axis-aligned rectangle, quantized to the pixel grid. Copy copies
+// pixels in a 1:1 manner, Scale is more general. They have simpler parameters
+// than Draw, using ints instead of float64s.
+//
+// When drawing on a Window, there will not be any visible effect until Publish
+// is called.
+type Drawer interface {
+ // Draw draws the sub-Texture defined by src and sr to the destination (the
+ // method receiver). src2dst defines how to transform src coordinates to
+ // dst coordinates. For example, if src2dst is the matrix
+ //
+ // m00 m01 m02
+ // m10 m11 m12
+ //
+ // then the src-space point (sx, sy) maps to the dst-space point
+ // (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12).
+ Draw(src2dst f64.Aff3, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
+
+ // DrawUniform is like Draw except that the src is a uniform color instead
+ // of a Texture.
+ DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *DrawOptions)
+
+ // Copy copies the sub-Texture defined by src and sr to the destination
+ // (the method receiver), such that sr.Min in src-space aligns with dp in
+ // dst-space.
+ Copy(dp image.Point, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
+
+ // Scale scales the sub-Texture defined by src and sr to the destination
+ // (the method receiver), such that sr in src-space is mapped to dr in
+ // dst-space.
+ Scale(dr image.Rectangle, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
+}
+
+// These draw.Op constants are provided so that users of this package don't
+// have to explicitly import "image/draw".
+const (
+ Over = draw.Over
+ Src = draw.Src
+)
+
+// DrawOptions are optional arguments to Draw.
+type DrawOptions struct {
+ // TODO: transparency in [0x0000, 0xffff]?
+ // TODO: scaler (nearest neighbor vs linear)?
+}
diff --git a/shiny/screen/screen_test.go b/shiny/screen/screen_test.go
new file mode 100644
index 0000000..bc95011
--- /dev/null
+++ b/shiny/screen/screen_test.go
@@ -0,0 +1,53 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package screen
+
+import (
+ "testing"
+)
+
+func TestSanitizeUTF8(t *testing.T) {
+ const n = 8
+
+ testCases := []struct {
+ s, want string
+ }{
+ {"", ""},
+ {"a", "a"},
+ {"a\x00", "a"},
+ {"a\x80", "a"},
+ {"\x00a", ""},
+ {"\x80a", ""},
+ {"abc", "abc"},
+ {"foo b\x00r qux", "foo b"},
+ {"foo b\x80r qux", "foo b"},
+ {"foo b\xffr qux", "foo b"},
+
+ // "\xc3\xa0" is U+00E0 LATIN SMALL LETTER A WITH GRAVE.
+ {"\xc3\xa0pqrs", "\u00e0pqrs"},
+ {"a\xc3\xa0pqrs", "a\u00e0pqrs"},
+ {"ab\xc3\xa0pqrs", "ab\u00e0pqrs"},
+ {"abc\xc3\xa0pqrs", "abc\u00e0pqr"},
+ {"abcd\xc3\xa0pqrs", "abcd\u00e0pq"},
+ {"abcde\xc3\xa0pqrs", "abcde\u00e0p"},
+ {"abcdef\xc3\xa0pqrs", "abcdef\u00e0"},
+ {"abcdefg\xc3\xa0pqrs", "abcdefg"},
+ {"abcdefgh\xc3\xa0pqrs", "abcdefgh"},
+ {"abcdefghi\xc3\xa0pqrs", "abcdefgh"},
+ {"abcdefghij\xc3\xa0pqrs", "abcdefgh"},
+
+ // "δΈ–" is "\xe4\xb8\x96".
+ // "η•Œ" is "\xe7\x95\x8c".
+ {"H δΈ–η•Œ", "H δΈ–η•Œ"},
+ {"Hi δΈ–η•Œ", "Hi δΈ–"},
+ {"Hello δΈ–η•Œ", "Hello "},
+ }
+
+ for _, tc := range testCases {
+ if got := sanitizeUTF8(tc.s, n); got != tc.want {
+ t.Errorf("sanitizeUTF8(%q): got %q, want %q", tc.s, got, tc.want)
+ }
+ }
+}
diff --git a/shiny/text/caret.go b/shiny/text/caret.go
new file mode 100644
index 0000000..24daa96
--- /dev/null
+++ b/shiny/text/caret.go
@@ -0,0 +1,795 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package text
+
+// TODO: do we care about "\n" vs "\r" vs "\r\n"? We only recognize "\n" for
+// now.
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "strings"
+ "unicode/utf8"
+
+ "golang.org/x/image/math/fixed"
+)
+
+// Caret is a location in a Frame's text, and is the mechanism for adding and
+// removing bytes of text. Conceptually, a Caret and a Frame's text is like an
+// int c and a []byte t such that the text before and after that Caret is t[:c]
+// and t[c:]. That byte-count location remains unchanged even when a Frame is
+// re-sized and laid out into a new tree of Paragraphs, Lines and Boxes.
+//
+// A Frame can have multiple open Carets. For example, the beginning and end of
+// a text selection can be represented by two Carets. Multiple Carets for the
+// one Frame are not safe to use concurrently, but it is valid to interleave
+// such operations sequentially. For example, if two Carets c0 and c1 for the
+// one Frame are positioned at the 10th and 20th byte, and 4 bytes are written
+// to c0, inserting what becomes the equivalent of text[10:14], then c0's
+// position is updated to be 14 but c1's position is also updated to be 24.
+type Caret struct {
+ f *Frame
+
+ // caretsIndex is the index of this Caret in the f.carets slice.
+ caretsIndex int
+
+ // seqNum is the Frame f's sequence number for which this Caret's cached p,
+ // l, b and k fields are valid. If f has been modified since then, those
+ // fields will have to be re-calculated based on the pos field (which is
+ // always valid).
+ //
+ // TODO: when re-calculating p, l, b and k, be more efficient than a linear
+ // scan from the start or end?
+ seqNum uint64
+
+ // p, l and b cache the index of the Caret's Paragraph, Line and Box. None
+ // of these values can be zero.
+ p, l, b int32
+
+ // k caches the Caret's position in the text, in Frame.text order. It is
+ // valid to index the Frame.text slice with k, analogous to the Box.i and
+ // Box.j fields. For a Caret c, letting bb := c.f.boxes[c.b], an invariant
+ // is that bb.i <= c.k && c.k <= bb.j if the cache is valid (i.e. the
+ // Caret's seqNum equals the Frame's seqNum).
+ k int32
+
+ // pos is the Caret's position in the text, in layout order. It is the "c"
+ // as in "t[:c]" in the doc comment for type Caret above. It is not valid
+ // to index the Frame.text slice with pos, since the Frame.text slice does
+ // not necessarily hold the textual content in layout order.
+ pos int32
+
+ tmp [utf8.UTFMax]byte
+}
+
+// Close closes the Caret.
+func (c *Caret) Close() error {
+ i, j := c.caretsIndex, len(c.f.carets)-1
+
+ // Swap c with the last element of c.f.carets.
+ if i != j {
+ other := c.f.carets[j]
+ other.caretsIndex = i
+ c.f.carets[i] = other
+ }
+
+ c.f.carets[j] = nil
+ c.f.carets = c.f.carets[:j]
+ *c = Caret{}
+ return nil
+}
+
+type leanResult int
+
+const (
+ // leanOK means that the lean changed the Caret's Box.
+ leanOK leanResult = iota
+ // leanFailedEOF means that the lean did not change the Caret's Box,
+ // because the Caret was already at the end / beginning of the Frame (when
+ // leaning forwards / backwards).
+ leanFailedEOF
+ // leanFailedNotEOB means that the lean did not change the Caret's Box,
+ // because the Caret was not placed at the end / beginning of the Box (when
+ // leaning forwards / backwards).
+ leanFailedNotEOB
+)
+
+// leanForwards moves the Caret from the right end of one Box to the left end
+// of the next Box, crossing Lines and Paragraphs to find that next Box. It
+// returns whether the Caret moved to a different Box.
+//
+// Diagramatically, suppose we have two adjacent boxes (shown by square
+// brackets below), with the Caret (an integer location called Caret.pos in the
+// Frame's text) in the middle of the "foo2bar3" word:
+//
+// [foo0 foo1 foo2]^[bar3 bar4 bar5]
+//
+// leanForwards moves Caret.k from fooBox.j to barBox.i, also updating the
+// Caret's p, l and b. Caret.pos remains unchanged.
+func (c *Caret) leanForwards() leanResult {
+ if c.k != c.f.boxes[c.b].j {
+ return leanFailedNotEOB
+ }
+ if nextB := c.f.boxes[c.b].next; nextB != 0 {
+ c.b = nextB
+ c.k = c.f.boxes[c.b].i
+ return leanOK
+ }
+ if nextL := c.f.lines[c.l].next; nextL != 0 {
+ c.l = nextL
+ c.b = c.f.lines[c.l].firstB
+ c.k = c.f.boxes[c.b].i
+ return leanOK
+ }
+ if nextP := c.f.paragraphs[c.p].next; nextP != 0 {
+ c.p = nextP
+ c.l = c.f.paragraphs[c.p].firstL
+ c.b = c.f.lines[c.l].firstB
+ c.k = c.f.boxes[c.b].i
+ return leanOK
+ }
+ return leanFailedEOF
+}
+
+// leanBackwards is like leanForwards but in the other direction.
+func (c *Caret) leanBackwards() leanResult {
+ if c.k != c.f.boxes[c.b].i {
+ return leanFailedNotEOB
+ }
+ if prevB := c.f.boxes[c.b].prev; prevB != 0 {
+ c.b = prevB
+ c.k = c.f.boxes[c.b].j
+ return leanOK
+ }
+ if prevL := c.f.lines[c.l].prev; prevL != 0 {
+ c.l = prevL
+ c.b = c.f.lines[c.l].lastBox(c.f)
+ c.k = c.f.boxes[c.b].j
+ return leanOK
+ }
+ if prevP := c.f.paragraphs[c.p].prev; prevP != 0 {
+ c.p = prevP
+ c.l = c.f.paragraphs[c.p].lastLine(c.f)
+ c.b = c.f.lines[c.l].lastBox(c.f)
+ c.k = c.f.boxes[c.b].j
+ return leanOK
+ }
+ return leanFailedEOF
+}
+
+func (c *Caret) seekStart() {
+ c.p = c.f.firstP
+ c.l = c.f.paragraphs[c.p].firstL
+ c.b = c.f.lines[c.l].firstB
+ c.k = c.f.boxes[c.b].i
+ c.pos = 0
+}
+
+func (c *Caret) seekEnd() {
+ c.p = c.f.lastParagraph()
+ c.l = c.f.paragraphs[c.p].lastLine(c.f)
+ c.b = c.f.lines[c.l].lastBox(c.f)
+ c.k = c.f.boxes[c.b].j
+ c.pos = int32(c.f.len)
+}
+
+// calculatePLBK ensures that the Caret's cached p, l, b and k fields are
+// valid.
+func (c *Caret) calculatePLBK() {
+ if c.seqNum != c.f.seqNum {
+ c.seek(c.pos)
+ }
+}
+
+// Seek satisfies the io.Seeker interface.
+func (c *Caret) Seek(offset int64, whence int) (int64, error) {
+ switch whence {
+ case SeekSet:
+ // No-op.
+ case SeekCur:
+ offset += int64(c.pos)
+ case SeekEnd:
+ offset += int64(c.f.len)
+ default:
+ return 0, errors.New("text: invalid seek whence")
+ }
+ if offset < 0 {
+ return 0, errors.New("text: negative seek position")
+ }
+ if offset > int64(c.f.len) {
+ offset = int64(c.f.len)
+ }
+ c.seek(int32(offset))
+ return offset, nil
+}
+
+func (c *Caret) seek(off int32) {
+ delta := off - c.pos
+ // If the new offset is closer to the start or the end than to the current
+ // c.pos, or if c's cached {p,l,b,k} values are invalid, move to the start
+ // or end first. In case of a tie, we prefer to seek forwards (i.e. set
+ // delta > 0).
+ if (delta < 0 && -delta >= off) || (c.seqNum != c.f.seqNum) {
+ c.seekStart()
+ delta = off - c.pos
+ }
+ if delta > 0 && delta > int32(c.f.len)-off {
+ c.seekEnd()
+ delta = off - c.pos
+ }
+
+ if delta != 0 {
+ // Seek forwards.
+ for delta > 0 {
+ if n := c.f.boxes[c.b].j - c.k; n > 0 {
+ if n > delta {
+ n = delta
+ }
+ c.pos += n
+ c.k += n
+ delta -= n
+ } else if c.leanForwards() != leanOK {
+ panic("text: invalid state")
+ }
+ }
+
+ // Seek backwards.
+ for delta < 0 {
+ if n := c.f.boxes[c.b].i - c.k; n < 0 {
+ if n < delta {
+ n = delta
+ }
+ c.pos += n
+ c.k += n
+ delta -= n
+ } else if c.leanBackwards() != leanOK {
+ panic("text: invalid state")
+ }
+ }
+
+ // A Caret can't be placed at the end of a Paragraph, unless it is the
+ // final Paragraph. A simple way to enforce this is to lean forwards.
+ c.leanForwards()
+ }
+
+ c.seqNum = c.f.seqNum
+}
+
+// Read satisfies the io.Reader interface by copying those bytes after the
+// Caret and incrementing the Caret.
+func (c *Caret) Read(buf []byte) (n int, err error) {
+ c.calculatePLBK()
+ for len(buf) > 0 {
+ if j := c.f.boxes[c.b].j; c.k < j {
+ nn := copy(buf, c.f.text[c.k:j])
+ buf = buf[nn:]
+ n += nn
+ c.pos += int32(nn)
+ c.k += int32(nn)
+ }
+ // A Caret can't be placed at the end of a Paragraph, unless it is the
+ // final Paragraph. A simple way to enforce this is to lean forwards.
+ if c.leanForwards() == leanFailedEOF {
+ break
+ }
+ }
+ if int(c.pos) == c.f.len {
+ err = io.EOF
+ }
+ return n, err
+}
+
+// ReadByte returns the next byte after the Caret and increments the Caret.
+func (c *Caret) ReadByte() (x byte, err error) {
+ c.calculatePLBK()
+ for {
+ if j := c.f.boxes[c.b].j; c.k < j {
+ x = c.f.text[c.k]
+ c.pos++
+ c.k++
+ // A Caret can't be placed at the end of a Paragraph, unless it is
+ // the final Paragraph. A simple way to enforce this is to lean
+ // forwards.
+ c.leanForwards()
+ return x, nil
+ }
+ if c.leanForwards() == leanFailedEOF {
+ return 0, io.EOF
+ }
+ }
+}
+
+// ReadRune returns the next rune after the Caret and increments the Caret.
+func (c *Caret) ReadRune() (r rune, size int, err error) {
+ c.calculatePLBK()
+ for {
+ if c.k < c.f.boxes[c.b].j {
+ r, size, c.b, c.k = c.f.readRune(c.b, c.k)
+ c.pos += int32(size)
+ // A Caret can't be placed at the end of a Paragraph, unless it is
+ // the final Paragraph. A simple way to enforce this is to lean
+ // forwards.
+ c.leanForwards()
+ return r, size, nil
+ }
+ if c.leanForwards() == leanFailedEOF {
+ return 0, 0, io.EOF
+ }
+ }
+}
+
+// WriteByte inserts x into the Frame's text at the Caret and increments the
+// Caret.
+func (c *Caret) WriteByte(x byte) error {
+ c.tmp[0] = x
+ return c.write(c.tmp[:1], "")
+}
+
+// WriteRune inserts r into the Frame's text at the Caret and increments the
+// Caret.
+func (c *Caret) WriteRune(r rune) (size int, err error) {
+ size = utf8.EncodeRune(c.tmp[:], r)
+ if err = c.write(c.tmp[:size], ""); err != nil {
+ return 0, err
+ }
+ return size, nil
+}
+
+// WriteString inserts s into the Frame's text at the Caret and increments the
+// Caret.
+func (c *Caret) WriteString(s string) (n int, err error) {
+ for len(s) > 0 {
+ i := 1 + strings.IndexByte(s, '\n')
+ if i == 0 {
+ i = len(s)
+ }
+ if err = c.write(nil, s[:i]); err != nil {
+ break
+ }
+ n += i
+ s = s[i:]
+ }
+ return n, err
+}
+
+// Write inserts s into the Frame's text at the Caret and increments the Caret.
+func (c *Caret) Write(s []byte) (n int, err error) {
+ for len(s) > 0 {
+ i := 1 + bytes.IndexByte(s, '\n')
+ if i == 0 {
+ i = len(s)
+ }
+ if err = c.write(s[:i], ""); err != nil {
+ break
+ }
+ n += i
+ s = s[i:]
+ }
+ return n, err
+}
+
+// write inserts a []byte or string into the Frame's text at the Caret.
+//
+// Exactly one of s0 and s1 must be non-empty. That non-empty argument must
+// contain at most one '\n' and if it does contain one, it must be the final
+// byte.
+func (c *Caret) write(s0 []byte, s1 string) error {
+ if m := maxLen - len(c.f.text); len(s0) > m || len(s1) > m {
+ return errors.New("text: insufficient space for writing")
+ }
+
+ // Ensure that the Caret is at the end of its Box, and that Box's text is
+ // at the end of the Frame's buffer.
+ c.calculatePLBK()
+ for {
+ bb, n := &c.f.boxes[c.b], int32(len(c.f.text))
+ if c.k == bb.j && c.k == n {
+ break
+ }
+
+ // If the Box's text is empty, move its empty i:j range to the
+ // equivalent empty range at the end of c.f.text.
+ if bb.i == bb.j {
+ bb.i = n
+ bb.j = n
+ for _, cc := range c.f.carets {
+ if cc.b == c.b {
+ cc.k = n
+ }
+ }
+ continue
+ }
+
+ // Make the Caret be at the end of its Box.
+ if c.k != bb.j {
+ c.splitBox(true)
+ continue
+ }
+
+ // Make a new empty Box and move the Caret to it.
+ c.splitBox(true)
+ c.leanForwards()
+ }
+
+ c.f.invalidateCaches()
+ c.f.paragraphs[c.p].invalidateCaches()
+ c.f.lines[c.l].invalidateCaches()
+
+ length, nl := len(s0), false
+ if length > 0 {
+ nl = s0[length-1] == '\n'
+ c.f.text = append(c.f.text, s0...)
+ } else {
+ length = len(s1)
+ nl = s1[length-1] == '\n'
+ c.f.text = append(c.f.text, s1...)
+ }
+ c.f.len += length
+ c.f.boxes[c.b].j += int32(length)
+ c.k += int32(length)
+ for _, cc := range c.f.carets {
+ if cc.pos > c.pos {
+ cc.pos += int32(length)
+ }
+ }
+ c.pos += int32(length)
+ oldL := c.l
+
+ if nl {
+ breakParagraph(c.f, c.p, c.l, c.b)
+ c.p = c.f.paragraphs[c.p].next
+ c.l = c.f.paragraphs[c.p].firstL
+ c.b = c.f.lines[c.l].firstB
+ c.k = c.f.boxes[c.b].i
+ }
+
+ // TODO: re-layout the new c.p paragraph, if we saw '\n'.
+ layout(c.f, oldL)
+
+ c.f.seqNum++
+ return nil
+}
+
+// breakParagraph breaks the Paragraph p into two Paragraphs, just after Box b
+// in Line l in Paragraph p. b's text must end with a '\n'. The new Paragraph
+// is inserted after p.
+func breakParagraph(f *Frame, p, l, b int32) {
+ // Assert that the Box b's text ends with a '\n'.
+ if j := f.boxes[b].j; j == 0 || f.text[j-1] != '\n' {
+ panic("text: invalid state")
+ }
+
+ // Make a new, empty Paragraph after this Paragraph p.
+ newP, _ := f.newParagraph()
+ nextP := f.paragraphs[p].next
+ if nextP != 0 {
+ f.paragraphs[nextP].prev = newP
+ }
+ f.paragraphs[newP].next = nextP
+ f.paragraphs[newP].prev = p
+ f.paragraphs[p].next = newP
+
+ // Any Lines in this Paragraph after the break point's Line l move to the
+ // newP Paragraph.
+ if nextL := f.lines[l].next; nextL != 0 {
+ f.lines[l].next = 0
+ f.lines[nextL].prev = 0
+ f.paragraphs[newP].firstL = nextL
+ }
+
+ // Any Boxes in this Line after the break point's Box b move to a new Line
+ // at the start of the newP Paragraph.
+ if nextB := f.boxes[b].next; nextB != 0 {
+ f.boxes[b].next = 0
+ f.boxes[nextB].prev = 0
+ newL, _ := f.newLine()
+ f.lines[newL].firstB = nextB
+ if newPFirstL := f.paragraphs[newP].firstL; newPFirstL != 0 {
+ f.lines[newL].next = newPFirstL
+ f.lines[newPFirstL].prev = newL
+ }
+ f.paragraphs[newP].firstL = newL
+ }
+
+ // Make the newP Paragraph's first Line and first Box explicit, since
+ // Carets require an explicit p, l and b.
+ {
+ pp := &f.paragraphs[newP]
+ if pp.firstL == 0 {
+ pp.firstL, _ = f.newLine()
+ }
+ ll := &f.lines[pp.firstL]
+ if ll.firstB == 0 {
+ ll.firstB, _ = f.newBox()
+ }
+ }
+
+ // TODO: re-layout the newP paragraph.
+}
+
+// breakLine breaks the Line l at text index k in Box b. The b-and-k index must
+// not be at the start or end of the Line. Text to the right of b-and-k in the
+// Line l will be moved to the start of the next Line in the Paragraph, with
+// that next Line being created if it didn't already exist.
+func breakLine(f *Frame, l, b, k int32) {
+ // Split this Box into two if necessary, so that k equals a Box's j end.
+ bb := &f.boxes[b]
+ if k != bb.j {
+ if k == bb.i {
+ panic("TODO: degenerate split left, possibly adjusting the Line's firstB??")
+ }
+ newB, realloc := f.newBox()
+ if realloc {
+ bb = &f.boxes[b]
+ }
+ nextB := bb.next
+ if nextB != 0 {
+ f.boxes[nextB].prev = newB
+ }
+ f.boxes[newB].next = nextB
+ f.boxes[newB].prev = b
+ f.boxes[newB].i = k
+ f.boxes[newB].j = bb.j
+ bb.next = newB
+ bb.j = k
+ }
+
+ // Assert that the break point isn't already at the start or end of the Line.
+ if bb.next == 0 || (bb.prev == 0 && k == bb.i) {
+ panic("text: invalid state")
+ }
+
+ // Insert a line after this one, if one doesn't already exist.
+ ll := &f.lines[l]
+ if ll.next == 0 {
+ newL, realloc := f.newLine()
+ if realloc {
+ ll = &f.lines[l]
+ }
+ f.lines[newL].prev = l
+ ll.next = newL
+ }
+
+ // Move the remaining boxes to the next line.
+ nextB, nextL := bb.next, ll.next
+ bb.next = 0
+ f.boxes[nextB].prev = 0
+ fb := f.lines[nextL].firstB
+ f.lines[nextL].firstB = nextB
+
+ // If the next Line already contained Boxes, append them to the end of the
+ // nextB chain, and join the two newly linked Boxes if possible.
+ if fb != 0 {
+ lb := f.lines[nextL].lastBox(f)
+ lbb := &f.boxes[lb]
+ fbb := &f.boxes[fb]
+ lbb.next = fb
+ fbb.prev = lb
+ f.joinBoxes(lb, fb, lbb, fbb)
+ }
+}
+
+// layout inserts a soft return in the Line l if its text measures longer than
+// f.maxWidth and a suitable line break point is found. This may spill text
+// onto the next line, which will also be laid out, and so on recursively.
+func layout(f *Frame, l int32) {
+ if f.maxWidth <= 0 || f.face == nil {
+ return
+ }
+ f.seqNum++
+
+ for ; l != 0; l = f.lines[l].next {
+ var (
+ firstB = f.lines[l].firstB
+ reader = f.lineReader(firstB, f.boxes[firstB].i)
+ breakPoint bAndK
+ prevR rune
+ prevRValid bool
+ advance fixed.Int26_6
+ )
+ for {
+ r, _, err := reader.ReadRune()
+ if err != nil || r == '\n' {
+ return
+ }
+ if prevRValid {
+ advance += f.face.Kern(prevR, r)
+ }
+ // TODO: match all whitespace, not just ' '?
+ if r == ' ' {
+ breakPoint = reader.bAndK()
+ }
+ a, ok := f.face.GlyphAdvance(r)
+ if !ok {
+ panic("TODO: is falling back on the U+FFFD glyph the responsibility of the caller or the Face?")
+ }
+ advance += a
+ if r != ' ' && advance > f.maxWidth && breakPoint.b != 0 {
+ breakLine(f, l, breakPoint.b, breakPoint.k)
+ break
+ }
+ prevR, prevRValid = r, true
+ }
+ }
+}
+
+// Delete deletes nBytes bytes in the specified direction from the Caret's
+// location. It returns the number of bytes deleted, which can be fewer than
+// that requested if it hits the beginning or end of the Frame.
+func (c *Caret) Delete(dir Direction, nBytes int) (dBytes int) {
+ if nBytes <= 0 {
+ return 0
+ }
+
+ // Convert a backwards delete of n bytes from position p to a forwards
+ // delete of n bytes from position p-n.
+ //
+ // In general, it's easier to delete forwards than backwards. For example,
+ // when crossing paragraph boundaries, it's easier to find the first line
+ // of the next paragraph than the last line of the previous paragraph.
+ if dir == Backwards {
+ newPos := int(c.pos) - nBytes
+ if newPos < 0 {
+ newPos = 0
+ nBytes = int(c.pos)
+ if nBytes == 0 {
+ return 0
+ }
+ }
+ c.seek(int32(newPos))
+ }
+
+ if int(c.pos) == c.f.len {
+ return 0
+ }
+
+ c.calculatePLBK()
+ c.leanForwards()
+ if c.f.boxes[c.b].i != c.k && c.splitBox(false) {
+ c.leanForwards()
+ }
+ for nBytes > 0 && int(c.pos) != c.f.len {
+ bb := &c.f.boxes[c.b]
+ n := bb.j - bb.i
+ newLine := n != 0 && c.f.text[bb.j-1] == '\n'
+ if int(n) > nBytes {
+ n = int32(nBytes)
+ }
+ bb.i += n
+ c.k += n
+ dBytes += int(n)
+ nBytes -= int(n)
+ c.f.len -= int(n)
+
+ if bb.i != bb.j {
+ break
+ }
+
+ if newLine {
+ c.joinNextParagraph()
+ }
+ c.leanForwards()
+ }
+
+ // The mergeIntoOneLine will shake out any empty Boxes.
+ l := c.f.mergeIntoOneLine(c.p)
+ layout(c.f, l)
+ c.f.invalidateCaches()
+
+ // Compact c.f.text if it's large enough and the fraction of deleted text
+ // is above some threshold. The actual threshold value (25%) is arbitrary.
+ // A lower value means more frequent compactions, so less memory on average
+ // but more CPU. A higher value means the opposite.
+ if len(c.f.text) > 4096 && len(c.f.text)/4 < c.f.deletedLen() {
+ c.f.compactText()
+ }
+
+ c.f.seqNum++
+ for _, cc := range c.f.carets {
+ if cc == c {
+ continue
+ }
+ switch relPos := cc.pos - c.pos; {
+ case relPos <= 0:
+ // No-op.
+ case relPos <= int32(dBytes):
+ cc.pos = c.pos
+ default:
+ cc.pos -= int32(dBytes)
+ }
+ }
+ return dBytes
+}
+
+// DeleteRunes deletes nRunes runes in the specified direction from the Caret's
+// location. It returns the number of runes and bytes deleted, which can be
+// fewer than that requested if it hits the beginning or end of the Frame.
+func (c *Caret) DeleteRunes(dir Direction, nRunes int) (dRunes, dBytes int) {
+ // Save the current Caret position, move the Caret by nRunes runes to
+ // calculate how many bytes to delete, restore that saved Caret position,
+ // then delete that many bytes.
+ c.calculatePLBK()
+ savedC := *c
+ if dir == Forwards {
+ for dRunes < nRunes {
+ var size int
+ _, size, c.b, c.k = c.f.readRune(c.b, c.k)
+ if size != 0 {
+ dRunes++
+ dBytes += size
+ } else if c.leanForwards() != leanOK {
+ break
+ }
+ }
+ } else {
+ for dRunes < nRunes {
+ var size int
+ _, size, c.b, c.k = c.f.readLastRune(c.b, c.k)
+ if size != 0 {
+ dRunes++
+ dBytes += size
+ } else if c.leanBackwards() != leanOK {
+ break
+ }
+ }
+ }
+ *c = savedC
+ if dBytes != c.Delete(dir, dBytes) {
+ panic("text: invalid state")
+ }
+ return dRunes, dBytes
+}
+
+// joinNextParagraph joins c's current and next Paragraph. That next Paragraph
+// must exist, and c must be at the last Line of its current Paragraph.
+func (c *Caret) joinNextParagraph() {
+ pp0 := &c.f.paragraphs[c.p]
+ ll0 := &c.f.lines[c.l]
+ if pp0.next == 0 || ll0.next != 0 {
+ panic("text: invalid state")
+ }
+ pp1 := &c.f.paragraphs[pp0.next]
+ l1 := pp1.firstL
+
+ ll0.next = l1
+ c.f.lines[l1].prev = c.l
+
+ toFree := pp0.next
+ pp0.next = pp1.next
+ if pp0.next != 0 {
+ c.f.paragraphs[pp0.next].prev = c.p
+ }
+
+ c.f.freeParagraph(toFree)
+}
+
+// splitBox splits the Caret's Box into two, at the Caret's location. Unless
+// force is set, it does nothing if the Caret is at either edge of its Box. It
+// returns whether the Box was split. If so, the new Box is created after, not
+// before, the Caret's current Box.
+func (c *Caret) splitBox(force bool) bool {
+ bb := &c.f.boxes[c.b]
+ if !force && (c.k == bb.i || c.k == bb.j) {
+ return false
+ }
+ newB, realloc := c.f.newBox()
+ if realloc {
+ bb = &c.f.boxes[c.b]
+ }
+ nextB := bb.next
+ if nextB != 0 {
+ c.f.boxes[nextB].prev = newB
+ }
+ c.f.boxes[newB] = Box{
+ next: nextB,
+ prev: c.b,
+ i: c.k,
+ j: bb.j,
+ }
+ bb.next = newB
+ bb.j = c.k
+ return true
+}
diff --git a/shiny/text/example_test.go b/shiny/text/example_test.go
new file mode 100644
index 0000000..f4e4324
--- /dev/null
+++ b/shiny/text/example_test.go
@@ -0,0 +1,93 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package text_test
+
+import (
+ "fmt"
+ "image"
+ "os"
+
+ "golang.org/x/exp/shiny/text"
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+)
+
+// toyFace implements the font.Face interface by measuring every rune's width
+// as 1 pixel.
+type toyFace struct{}
+
+func (toyFace) Close() error {
+ return nil
+}
+
+func (toyFace) Glyph(dot fixed.Point26_6, r rune) (image.Rectangle, image.Image, image.Point, fixed.Int26_6, bool) {
+ panic("unimplemented")
+}
+
+func (toyFace) GlyphBounds(r rune) (fixed.Rectangle26_6, fixed.Int26_6, bool) {
+ panic("unimplemented")
+}
+
+func (toyFace) GlyphAdvance(r rune) (fixed.Int26_6, bool) {
+ return fixed.I(1), true
+}
+
+func (toyFace) Kern(r0, r1 rune) fixed.Int26_6 {
+ return 0
+}
+
+func (toyFace) Metrics() font.Metrics {
+ return font.Metrics{}
+}
+
+func printFrame(f *text.Frame, softReturnsOnly bool) {
+ for p := f.FirstParagraph(); p != nil; p = p.Next(f) {
+ for l := p.FirstLine(f); l != nil; l = l.Next(f) {
+ for b := l.FirstBox(f); b != nil; b = b.Next(f) {
+ if softReturnsOnly {
+ os.Stdout.Write(b.TrimmedText(f))
+ } else {
+ os.Stdout.Write(b.Text(f))
+ }
+ }
+ if softReturnsOnly {
+ fmt.Println()
+ }
+ }
+ }
+}
+
+func Example() {
+ var f text.Frame
+ f.SetFace(toyFace{})
+ f.SetMaxWidth(fixed.I(60))
+
+ c := f.NewCaret()
+ c.WriteString(mobyDick)
+ c.Close()
+
+ fmt.Println("====")
+ printFrame(&f, false)
+ fmt.Println("====")
+ fmt.Println("123456789_123456789_123456789_123456789_123456789_123456789_")
+ printFrame(&f, true)
+ fmt.Println("====")
+
+ // Output:
+ // ====
+ // CHAPTER 1. Loomings.
+ // Call me Ishmael. Some years agoβ€”never mind how long preciselyβ€”having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world...
+ // ====
+ // 123456789_123456789_123456789_123456789_123456789_123456789_
+ // CHAPTER 1. Loomings.
+ // Call me Ishmael. Some years agoβ€”never mind how long
+ // preciselyβ€”having little or no money in my purse, and nothing
+ // particular to interest me on shore, I thought I would sail
+ // about a little and see the watery part of the world...
+ //
+ // ====
+}
+
+const mobyDick = "CHAPTER 1. Loomings.\nCall me Ishmael. Some years agoβ€”never mind how long preciselyβ€”having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world...\n"
diff --git a/shiny/text/text.go b/shiny/text/text.go
new file mode 100644
index 0000000..f8bb5ca
--- /dev/null
+++ b/shiny/text/text.go
@@ -0,0 +1,788 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package text lays out paragraphs of text.
+//
+// A body of text is laid out into a Frame: Frames contain Paragraphs (stacked
+// vertically), Paragraphs contain Lines (stacked vertically), and Lines
+// contain Boxes (stacked horizontally). Each Box holds a []byte slice of the
+// text. For example, to simply print a Frame's text from start to finish:
+//
+// var f *text.Frame = etc
+// for p := f.FirstParagraph(); p != nil; p = p.Next(f) {
+// for l := p.FirstLine(f); l != nil; l = l.Next(f) {
+// for b := l.FirstBox(f); b != nil; b = b.Next(f) {
+// fmt.Print(b.Text(f))
+// }
+// }
+// }
+//
+// A Frame's structure (the tree of Paragraphs, Lines and Boxes), and its
+// []byte text, are not modified directly. Instead, a Frame's maximum width can
+// be re-sized, and text can be added and removed via Carets (which implement
+// standard io interfaces). For example, to add some words to the end of a
+// frame:
+//
+// var f *text.Frame = etc
+// c := f.NewCaret()
+// c.Seek(0, text.SeekEnd)
+// c.WriteString("Not with a bang but a whimper.\n")
+// c.Close()
+//
+// Either way, such modifications can cause re-layout, which can add or remove
+// Paragraphs, Lines and Boxes. The underlying memory for such structs can be
+// re-used, so pointer values, such as of type *Box, should not be held over
+// such modifications.
+package text // import "golang.org/x/exp/shiny/text"
+
+import (
+ "io"
+ "unicode/utf8"
+
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+)
+
+// Direction is either forwards or backwards.
+type Direction bool
+
+const (
+ Forwards Direction = false
+ Backwards Direction = true
+)
+
+// These constants are equal to os.SEEK_SET, os.SEEK_CUR and os.SEEK_END,
+// understood by the io.Seeker interface, and are provided so that users of
+// this package don't have to explicitly import "os".
+const (
+ SeekSet int = 0
+ SeekCur int = 1
+ SeekEnd int = 2
+)
+
+// maxLen is maximum (inclusive) value of len(Frame.text) and therefore of
+// Box.i, Box.j and Caret.k.
+const maxLen = 0x7fffffff
+
+// Frame holds Paragraphs of text.
+//
+// The zero value is a valid Frame of empty text, which contains one Paragraph,
+// which contains one Line, which contains one Box.
+type Frame struct {
+ // These slices hold the Frame's Paragraphs, Lines and Boxes, indexed by
+ // fields such as Paragraph.firstL and Box.next.
+ //
+ // Their contents are not necessarily in layout order. Each slice is
+ // obviously backed by an array, but a Frame's list of children
+ // (Paragraphs) forms a doubly-linked list, not an array list, so that
+ // insertion has lower algorithmic complexity. Similarly for a Paragraph's
+ // list of children (Lines) and a Line's list of children (Boxes).
+ //
+ // The 0'th index into each slice is a special case. Otherwise, each
+ // element is either in use (forming a double linked list with its
+ // siblings) or in a free list (forming a single linked list; the prev
+ // field is -1).
+ //
+ // A zero firstFoo field means that the parent holds a single, implicit
+ // (lazily allocated), empty-but-not-nil *Foo child. Every Frame contains
+ // at least one Paragraph. Similarly, every Paragraph contains at least one
+ // Line, and every Line contains at least one Box.
+ //
+ // A zero next or prev field means that there is no such sibling (for an
+ // in-use Paragraph, Line or Box) or no such next free element (if in the
+ // free list).
+ paragraphs []Paragraph
+ lines []Line
+ boxes []Box
+
+ // These values cache the total height-in-pixels of or the number of
+ // elements in the paragraphs or lines linked lists. The plus one is so
+ // that the zero value means the cache is invalid.
+ cachedHeightPlus1 int32
+ cachedLineCountPlus1 int32
+ cachedParagraphCountPlus1 int32
+
+ // freeX is the index of the first X (Paragraph, Line or Box) in the
+ // respective free list. Zero means that there is no such free element.
+ freeP, freeL, freeB int32
+
+ firstP int32
+
+ maxWidth fixed.Int26_6
+
+ faceHeight int32
+ face font.Face
+
+ // len is the total length of the Frame's current textual content, in
+ // bytes. It can be smaller then len(text), since that []byte can contain
+ // 'holes' of deleted content.
+ //
+ // Like the paragraphs, lines and boxes slice-typed fields above, the text
+ // []byte does not necessarily hold the textual content in layout order.
+ // Instead, it holds the content in edit (insertion) order, with occasional
+ // compactions. Again, the algorithmic complexity of insertions matters.
+ len int
+ text []byte
+
+ // seqNum is a sequence number that is incremented every time the Frame's
+ // text or layout is modified. It lets us detect whether a Caret's cached
+ // p, l, b and k fields are stale.
+ seqNum uint64
+
+ carets []*Caret
+
+ // lineReaderData supports the Frame's lineReader, used when reading a
+ // Line's text one rune at a time.
+ lineReaderData bAndK
+}
+
+// TODO: allow multiple font faces, i.e. rich text?
+
+// SetFace sets the font face for measuring text.
+func (f *Frame) SetFace(face font.Face) {
+ if !f.initialized() {
+ f.initialize()
+ }
+ f.face = face
+ if face == nil {
+ f.faceHeight = 0
+ } else {
+ // We round up the ascent and descent separately, instead of asking for
+ // the metrics' height, since we quantize the baseline to the integer
+ // pixel grid. For example, if ascent and descent were both 3.2 pixels,
+ // then the naive height would be 6.4, which rounds up to 7, but we
+ // should really provide 8 pixels (= ceil(3.2) + ceil(3.2)) between
+ // each line to avoid overlap.
+ //
+ // TODO: is a font.Metrics.Height actually useful in practice??
+ //
+ // TODO: is it the font face's responsibility to track line spacing, as
+ // in "double line spacing", or does that belong somewhere else, since
+ // it doesn't affect the face's glyph masks?
+ m := face.Metrics()
+ f.faceHeight = int32(m.Ascent.Ceil() + m.Descent.Ceil())
+ }
+ if f.len != 0 {
+ f.relayout()
+ }
+}
+
+// TODO: should SetMaxWidth take an int number of pixels instead of a
+// fixed.Int26_6 number of sub-pixels? Height returns an int, since it assumes
+// that the text baselines are quantized to the integer pixel grid.
+
+// SetMaxWidth sets the target maximum width of a Line of text, as a
+// fixed-point fractional number of pixels. Text will be broken so that a
+// Line's width is less than or equal to this maximum width. This line breaking
+// is not strict. A Line containing asingleverylongword combined with a narrow
+// maximum width will not be broken and will remain longer than the target
+// maximum width; soft hyphens are not inserted.
+//
+// A non-positive argument is treated as an infinite maximum width.
+func (f *Frame) SetMaxWidth(m fixed.Int26_6) {
+ if !f.initialized() {
+ f.initialize()
+ }
+ if f.maxWidth == m {
+ return
+ }
+ f.maxWidth = m
+ if f.len != 0 {
+ f.relayout()
+ }
+}
+
+func (f *Frame) relayout() {
+ for p := f.firstP; p != 0; p = f.paragraphs[p].next {
+ l := f.mergeIntoOneLine(p)
+ layout(f, l)
+ }
+ f.invalidateCaches()
+ f.seqNum++
+}
+
+// mergeIntoOneLine merges all of a Paragraph's Lines into a single Line, and
+// compacts its empty and otherwise joinable Boxes. It returns the index of
+// that Line.
+func (f *Frame) mergeIntoOneLine(p int32) (l int32) {
+ firstL := f.paragraphs[p].firstL
+ ll := &f.lines[firstL]
+ b0 := ll.firstB
+ bb0 := &f.boxes[b0]
+ for {
+ if b1 := bb0.next; b1 != 0 {
+ bb1 := &f.boxes[b1]
+ if !f.joinBoxes(b0, b1, bb0, bb1) {
+ b0, bb0 = b1, bb1
+ }
+ continue
+ }
+
+ if ll.next == 0 {
+ f.paragraphs[p].invalidateCaches()
+ f.lines[firstL].invalidateCaches()
+ return firstL
+ }
+
+ // Unbreak the Line.
+ nextLL := &f.lines[ll.next]
+ b1 := nextLL.firstB
+ bb1 := &f.boxes[b1]
+ bb0.next = b1
+ bb1.prev = b0
+
+ toFree := ll.next
+ ll.next = nextLL.next
+ // There's no need to fix up f.lines[ll.next].prev since it will just
+ // be freed later in the loop.
+ f.freeLine(toFree)
+ }
+}
+
+// joinBoxes joins two adjacent Boxes if the Box.j field of the first one
+// equals the Box.i field of the second, or at least one of them is empty. It
+// returns whether they were joined. If they were joined, the second of the two
+// Boxes is freed.
+func (f *Frame) joinBoxes(b0, b1 int32, bb0, bb1 *Box) bool {
+ switch {
+ case bb0.i == bb0.j:
+ // The first Box is empty. Replace its i/j with the second one's.
+ bb0.i, bb0.j = bb1.i, bb1.j
+ case bb1.i == bb1.j:
+ // The second box is empty. Drop it.
+ case bb0.j == bb1.i:
+ // The two non-empty Boxes are joinable.
+ bb0.j = bb1.j
+ default:
+ return false
+ }
+ bb0.next = bb1.next
+ if bb0.next != 0 {
+ f.boxes[bb0.next].prev = b0
+ }
+ f.freeBox(b1)
+ return true
+}
+
+func (f *Frame) initialized() bool {
+ return len(f.paragraphs) > 0
+}
+
+func (f *Frame) initialize() {
+ // The first valid Paragraph, Line and Box all have index 1. The 0'th index
+ // of each slice is a special case.
+ f.paragraphs = make([]Paragraph, 2, 16)
+ f.lines = make([]Line, 2, 16)
+ f.boxes = make([]Box, 2, 16)
+
+ f.firstP = 1
+ f.paragraphs[1].firstL = 1
+ f.lines[1].firstB = 1
+}
+
+// newParagraph returns the index of an empty Paragraph, and whether or not the
+// underlying memory has been re-allocated. Re-allocation means that any
+// existing *Paragraph pointers become invalid.
+func (f *Frame) newParagraph() (p int32, realloc bool) {
+ if f.freeP != 0 {
+ p := f.freeP
+ pp := &f.paragraphs[p]
+ f.freeP = pp.next
+ *pp = Paragraph{}
+ return p, false
+ }
+ realloc = len(f.paragraphs) == cap(f.paragraphs)
+ f.paragraphs = append(f.paragraphs, Paragraph{})
+ return int32(len(f.paragraphs) - 1), realloc
+}
+
+// newLine returns the index of an empty Line, and whether or not the
+// underlying memory has been re-allocated. Re-allocation means that any
+// existing *Line pointers become invalid.
+func (f *Frame) newLine() (l int32, realloc bool) {
+ if f.freeL != 0 {
+ l := f.freeL
+ ll := &f.lines[l]
+ f.freeL = ll.next
+ *ll = Line{}
+ return l, false
+ }
+ realloc = len(f.lines) == cap(f.lines)
+ f.lines = append(f.lines, Line{})
+ return int32(len(f.lines) - 1), realloc
+}
+
+// newBox returns the index of an empty Box, and whether or not the underlying
+// memory has been re-allocated. Re-allocation means that any existing *Box
+// pointers become invalid.
+func (f *Frame) newBox() (b int32, realloc bool) {
+ if f.freeB != 0 {
+ b := f.freeB
+ bb := &f.boxes[b]
+ f.freeB = bb.next
+ *bb = Box{}
+ return b, false
+ }
+ realloc = len(f.boxes) == cap(f.boxes)
+ f.boxes = append(f.boxes, Box{})
+ return int32(len(f.boxes) - 1), realloc
+}
+
+func (f *Frame) freeParagraph(p int32) {
+ f.paragraphs[p] = Paragraph{next: f.freeP, prev: -1}
+ f.freeP = p
+ // TODO: run a compaction if the free-list is too large?
+}
+
+func (f *Frame) freeLine(l int32) {
+ f.lines[l] = Line{next: f.freeL, prev: -1}
+ f.freeL = l
+ // TODO: run a compaction if the free-list is too large?
+}
+
+func (f *Frame) freeBox(b int32) {
+ f.boxes[b] = Box{next: f.freeB, prev: -1}
+ f.freeB = b
+ // TODO: run a compaction if the free-list is too large?
+}
+
+func (f *Frame) lastParagraph() int32 {
+ for p := f.firstP; ; {
+ if next := f.paragraphs[p].next; next != 0 {
+ p = next
+ continue
+ }
+ return p
+ }
+}
+
+// FirstParagraph returns the first paragraph of this frame.
+func (f *Frame) FirstParagraph() *Paragraph {
+ if !f.initialized() {
+ f.initialize()
+ }
+ return &f.paragraphs[f.firstP]
+}
+
+func (f *Frame) invalidateCaches() {
+ f.cachedHeightPlus1 = 0
+ f.cachedLineCountPlus1 = 0
+ f.cachedParagraphCountPlus1 = 0
+}
+
+// Height returns the height in pixels of this Frame.
+func (f *Frame) Height() int {
+ if !f.initialized() {
+ f.initialize()
+ }
+ if f.cachedHeightPlus1 <= 0 {
+ h := 1
+ for p := f.firstP; p != 0; p = f.paragraphs[p].next {
+ h += f.paragraphs[p].Height(f)
+ }
+ f.cachedHeightPlus1 = int32(h)
+ }
+ return int(f.cachedHeightPlus1 - 1)
+}
+
+// LineCount returns the number of Lines in this Frame.
+//
+// This count includes any soft returns inserted to wrap text to the maxWidth.
+func (f *Frame) LineCount() int {
+ if !f.initialized() {
+ f.initialize()
+ }
+ if f.cachedLineCountPlus1 <= 0 {
+ n := 1
+ for p := f.firstP; p != 0; p = f.paragraphs[p].next {
+ n += f.paragraphs[p].LineCount(f)
+ }
+ f.cachedLineCountPlus1 = int32(n)
+ }
+ return int(f.cachedLineCountPlus1 - 1)
+}
+
+// ParagraphCount returns the number of Paragraphs in this Frame.
+//
+// This count excludes any soft returns inserted to wrap text to the maxWidth.
+func (f *Frame) ParagraphCount() int {
+ if !f.initialized() {
+ f.initialize()
+ }
+ if f.cachedParagraphCountPlus1 <= 0 {
+ n := 1
+ for p := f.firstP; p != 0; p = f.paragraphs[p].next {
+ n++
+ }
+ f.cachedParagraphCountPlus1 = int32(n)
+ }
+ return int(f.cachedParagraphCountPlus1 - 1)
+}
+
+// Len returns the number of bytes in the Frame's text.
+func (f *Frame) Len() int {
+ // We would normally check f.initialized() at the start of each exported
+ // method of a Frame, but that is not necessary here. The Frame's text's
+ // length does not depend on its Paragraphs, Lines and Boxes.
+ return f.len
+}
+
+// deletedLen returns the number of deleted bytes in the Frame's text.
+func (f *Frame) deletedLen() int {
+ return len(f.text) - f.len
+}
+
+func (f *Frame) compactText() {
+ // f.text contains f.len live bytes and len(f.text) - f.len deleted bytes.
+ // After the compaction, the new f.text slice's capacity should be at least
+ // f.len, to hold all of the live bytes, but also be below len(f.text) to
+ // allow total memory use to decrease. The actual value used (halfway
+ // between them) is arbitrary. A lower value means less up-front memory
+ // consumption but a lower threshold for re-allocating the f.text slice
+ // upon further writes, such as a paste immediately after a cut. A higher
+ // value means the opposite.
+ newText := make([]byte, 0, f.len+f.deletedLen()/2)
+ for p := f.firstP; p != 0; {
+ pp := &f.paragraphs[p]
+ for l := pp.firstL; l != 0; {
+ ll := &f.lines[l]
+
+ i := int32(len(newText))
+ for b := ll.firstB; b != 0; {
+ bb := &f.boxes[b]
+ newText = append(newText, f.text[bb.i:bb.j]...)
+ nextB := bb.next
+ f.freeBox(b)
+ b = nextB
+ }
+ j := int32(len(newText))
+ ll.firstB, _ = f.newBox()
+ bb := &f.boxes[ll.firstB]
+ bb.i, bb.j = i, j
+
+ l = ll.next
+ }
+ p = pp.next
+ }
+ f.text = newText
+ if len(newText) != f.len {
+ panic("text: invalid state")
+ }
+}
+
+// NewCaret returns a new Caret at the start of this Frame.
+func (f *Frame) NewCaret() *Caret {
+ if !f.initialized() {
+ f.initialize()
+ }
+ // Make the first Paragraph, Line and Box explicit, since Carets require an
+ // explicit p, l and b.
+ p := f.FirstParagraph()
+ l := p.FirstLine(f)
+ b := l.FirstBox(f)
+ c := &Caret{
+ f: f,
+ p: f.firstP,
+ l: p.firstL,
+ b: l.firstB,
+ k: b.i,
+ caretsIndex: len(f.carets),
+ }
+ f.carets = append(f.carets, c)
+ return c
+}
+
+// readRune returns the next rune and its size in bytes, starting from the Box
+// indexed by b and the text in that Box indexed by k. It also returns the new
+// b and k indexes after reading size bytes. The b argument must not be zero,
+// and the newB return value will not be zero.
+//
+// It can cross Box boundaries, but not Line boundaries, in finding the next
+// rune.
+func (f *Frame) readRune(b, k int32) (r rune, size int, newB, newK int32) {
+ bb := &f.boxes[b]
+
+ // In the fastest, common case, see if we can read a rune without crossing
+ // a Box boundary.
+ r, size = utf8.DecodeRune(f.text[k:bb.j])
+ if r < utf8.RuneSelf || size > 1 {
+ return r, size, b, k + int32(size)
+ }
+
+ // Otherwise, we decoded invalid UTF-8, possibly because a valid UTF-8 rune
+ // straddled this Box and the next one. Try again, copying up to
+ // utf8.UTFMax bytes from multiple Boxes into a single contiguous buffer.
+ buf := [utf8.UTFMax]byte{}
+ newBAndKs := [utf8.UTFMax + 1]bAndK{
+ 0: bAndK{b, k},
+ }
+ n := int32(0)
+ for {
+ if k < bb.j {
+ nCopied := int32(copy(buf[n:], f.text[k:bb.j]))
+ for i := int32(1); i <= nCopied; i++ {
+ newBAndKs[n+i] = bAndK{b, k + i}
+ }
+ n += nCopied
+ if n == utf8.UTFMax {
+ break
+ }
+ }
+ b = bb.next
+ if b == 0 {
+ break
+ }
+ bb = &f.boxes[b]
+ k = bb.i
+ }
+ r, size = utf8.DecodeRune(buf[:n])
+ bk := newBAndKs[size]
+ if bk.b == 0 {
+ panic("text: invalid state")
+ }
+ return r, size, bk.b, bk.k
+}
+
+// readLastRune is like readRune but it reads the last rune before b-and-k
+// instead of the first rune after.
+func (f *Frame) readLastRune(b, k int32) (r rune, size int, newB, newK int32) {
+ bb := &f.boxes[b]
+
+ // In the fastest, common case, see if we can read a rune without crossing
+ // a Box boundary.
+ r, size = utf8.DecodeLastRune(f.text[bb.i:k])
+ if r < utf8.RuneSelf || size > 1 {
+ return r, size, b, k - int32(size)
+ }
+
+ // Otherwise, we decoded invalid UTF-8, possibly because a valid UTF-8 rune
+ // straddled this Box and the previous one. Try again, copying up to
+ // utf8.UTFMax bytes from multiple Boxes into a single contiguous buffer.
+ buf := [utf8.UTFMax]byte{}
+ newBAndKs := [utf8.UTFMax + 1]bAndK{
+ utf8.UTFMax: bAndK{b, k},
+ }
+ n := int32(utf8.UTFMax)
+ for {
+ if k < bb.j {
+ nCopied := k - bb.i
+ if nCopied > n {
+ nCopied = n
+ }
+ copy(buf[n-nCopied:n], f.text[k-nCopied:k])
+ for i := int32(1); i <= nCopied; i++ {
+ newBAndKs[n-i] = bAndK{b, k - i}
+ }
+ n -= nCopied
+ if n == 0 {
+ break
+ }
+ }
+ b = bb.prev
+ if b == 0 {
+ break
+ }
+ bb = &f.boxes[b]
+ k = bb.j
+ }
+ r, size = utf8.DecodeLastRune(buf[n:])
+ bk := newBAndKs[utf8.UTFMax-size]
+ if bk.b == 0 {
+ panic("text: invalid state")
+ }
+ return r, size, bk.b, bk.k
+}
+
+func (f *Frame) lineReader(b, k int32) lineReader {
+ f.lineReaderData.b = b
+ f.lineReaderData.k = k
+ return lineReader{f}
+}
+
+// bAndK is a text position k within a Box b. The k is analogous to the Caret.k
+// field. For a bAndK x, letting bb := Frame.boxes[x.b], an invariant is that
+// bb.i <= x.k && x.k <= bb.j.
+type bAndK struct {
+ b int32
+ k int32
+}
+
+// lineReader is an io.RuneReader for a Line of text, from its current position
+// (a bAndK) up until the end of the Line containing that Box.
+//
+// A Frame can have only one active lineReader at any one time. To avoid
+// excessive memory allocation and garbage collection, the lineReader's data is
+// a field of the Frame struct and re-used.
+type lineReader struct{ f *Frame }
+
+func (z lineReader) bAndK() bAndK {
+ return z.f.lineReaderData
+}
+
+func (z lineReader) ReadRune() (r rune, size int, err error) {
+ d := &z.f.lineReaderData
+ for d.b != 0 {
+ bb := &z.f.boxes[d.b]
+ if d.k < bb.j {
+ r, size, d.b, d.k = z.f.readRune(d.b, d.k)
+ return r, size, nil
+ }
+ d.b = bb.next
+ d.k = z.f.boxes[d.b].i
+ }
+ return 0, 0, io.EOF
+}
+
+// Paragraph holds Lines of text.
+type Paragraph struct {
+ firstL, next, prev int32
+ cachedHeightPlus1 int32
+ cachedLineCountPlus1 int32
+}
+
+func (p *Paragraph) lastLine(f *Frame) int32 {
+ for l := p.firstL; ; {
+ if next := f.lines[l].next; next != 0 {
+ l = next
+ continue
+ }
+ return l
+ }
+}
+
+// FirstLine returns the first Line of this Paragraph.
+//
+// f is the Frame that contains the Paragraph.
+func (p *Paragraph) FirstLine(f *Frame) *Line {
+ return &f.lines[p.firstL]
+}
+
+// Next returns the next Paragraph after this one in the Frame.
+//
+// f is the Frame that contains the Paragraph.
+func (p *Paragraph) Next(f *Frame) *Paragraph {
+ if p.next == 0 {
+ return nil
+ }
+ return &f.paragraphs[p.next]
+}
+
+func (p *Paragraph) invalidateCaches() {
+ p.cachedHeightPlus1 = 0
+ p.cachedLineCountPlus1 = 0
+}
+
+// Height returns the height in pixels of this Paragraph.
+func (p *Paragraph) Height(f *Frame) int {
+ if p.cachedHeightPlus1 <= 0 {
+ h := 1
+ for l := p.firstL; l != 0; l = f.lines[l].next {
+ h += f.lines[l].Height(f)
+ }
+ p.cachedHeightPlus1 = int32(h)
+ }
+ return int(p.cachedHeightPlus1 - 1)
+}
+
+// LineCount returns the number of Lines in this Paragraph.
+//
+// This count includes any soft returns inserted to wrap text to the maxWidth.
+func (p *Paragraph) LineCount(f *Frame) int {
+ if p.cachedLineCountPlus1 <= 0 {
+ n := 1
+ for l := p.firstL; l != 0; l = f.lines[l].next {
+ n++
+ }
+ p.cachedLineCountPlus1 = int32(n)
+ }
+ return int(p.cachedLineCountPlus1 - 1)
+}
+
+// Line holds Boxes of text.
+type Line struct {
+ firstB, next, prev int32
+ cachedHeightPlus1 int32
+}
+
+func (l *Line) lastBox(f *Frame) int32 {
+ for b := l.firstB; ; {
+ if next := f.boxes[b].next; next != 0 {
+ b = next
+ continue
+ }
+ return b
+ }
+}
+
+// FirstBox returns the first Box of this Line.
+//
+// f is the Frame that contains the Line.
+func (l *Line) FirstBox(f *Frame) *Box {
+ return &f.boxes[l.firstB]
+}
+
+// Next returns the next Line after this one in the Paragraph.
+//
+// f is the Frame that contains the Line.
+func (l *Line) Next(f *Frame) *Line {
+ if l.next == 0 {
+ return nil
+ }
+ return &f.lines[l.next]
+}
+
+func (l *Line) invalidateCaches() {
+ l.cachedHeightPlus1 = 0
+}
+
+// Height returns the height in pixels of this Line.
+func (l *Line) Height(f *Frame) int {
+ // TODO: measure the height of each box, if we allow rich text (i.e. more
+ // than one Frame-wide font face).
+ if f.face == nil {
+ return 0
+ }
+ if l.cachedHeightPlus1 <= 0 {
+ l.cachedHeightPlus1 = f.faceHeight + 1
+ }
+ return int(l.cachedHeightPlus1 - 1)
+}
+
+// Box holds a contiguous run of text.
+type Box struct {
+ next, prev int32
+ // Frame.text[i:j] holds this Box's text.
+ i, j int32
+}
+
+// Next returns the next Box after this one in the Line.
+//
+// f is the Frame that contains the Box.
+func (b *Box) Next(f *Frame) *Box {
+ if b.next == 0 {
+ return nil
+ }
+ return &f.boxes[b.next]
+}
+
+// Text returns the Box's text.
+//
+// f is the Frame that contains the Box.
+func (b *Box) Text(f *Frame) []byte {
+ return f.text[b.i:b.j]
+}
+
+// TrimmedText returns the Box's text, trimmed right of any white space if it
+// is the last Box in its Line.
+//
+// f is the Frame that contains the Box.
+func (b *Box) TrimmedText(f *Frame) []byte {
+ s := f.text[b.i:b.j]
+ if b.next == 0 {
+ for len(s) > 0 && s[len(s)-1] <= ' ' {
+ s = s[:len(s)-1]
+ }
+ }
+ return s
+}
diff --git a/shiny/text/text_test.go b/shiny/text/text_test.go
new file mode 100644
index 0000000..7c93212
--- /dev/null
+++ b/shiny/text/text_test.go
@@ -0,0 +1,1381 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package text
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "io"
+ "math/rand"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+ "unicode/utf8"
+
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+)
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func readAllText(dst []byte, f *Frame) []byte {
+ for p := f.FirstParagraph(); p != nil; p = p.Next(f) {
+ for l := p.FirstLine(f); l != nil; l = l.Next(f) {
+ for b := l.FirstBox(f); b != nil; b = b.Next(f) {
+ dst = append(dst, b.Text(f)...)
+ }
+ }
+ }
+ return dst
+}
+
+func readAllRunes(dst []rune, rr io.RuneReader) ([]rune, error) {
+ for {
+ r, size, err := rr.ReadRune()
+ if err == io.EOF {
+ return dst, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Check that r and size are consistent.
+ if wantSize := utf8.RuneLen(r); size == wantSize {
+ // OK.
+ } else if r == utf8.RuneError && size == 1 {
+ // Also OK; one byte of invalid UTF-8 was replaced by '\ufffd'.
+ } else {
+ return nil, fmt.Errorf("rune %#x: got size %d, want %d", r, size, wantSize)
+ }
+
+ dst = append(dst, r)
+ }
+}
+
+func runesEqual(xs, ys []rune) bool {
+ if len(xs) != len(ys) {
+ return false
+ }
+ for i := range xs {
+ if xs[i] != ys[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func allASCII(rs []rune) bool {
+ for _, r := range rs {
+ if r >= 128 {
+ return false
+ }
+ }
+ return true
+}
+
+func runesLen(rs []rune) (n int) {
+ for _, r := range rs {
+ n += utf8.RuneLen(r)
+ }
+ return n
+}
+
+// insert inserts the insert argument into buf at the given offset.
+//
+// cap(buf) must be >= len(buf) + len(insert).
+func insert(buf, insert []byte, offset int) []byte {
+ n := len(insert)
+ buf = buf[:len(buf)+n]
+ copy(buf[offset+n:], buf[offset:])
+ copy(buf[offset:offset+n], insert)
+ return buf
+}
+
+func rngIntPair(rng *rand.Rand, n int) (x, y int) {
+ x = rng.Intn(n)
+ y = rng.Intn(n)
+ if x > y {
+ x, y = y, x
+ }
+ return x, y
+}
+
+// toyFace implements the font.Face interface by measuring every rune's width
+// as 1 pixel.
+type toyFace struct{}
+
+func (toyFace) Close() error {
+ return nil
+}
+
+func (toyFace) Glyph(dot fixed.Point26_6, r rune) (image.Rectangle, image.Image, image.Point, fixed.Int26_6, bool) {
+ panic("unimplemented")
+}
+
+func (toyFace) GlyphBounds(r rune) (fixed.Rectangle26_6, fixed.Int26_6, bool) {
+ panic("unimplemented")
+}
+
+func (toyFace) GlyphAdvance(r rune) (fixed.Int26_6, bool) {
+ return fixed.I(1), true
+}
+
+func (toyFace) Kern(r0, r1 rune) fixed.Int26_6 {
+ return 0
+}
+
+func (toyFace) Metrics() font.Metrics {
+ return font.Metrics{
+ Ascent: fixed.I(2),
+ Descent: fixed.I(1),
+ }
+}
+
+const toyFaceLineHeight = 3
+
+// iRobot is some text that contains both ASCII and non-ASCII runes.
+const iRobot = "\"I, Robot\" in Russian is \"Π―, Ρ€ΠΎΠ±ΠΎΡ‚\".\nIt's about robots.\n"
+
+var (
+ iRobotBytes = []byte(iRobot)
+ iRobotRunes = []rune(iRobot)
+)
+
+func iRobotFrame(maxWidth int) *Frame {
+ f := new(Frame)
+ f.SetFace(toyFace{})
+ f.SetMaxWidth(fixed.I(maxWidth))
+ c := f.NewCaret()
+ c.WriteString(iRobot)
+ c.Close()
+ return f
+}
+
+func TestZeroFrame(t *testing.T) {
+ f := new(Frame)
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestSeek(t *testing.T) {
+ f := iRobotFrame(10)
+ c := f.NewCaret()
+ defer c.Close()
+ rng := rand.New(rand.NewSource(1))
+ seen := [1 + len(iRobot)]bool{}
+ for i := 0; i < 10*len(iRobot); i++ {
+ wantPos := int64(rng.Intn(len(iRobot) + 1))
+ gotPos, err := c.Seek(wantPos, SeekSet)
+ if err != nil {
+ t.Fatalf("i=%d: Seek: %v", i, err)
+ }
+ if gotPos != wantPos {
+ t.Fatalf("i=%d: Seek: got %d, want %d", i, gotPos, wantPos)
+ }
+ seen[gotPos] = true
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+
+ gotByte, gotErr := c.ReadByte()
+ wantByte, wantErr := byte(0), io.EOF
+ if gotPos < int64(len(iRobot)) {
+ wantByte, wantErr = iRobot[gotPos], nil
+ }
+ if gotByte != wantByte || gotErr != wantErr {
+ t.Fatalf("i=%d: ReadByte: got %d, %v, want %d, %v", i, gotByte, gotErr, wantByte, wantErr)
+ }
+ }
+ for i, s := range seen {
+ if !s {
+ t.Errorf("randomly generated positions weren't exhaustive: position %d / %d not seen", i, len(iRobot))
+ }
+ }
+}
+
+func testRead(f *Frame, want string) error {
+ c := f.NewCaret()
+ defer c.Close()
+ asBytes, err := io.ReadAll(c)
+ if err != nil {
+ return fmt.Errorf("ReadAll: %v", err)
+ }
+ if got := string(asBytes); got != want {
+ return fmt.Errorf("Read\ngot: %q\nwant: %q", got, want)
+ }
+ return nil
+}
+
+func TestRead(t *testing.T) {
+ f := iRobotFrame(10)
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+ if err := testRead(f, iRobot); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReadByte(t *testing.T) {
+ f := iRobotFrame(10)
+ c := f.NewCaret()
+ defer c.Close()
+ got, want := []byte(nil), []byte(iRobot)
+ for {
+ x, err := c.ReadByte()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("ReadByte: %v", err)
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+ got = append(got, x)
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("\ngot: %v\nwant: %v", got, want)
+ }
+}
+
+func TestReadRune(t *testing.T) {
+ f := iRobotFrame(10)
+ c := f.NewCaret()
+ defer c.Close()
+ got, want := []rune(nil), []rune(iRobot)
+ for {
+ r, _, err := c.ReadRune()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("ReadRune: %v", err)
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+ got = append(got, r)
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("\ngot: %v\nwant: %v", got, want)
+ }
+}
+
+func TestWrite(t *testing.T) {
+ f := new(Frame)
+ c := f.NewCaret()
+ c.Write([]byte{0xff, 0xfe, 0xfd})
+ c.WriteByte(0x80)
+ c.WriteRune('\U0001f4a9')
+ c.WriteString("abc\x00")
+ c.Close()
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+ got := f.text[:f.len]
+ want := []byte{0xff, 0xfe, 0xfd, 0x80, 0xf0, 0x9f, 0x92, 0xa9, 0x61, 0x62, 0x63, 0x00}
+ if !bytes.Equal(got, want) {
+ t.Fatalf("\ngot % x\nwant % x", got, want)
+ }
+}
+
+func TestManyWrites(t *testing.T) {
+ f := new(Frame)
+ f.SetFace(toyFace{})
+ f.SetMaxWidth(fixed.I(10))
+ c := f.NewCaret()
+ defer c.Close()
+
+ const n, abc16 = 100, "abcdefghijkl\n "
+ rng := rand.New(rand.NewSource(1))
+ buf := make([]byte, n)
+ for i := range buf {
+ buf[i] = abc16[rng.Intn(len(abc16))]
+ }
+
+ for i := 0; i < 100; i++ {
+ x, y := rngIntPair(rng, len(buf)+1)
+ c.Write(buf[x:y])
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+ }
+}
+
+func TestRandomAccessWrite(t *testing.T) {
+ f := new(Frame)
+ c := f.NewCaret()
+ defer c.Close()
+
+ rng := rand.New(rand.NewSource(1))
+ gotBuf := make([]byte, 0, 10000)
+ want := make([]byte, 0, 10000)
+ buf := make([]byte, 10)
+ x := byte(0)
+ for i := 0; i < 100; i++ {
+ n := rng.Intn(len(buf) + 1)
+ for j := range buf[:n] {
+ buf[j] = x
+ x++
+ }
+ offset := rng.Intn(len(want) + 1)
+
+ // Insert buf[:n] into the Frame at the given offset.
+ c.Seek(int64(offset), SeekSet)
+ c.Write(buf[:n])
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+
+ // Insert buf[:n] into want at the given offset.
+ want = insert(want, buf[:n], offset)
+
+ if got := readAllText(gotBuf[:0], f); !bytes.Equal(got, want) {
+ t.Errorf("i=%d:\ngot % x\nwant % x", i, got, want)
+ }
+ }
+}
+
+func TestWriteAtStart(t *testing.T) {
+ f := iRobotFrame(10)
+ c := f.NewCaret()
+ defer c.Close()
+
+ prefix := "The Truth of the Matter\n(insofaras): "
+ gotBuf := make([]byte, len(prefix)+len(iRobot))
+ want := make([]byte, len(iRobot), len(prefix)+len(iRobot))
+ copy(want, iRobot)
+
+ for i := 0; i < len(prefix); i++ {
+ x := prefix[len(prefix)-1-i]
+
+ c.Seek(0, SeekSet)
+ c.WriteByte(x)
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+
+ want = want[:len(want)+1]
+ copy(want[1:], want)
+ want[0] = x
+
+ if got := readAllText(gotBuf[:0], f); !bytes.Equal(got, want) {
+ t.Fatalf("i=%d:\ngot % x\nwant % x", i, got, want)
+ }
+ }
+
+ if nl, nb := f.nUsedLines(), f.nUsedBoxes(); nl == nb {
+ t.Fatalf("before compaction: using %d lines and %d boxes, should be not equal", nl, nb)
+ }
+ f.compactText()
+ if nl, nb := f.nUsedLines(), f.nUsedBoxes(); nl != nb {
+ t.Fatalf("after compaction: using %d lines and %d boxes, should be equal", nl, nb)
+ }
+}
+
+func TestLineCount(t *testing.T) {
+ f := iRobotFrame(0)
+
+ testCases := []struct {
+ desc string
+ maxWidth int
+ wantLines []string
+ wantLineCounts []int
+ }{{
+ desc: "infinite maxWidth",
+ maxWidth: 0,
+ wantLines: []string{
+ `"I, Robot" in Russian is "Π―, Ρ€ΠΎΠ±ΠΎΡ‚".`,
+ `It's about robots.`,
+ ``,
+ },
+ wantLineCounts: []int{1, 1, 1},
+ }, {
+ desc: "finite maxWidth",
+ maxWidth: 10,
+ wantLines: []string{
+ `"I, Robot"`,
+ `in Russian`,
+ `is "Π―,`,
+ `Ρ€ΠΎΠ±ΠΎΡ‚".`,
+ `It's about`,
+ `robots.`,
+ ``,
+ },
+ wantLineCounts: []int{4, 2, 1},
+ }}
+ for _, tc := range testCases {
+ f.SetMaxWidth(fixed.I(tc.maxWidth))
+ lines, lineCounts := []string{}, []int{}
+ for p := f.FirstParagraph(); p != nil; p = p.Next(f) {
+ for l := p.FirstLine(f); l != nil; l = l.Next(f) {
+ var line []byte
+ for b := l.FirstBox(f); b != nil; b = b.Next(f) {
+ line = append(line, b.TrimmedText(f)...)
+ }
+ lines = append(lines, string(line))
+ }
+ lineCounts = append(lineCounts, p.LineCount(f))
+ }
+
+ if got, want := f.LineCount(), len(tc.wantLines); got != want {
+ t.Errorf("%s: LineCount: got %d, want %d", tc.desc, got, want)
+ continue
+ }
+ if !reflect.DeepEqual(lines, tc.wantLines) {
+ t.Errorf("%s: lines: got %q, want %q", tc.desc, lines, tc.wantLines)
+ continue
+ }
+ if got, want := f.ParagraphCount(), len(tc.wantLineCounts); got != want {
+ t.Errorf("%s: ParagraphCount: got %d, want %d", tc.desc, got, want)
+ continue
+ }
+ if !reflect.DeepEqual(lineCounts, tc.wantLineCounts) {
+ t.Errorf("%s: lineCounts: got %d, want %d", tc.desc, lineCounts, tc.wantLineCounts)
+ continue
+ }
+ if got, want := f.Height(), toyFaceLineHeight*len(tc.wantLines); got != want {
+ t.Errorf("%s: Height: got %d, want %d", tc.desc, got, want)
+ continue
+ }
+ }
+}
+
+func TestSetMaxWidth(t *testing.T) {
+ f := new(Frame)
+ f.SetFace(toyFace{})
+ want := ""
+ rng := rand.New(rand.NewSource(1))
+ for i := 0; i < 100; i++ {
+ if i%20 == 5 {
+ c := f.NewCaret()
+ c.WriteString(iRobot)
+ c.Close()
+ want += iRobot
+ }
+ maxWidth := rng.Intn(20)
+ f.SetMaxWidth(fixed.I(maxWidth))
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+ if err := testRead(f, want); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+ if want == "" {
+ continue
+ }
+ // Check that a zero (which means infinite) maxWidth corresponds to one
+ // Line per Paragraph.
+ np, nl := f.nUsedParagraphs(), f.nUsedLines()
+ if maxWidth == 0 {
+ if np != nl {
+ t.Fatalf("i=%d: maxWidth=0: got %d paragraphs and %d lines, should be equal", i, np, nl)
+ }
+ } else {
+ if np == nl {
+ t.Fatalf("i=%d: maxWidth=%d: got %d paragraphs and %d lines, should be unequal", i, maxWidth, np, nl)
+ }
+ }
+ }
+}
+
+func TestReadRuneAcrossBoxes(t *testing.T) {
+ rng := rand.New(rand.NewSource(1))
+ for i := 0; i < 6; i++ {
+ // text is a mixture of valid and invalid UTF-8.
+ text := "ab_\u0100\u0101_\ufffdδΈ­ζ–‡_\xff\xffΓ±\xff_z_\U0001f4a9_\U0001f600" +
+ strings.Repeat("\xff", i)
+
+ wantBytes := []byte(text)
+ wantRunes := []rune(text)
+ gotBytesBuf := make([]byte, 0, len(text))
+ gotRunesBuf := make([]rune, 0, len(text))
+
+ for j := 0; j < 100; j++ {
+ f := new(Frame)
+
+ {
+ c := f.NewCaret()
+ c.WriteString(text)
+ // Split the sole Line into multiple Boxes at random byte offsets,
+ // possibly cutting across multi-byte UTF-8 encoded runes.
+ for {
+ if rng.Intn(8) == 0 {
+ break
+ }
+ c.Seek(int64(rng.Intn(len(text)+1)), SeekSet)
+ c.splitBox(false)
+ }
+ c.Close()
+
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d, j=%d: %v", i, j, err)
+ }
+ if gotBytes := readAllText(gotBytesBuf[:0], f); !bytes.Equal(gotBytes, wantBytes) {
+ t.Fatalf("i=%d, j=%d: bytes\ngot % x\nwant % x", i, j, gotBytes, wantBytes)
+ }
+ }
+
+ // Test lineReader.ReadRune.
+ {
+ p := f.firstP
+ l := f.paragraphs[p].firstL
+ b := f.lines[l].firstB
+ lr := f.lineReader(b, f.boxes[b].i)
+ gotRunes, err := readAllRunes(gotRunesBuf[:0], lr)
+ if err != nil {
+ t.Fatalf("i=%d, j=%d: lineReader readAllRunes: %v", i, j, err)
+ }
+ if !runesEqual(gotRunes, wantRunes) {
+ t.Fatalf("i=%d, j=%d: lineReader readAllRunes:\ngot %#x\nwant %#x",
+ i, j, gotRunes, wantRunes)
+ }
+ }
+
+ // Test Caret.ReadRune.
+ {
+ c := f.NewCaret()
+ gotRunes, err := readAllRunes(gotRunesBuf[:0], c)
+ c.Close()
+ if err != nil {
+ t.Fatalf("i=%d, j=%d: Caret readAllRunes: %v", i, j, err)
+ }
+ if !runesEqual(gotRunes, wantRunes) {
+ t.Fatalf("i=%d, j=%d: Caret readAllRunes:\ngot %#x\nwant %#x",
+ i, j, gotRunes, wantRunes)
+ }
+ }
+ }
+ }
+}
+
+func TestDelete(t *testing.T) {
+ rng := rand.New(rand.NewSource(1))
+ gotBytesBuf := make([]byte, 0, len(iRobot))
+ wantBytesBuf := make([]byte, 0, len(iRobot))
+
+ for i := 0; i < 100; i++ {
+ f := iRobotFrame(rng.Intn(len(iRobot) + 1))
+ c := f.NewCaret()
+ defer c.Close()
+ wantBytes := append(wantBytesBuf[:0], iRobot...)
+ for j := 0; j < 8; j++ {
+ // Delete the text in the byte range [x, y).
+ x, y := rngIntPair(rng, len(wantBytes)+1)
+ got, want := 0, y-x
+ if rng.Intn(2) == 0 {
+ c.Seek(int64(x), SeekSet)
+ got = c.Delete(Forwards, want)
+ } else {
+ c.Seek(int64(y), SeekSet)
+ got = c.Delete(Backwards, want)
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d, j=%d, x=%d, y=%d: %v", i, j, x, y, err)
+ }
+ if got != want {
+ t.Fatalf("i=%d, j=%d, x=%d, y=%d: Delete: got %d, want %d", i, j, x, y, got, want)
+ }
+
+ gotBytes := readAllText(gotBytesBuf[:0], f)
+ wantBytes = append(wantBytes[:x], wantBytes[y:]...)
+ if !bytes.Equal(gotBytes, wantBytes) {
+ t.Fatalf("i=%d, j=%d, x=%d, y=%d:\ngot %q\nwant %q", i, j, x, y, gotBytes, wantBytes)
+ }
+ }
+ }
+}
+
+func TestDeleteRunes(t *testing.T) {
+ rng := rand.New(rand.NewSource(1))
+ gotBytesBuf := make([]byte, 0, len(iRobot))
+ wantRunesBuf := make([]rune, 0, len(iRobot))
+ wantBytesBuf := make([]byte, 0, len(iRobot))
+
+ for i := 0; i < 100; i++ {
+ f := iRobotFrame(rng.Intn(len(iRobot) + 1))
+ c := f.NewCaret()
+ defer c.Close()
+ wantRunes := append(wantRunesBuf[:0], iRobotRunes...)
+ wantBytes := append(wantBytesBuf[:0], iRobot...)
+ for j := 0; j < 8; j++ {
+ xR, yR := rngIntPair(rng, len(wantRunes)+1)
+ xB := runesLen(wantRunes[:xR])
+ yB := runesLen(wantRunes[:yR])
+
+ // Delete the text in the byte range [xB, yB).
+ gotR, gotB, wantR, wantB := 0, 0, yR-xR, yB-xB
+ if rng.Intn(2) == 0 {
+ c.Seek(int64(xB), SeekSet)
+ gotR, gotB = c.DeleteRunes(Forwards, wantR)
+ } else {
+ c.Seek(int64(yB), SeekSet)
+ gotR, gotB = c.DeleteRunes(Backwards, wantR)
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d, j=%d, xR=%d, xB=%d, yR=%d, yB=%d: %v", i, j, xR, xB, yR, yB, err)
+ }
+ if gotR != wantR || gotB != wantB {
+ t.Fatalf("i=%d, j=%d, xR=%d, xB=%d, yR=%d, yB=%d: Delete: got %d runes, %d bytes, want %d, %d",
+ i, j, xR, xB, yR, yB, gotR, gotB, wantR, wantB)
+ }
+
+ gotBytes := readAllText(gotBytesBuf[:0], f)
+ wantRunes = append(wantRunes[:xR], wantRunes[yR:]...)
+ wantBytes = append(wantBytes[:xB], wantBytes[yB:]...)
+ if !bytes.Equal(gotBytes, wantBytes) {
+ t.Fatalf("i=%d, j=%d, xR=%d, xB=%d, yR=%d, yB=%d:\ngot %q\nwant %q",
+ i, j, xR, xB, yR, yB, gotBytes, wantBytes)
+ }
+ }
+ }
+}
+
+func TestDeleteTooMuch(t *testing.T) {
+ if len(iRobot) < 17+15 {
+ t.Fatal("iRobot string is too short")
+ }
+
+ f := iRobotFrame(10)
+ c := f.NewCaret()
+ defer c.Close()
+
+ c.Seek(-15, SeekEnd)
+ if got, want := c.Delete(Forwards, 16), 15; got != want {
+ t.Errorf("Delete Forwards: got %d, want %d", got, want)
+ }
+
+ c.Seek(+17, SeekSet)
+ if got, want := c.Delete(Backwards, 18), 17; got != want {
+ t.Errorf("Delete Backwards: got %d, want %d", got, want)
+ }
+
+ got := string(readAllText(nil, f))
+ want := iRobot[17 : len(iRobot)-15]
+ if got != want {
+ t.Errorf("\ngot %q\nwant %q", got, want)
+ }
+}
+
+func TestDeleteRunesTooMuch(t *testing.T) {
+ if len(iRobotRunes) < 30+24 {
+ t.Fatal("iRobotRunes []rune is too short")
+ }
+ prefix := iRobotRunes[:30]
+ suffix := iRobotRunes[len(iRobotRunes)-24:]
+ if allASCII(prefix) {
+ t.Fatal("prefix contains no non-ASCII runes")
+ }
+ if allASCII(suffix) {
+ t.Fatal("suffix contains no non-ASCII runes")
+ }
+ prefixB := runesLen(prefix)
+ suffixB := runesLen(suffix)
+
+ f := iRobotFrame(10)
+ c := f.NewCaret()
+ defer c.Close()
+
+ c.Seek(-int64(suffixB), SeekEnd)
+ wantR, wantB := len(suffix), suffixB
+ if gotR, gotB := c.DeleteRunes(Forwards, 25); gotR != wantR || gotB != wantB {
+ t.Errorf("DeleteRunes Forwards: got %d runes, %d bytes, want %d, %d", gotR, gotB, wantR, wantB)
+ }
+
+ c.Seek(+int64(prefixB), SeekSet)
+ wantR, wantB = len(prefix), prefixB
+ if gotR, gotB := c.DeleteRunes(Backwards, 31); gotR != wantR || gotB != wantB {
+ t.Errorf("DeleteRunes Backwards: got %d runes, %d bytes, want %d, %d", gotR, gotB, wantR, wantB)
+ }
+
+ got := string(readAllText(nil, f))
+ want := iRobot[prefixB : len(iRobot)-suffixB]
+ if got != want {
+ t.Errorf("\ngot %q\nwant %q", got, want)
+ }
+}
+
+func TestCompactText(t *testing.T) {
+ f := new(Frame)
+ f.SetFace(toyFace{})
+ f.SetMaxWidth(fixed.I(80))
+ c := f.NewCaret()
+ defer c.Close()
+
+ const n, abc64 = 1024, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n "
+ rng := rand.New(rand.NewSource(1))
+ buf := make([]byte, n)
+ for i := range buf {
+ buf[i] = abc64[rng.Intn(len(abc64))]
+ }
+
+ written, wantLen := 0, 0
+ for i := 0; written < 131072; i++ {
+ if rng.Intn(10) != 0 {
+ x, y := rngIntPair(rng, len(buf)+1)
+ c.Seek(int64(rng.Intn(f.Len()+1)), SeekSet)
+ c.Write(buf[x:y])
+ written += y - x
+ wantLen += y - x
+ }
+
+ if rng.Intn(10) != 0 {
+ x, y := rngIntPair(rng, wantLen+1)
+ c.Seek(int64(x), SeekSet)
+ c.Delete(Forwards, y-x)
+ wantLen -= y - x
+ }
+
+ // Calculate cached counts, some of the time.
+ if rng.Intn(3) == 0 {
+ f.ParagraphCount()
+ }
+ if rng.Intn(4) == 0 {
+ f.LineCount()
+ }
+ if rng.Intn(5) == 0 {
+ c.Seek(int64(rng.Intn(f.Len()+1)), SeekSet)
+ f.paragraphs[c.p].LineCount(f)
+ }
+
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+ if gotLen := f.Len(); gotLen != wantLen {
+ t.Fatalf("i=%d: length: got %d, want %d", i, gotLen, wantLen)
+ }
+ }
+ if len(f.text) == written {
+ t.Fatal("f.text was not compacted")
+ }
+}
+
+func TestMergeIntoOneLine(t *testing.T) {
+ rng := rand.New(rand.NewSource(1))
+ for i := 0; i < 100; i++ {
+ f := new(Frame)
+ f.SetMaxWidth(fixed.I(100))
+
+ if !f.initialized() {
+ f.initialize()
+ }
+ p := f.firstP
+ pp := &f.paragraphs[p]
+ l := pp.firstL
+ ll := &f.lines[l]
+ b := ll.firstB
+ bb := &f.boxes[b]
+
+ // Make some Lines and Boxes.
+ wantNUsedBoxes := 0
+ prevIJ := int32(0)
+ emptyRun := true
+ for j := 0; ; j++ {
+ length := rng.Intn(3)
+ bb.i = prevIJ
+ prevIJ += int32(length)
+ bb.j = prevIJ
+ f.len += length
+ if length > 0 && emptyRun {
+ emptyRun = false
+ wantNUsedBoxes++
+ }
+
+ if rng.Intn(20) == 0 {
+ break
+ }
+
+ if rng.Intn(4) == 0 {
+ // Make a new Box on a new Line.
+ l1, realloc := f.newLine()
+ if realloc {
+ ll = &f.lines[l]
+ }
+ ll1 := &f.lines[l1]
+ ll.next = l1
+ ll1.prev = l
+ l, ll = l1, ll1
+
+ ll.firstB, _ = f.newBox()
+ b = ll.firstB
+ bb = &f.boxes[b]
+
+ } else {
+ // Make a new Box on the same Line.
+ b1, realloc := f.newBox()
+ if realloc {
+ bb = &f.boxes[b]
+ }
+ bb1 := &f.boxes[b1]
+ bb.next = b1
+ bb1.prev = b
+ b, bb = b1, bb1
+ }
+
+ if rng.Intn(5) == 0 {
+ // Put an i/j gap between this run and the previous one.
+ prevIJ += 1 + int32(rng.Intn(5))
+ emptyRun = true
+ }
+ }
+
+ // We normally remove all empty Boxes. However, if there is no text
+ // whatsoever, we still need one Box.
+ if f.len == 0 {
+ if wantNUsedBoxes != 0 {
+ t.Fatalf("i=%d: no text: wantNUsedBoxes: got %d, want 0", i, wantNUsedBoxes)
+ }
+ wantNUsedBoxes = 1
+ }
+
+ // Make f.text long enough to hold all of the Boxes' i's and j's.
+ f.text = make([]byte, prevIJ)
+ for i := range f.text {
+ f.text[i] = byte('a' + i%16)
+ }
+
+ // Do the merge.
+ if err := checkSomeInvariants(f, ignoreInvariantEmptyBoxes); err != nil {
+ t.Fatalf("i=%d: before: %v", i, err)
+ }
+ f.mergeIntoOneLine(p)
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: after: %v", i, err)
+ }
+
+ // Check that there is only one Line in use.
+ if n := f.nUsedLines(); n != 1 {
+ t.Errorf("i=%d: nUsedLines: got %d, want %d", i, n, 1)
+ }
+
+ // Check the number of Boxes in use.
+ if n := f.nUsedBoxes(); n != wantNUsedBoxes {
+ t.Errorf("i=%d: nUsedBoxes: got %d, want %d", i, n, wantNUsedBoxes)
+ }
+ }
+}
+
+// TestMultipleCarets tests multiple Carets all seeking, reading, writing and
+// deleting text.
+func TestMultipleCarets(t *testing.T) {
+ f := new(Frame)
+ f.SetFace(toyFace{})
+ f.SetMaxWidth(fixed.I(10))
+
+ rng := rand.New(rand.NewSource(1))
+ gotBuf := make([]byte, 0, 10000)
+ want := make([]byte, 0, 10000)
+ positions := map[*Caret]int{}
+ carets := []*Caret{}
+ defer func() {
+ for _, c := range carets {
+ c.Close()
+ }
+ }()
+
+ for i := 0; i < 1000; i++ {
+ if err := checkInvariants(f); err != nil {
+ t.Fatalf("i=%d: %v", i, err)
+ }
+ if len(carets) == 0 || rng.Intn(20) == 0 {
+ c := f.NewCaret()
+ carets = append(carets, c)
+ positions[c] = 0
+ continue
+ }
+ caretsIndex := rng.Intn(len(carets))
+ c := carets[caretsIndex]
+
+ if rng.Intn(40) == 0 {
+ c.Close()
+ carets[caretsIndex] = carets[len(carets)-1]
+ carets[len(carets)-1] = nil
+ carets = carets[:len(carets)-1]
+ delete(positions, c)
+ continue
+ }
+
+ // Seek, read, delete or write, with a slight bias to writing.
+ switch rng.Intn(5) {
+ case 0: // Seek.
+ p := rng.Intn(len(want) + 1)
+ c.Seek(int64(p), SeekSet)
+ positions[c] = p
+
+ case 1: // ReadByte.
+ gotB, gotErr := c.ReadByte()
+ wantB, wantErr := byte(0), io.EOF
+ if p := positions[c]; p < len(want) {
+ wantB, wantErr = want[p], nil
+ positions[c] = p + 1
+ }
+ if gotB != wantB || gotErr != wantErr {
+ t.Fatalf("i=%d: ReadByte: got %d, %v, want %d, %v", i, gotB, gotErr, wantB, wantErr)
+ }
+
+ case 2: // Delete.
+ dir := Direction(rng.Intn(2) == 0)
+ nBytes, dBytes := rng.Intn(len(want)), 0
+ pos, x, y := positions[c], 0, 0
+ if dir == Forwards {
+ dBytes = min(nBytes, len(want)-pos)
+ x, y = pos, pos+dBytes
+ } else {
+ dBytes = min(nBytes, pos)
+ x, y = pos-dBytes, pos
+ }
+ if d := c.Delete(dir, nBytes); d != dBytes {
+ t.Fatalf("i=%d: Delete: got %d bytes, want %d", i, d, dBytes)
+ }
+ want = append(want[:x], want[y:]...)
+ for _, cc := range carets {
+ if cc == c {
+ positions[cc] = x
+ continue
+ }
+ switch p := positions[cc]; {
+ case p <= x:
+ // No-op.
+ case p <= y:
+ positions[cc] = x
+ default:
+ positions[cc] -= dBytes
+ }
+ }
+
+ default: // Write.
+ i, j := rngIntPair(rng, len(iRobotBytes))
+ s := iRobotBytes[i:j]
+ c.Write(s)
+ pos := positions[c]
+ want = insert(want, s, pos)
+ for _, cc := range carets {
+ if cc == c || positions[cc] > pos {
+ positions[cc] += j - i
+ }
+ }
+ }
+
+ badPos := false
+ for ci, cc := range carets {
+ if int(cc.pos) != positions[cc] {
+ badPos = true
+ t.Errorf("i=%d, ci=%d: position: got %d, want %d", i, ci, cc.pos, positions[cc])
+ }
+ }
+ if badPos {
+ t.Fatalf("i=%d: there were inconsistent Caret positions", i)
+ }
+
+ if got := readAllText(gotBuf[:0], f); !bytes.Equal(got, want) {
+ t.Fatalf("i=%d:\ngot % x\nwant % x", i, got, want)
+ }
+ }
+ if err := checkInvariants(f); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type ijRange struct {
+ i, j int32
+}
+
+type byI []ijRange
+
+func (b byI) Len() int { return len(b) }
+func (b byI) Less(x, y int) bool { return b[x].i < b[y].i }
+func (b byI) Swap(x, y int) { b[x], b[y] = b[y], b[x] }
+
+const (
+ ignoreInvariantEmptyBoxes = 1 << iota
+)
+
+func checkInvariants(f *Frame) error {
+ return checkSomeInvariants(f, 0)
+}
+
+func checkSomeInvariants(f *Frame, ignoredInvariants uint32) error {
+ const infinity = 1e6
+
+ if !f.initialized() {
+ if !reflect.DeepEqual(*f, Frame{}) {
+ return fmt.Errorf("uninitialized Frame is not zero-valued")
+ }
+ return nil
+ }
+
+ // Iterate through the Paragraphs, Lines and Boxes. Check that every first
+ // child has no previous sibling, and no child is the first child of
+ // multiple parents. Also check the cached Paragraph and Line counts.
+ nUsedParagraphs, nUsedLines, nUsedBoxes := 0, 0, 0
+ {
+ firstLines := map[int32]bool{}
+ firstBoxes := map[int32]bool{}
+ p := f.firstP
+ if p == 0 {
+ return fmt.Errorf("firstP is zero")
+ }
+ if x := f.paragraphs[p].prev; x != 0 {
+ return fmt.Errorf("first Paragraph %d's prev: got %d, want 0", p, x)
+ }
+
+ for ; p != 0; p = f.paragraphs[p].next {
+ l := f.paragraphs[p].firstL
+ if l == 0 {
+ return fmt.Errorf("paragraphs[%d].firstL is zero", p)
+ }
+ if x := f.lines[l].prev; x != 0 {
+ return fmt.Errorf("first Line %d's prev: got %d, want 0", l, x)
+ }
+ if firstLines[l] {
+ return fmt.Errorf("duplicate first Line %d", l)
+ }
+ firstLines[l] = true
+
+ nUsedLinesThisParagraph := 0
+ for ; l != 0; l = f.lines[l].next {
+ b := f.lines[l].firstB
+ if b == 0 {
+ return fmt.Errorf("lines[%d].firstB is zero", l)
+ }
+ if x := f.boxes[b].prev; x != 0 {
+ return fmt.Errorf("first Box %d's prev: got %d, want 0", b, x)
+ }
+ if firstBoxes[b] {
+ return fmt.Errorf("duplicate first Box %d", b)
+ }
+ firstBoxes[b] = true
+
+ for ; b != 0; b = f.boxes[b].next {
+ nUsedBoxes++
+ if nUsedBoxes >= infinity {
+ return fmt.Errorf("too many used Boxes (infinite loop?)")
+ }
+ }
+ nUsedLines++
+ nUsedLinesThisParagraph++
+ }
+ nUsedParagraphs++
+
+ if n := int(f.paragraphs[p].cachedLineCountPlus1 - 1); n >= 0 && n != nUsedLinesThisParagraph {
+ return fmt.Errorf("Paragraph cached Line count: got %d, want %d", n, nUsedLinesThisParagraph)
+ }
+ }
+ if n := int(f.cachedLineCountPlus1 - 1); n >= 0 && n != nUsedLines {
+ return fmt.Errorf("Frame cached Line count: got %d, want %d", n, nUsedLines)
+ }
+ if n := int(f.cachedParagraphCountPlus1 - 1); n >= 0 && n != nUsedParagraphs {
+ return fmt.Errorf("Frame cached Paragraph count: got %d, want %d", n, nUsedParagraphs)
+ }
+ }
+
+ // Check that only the last Box can be empty.
+ if ignoredInvariants&ignoreInvariantEmptyBoxes == 0 {
+ for p := f.firstP; p != 0; {
+ nextP := f.paragraphs[p].next
+ for l := f.paragraphs[p].firstL; l != 0; {
+ nextL := f.lines[l].next
+ for b := f.lines[l].firstB; b != 0; {
+ nextB := f.boxes[b].next
+
+ emptyBox := f.boxes[b].i == f.boxes[b].j
+ lastBox := nextP == 0 && nextL == 0 && nextB == 0
+ if emptyBox && !lastBox {
+ return fmt.Errorf("boxes[%d] is empty, but isn't the last Box", b)
+ }
+
+ b = nextB
+ }
+ l = nextL
+ }
+ p = nextP
+ }
+ }
+
+ // Check the paragraphs.
+ for p, pp := range f.paragraphs {
+ if p == 0 {
+ if pp != (Paragraph{}) {
+ return fmt.Errorf("paragraphs[0] is a non-zero Paragraph: %v", pp)
+ }
+ continue
+ }
+ if pp.next < 0 || len(f.paragraphs) <= int(pp.next) {
+ return fmt.Errorf("invalid paragraphs[%d].next: got %d, want in [0, %d)", p, pp.next, len(f.paragraphs))
+ }
+ if len(f.paragraphs) <= int(pp.prev) {
+ return fmt.Errorf("invalid paragraphs[%d].prev: got %d, want in [0, %d)", p, pp.prev, len(f.paragraphs))
+ }
+ if pp.prev < 0 {
+ // The Paragraph is in the free-list, which is checked separately below.
+ continue
+ }
+ if pp.next != 0 && f.paragraphs[pp.next].prev != int32(p) {
+ return fmt.Errorf("invalid links: paragraphs[%d].next=%d, paragraphs[%d].prev=%d",
+ p, pp.next, pp.next, f.paragraphs[pp.next].prev)
+ }
+ if pp.prev != 0 && f.paragraphs[pp.prev].next != int32(p) {
+ return fmt.Errorf("invalid links: paragraphs[%d].prev=%d, paragraphs[%d].next=%d",
+ p, pp.prev, pp.prev, f.paragraphs[pp.prev].next)
+ }
+ }
+
+ // Check the paragraphs' free-list.
+ nFreeParagraphs := 0
+ for p := f.freeP; p != 0; nFreeParagraphs++ {
+ if nFreeParagraphs >= infinity {
+ return fmt.Errorf("Paragraph free-list is too long (infinite loop?)")
+ }
+ if p < 0 || len(f.paragraphs) <= int(p) {
+ return fmt.Errorf("invalid Paragraph free-list index: got %d, want in [0, %d)", p, len(f.paragraphs))
+ }
+ pp := &f.paragraphs[p]
+ if pp.prev >= 0 {
+ return fmt.Errorf("paragraphs[%d] is an invalid free-list element: %#v", p, *pp)
+ }
+ p = pp.next
+ }
+
+ // Check the lines.
+ for l, ll := range f.lines {
+ if l == 0 {
+ if ll != (Line{}) {
+ return fmt.Errorf("lines[0] is a non-zero Line: %v", ll)
+ }
+ continue
+ }
+ if ll.next < 0 || len(f.lines) <= int(ll.next) {
+ return fmt.Errorf("invalid lines[%d].next: got %d, want in [0, %d)", l, ll.next, len(f.lines))
+ }
+ if len(f.lines) <= int(ll.prev) {
+ return fmt.Errorf("invalid lines[%d].prev: got %d, want in [0, %d)", l, ll.prev, len(f.lines))
+ }
+ if ll.prev < 0 {
+ // The Line is in the free-list, which is checked separately below.
+ continue
+ }
+ if ll.next != 0 && f.lines[ll.next].prev != int32(l) {
+ return fmt.Errorf("invalid links: lines[%d].next=%d, lines[%d].prev=%d",
+ l, ll.next, ll.next, f.lines[ll.next].prev)
+ }
+ if ll.prev != 0 && f.lines[ll.prev].next != int32(l) {
+ return fmt.Errorf("invalid links: lines[%d].prev=%d, lines[%d].next=%d",
+ l, ll.prev, ll.prev, f.lines[ll.prev].next)
+ }
+ }
+
+ // Check the lines' free-list.
+ nFreeLines := 0
+ for l := f.freeL; l != 0; nFreeLines++ {
+ if nFreeLines >= infinity {
+ return fmt.Errorf("Line free-list is too long (infinite loop?)")
+ }
+ if l < 0 || len(f.lines) <= int(l) {
+ return fmt.Errorf("invalid Line free-list index: got %d, want in [0, %d)", l, len(f.lines))
+ }
+ ll := &f.lines[l]
+ if ll.prev >= 0 {
+ return fmt.Errorf("lines[%d] is an invalid free-list element: %#v", l, *ll)
+ }
+ l = ll.next
+ }
+
+ // Check the boxes.
+ for b, bb := range f.boxes {
+ if b == 0 {
+ if bb != (Box{}) {
+ return fmt.Errorf("boxes[0] is a non-zero Box: %v", bb)
+ }
+ continue
+ }
+ if bb.next < 0 || len(f.boxes) <= int(bb.next) {
+ return fmt.Errorf("invalid boxes[%d].next: got %d, want in [0, %d)", b, bb.next, len(f.boxes))
+ }
+ if len(f.boxes) <= int(bb.prev) {
+ return fmt.Errorf("invalid boxes[%d].prev: got %d, want in [0, %d)", b, bb.prev, len(f.boxes))
+ }
+ if bb.prev < 0 {
+ // The Box is in the free-list, which is checked separately below.
+ continue
+ }
+ if bb.next != 0 && f.boxes[bb.next].prev != int32(b) {
+ return fmt.Errorf("invalid links: boxes[%d].next=%d, boxes[%d].prev=%d",
+ b, bb.next, bb.next, f.boxes[bb.next].prev)
+ }
+ if bb.prev != 0 && f.boxes[bb.prev].next != int32(b) {
+ return fmt.Errorf("invalid links: boxes[%d].prev=%d, boxes[%d].next=%d",
+ b, bb.prev, bb.prev, f.boxes[bb.prev].next)
+ }
+ if 0 > bb.i || bb.i > bb.j || bb.j > int32(len(f.text)) {
+ return fmt.Errorf("invalid boxes[%d] i/j range: i=%d, j=%d, len=%d", b, bb.i, bb.j, len(f.text))
+ }
+ }
+
+ // Check the boxes' free-list.
+ nFreeBoxes := 0
+ for b := f.freeB; b != 0; nFreeBoxes++ {
+ if nFreeBoxes >= infinity {
+ return fmt.Errorf("Box free-list is too long (infinite loop?)")
+ }
+ if b < 0 || len(f.boxes) <= int(b) {
+ return fmt.Errorf("invalid Box free-list index: got %d, want in [0, %d)", b, len(f.boxes))
+ }
+ bb := &f.boxes[b]
+ if bb.i != 0 || bb.j != 0 || bb.prev >= 0 {
+ return fmt.Errorf("boxes[%d] is an invalid free-list element: %#v", b, *bb)
+ }
+ b = bb.next
+ }
+
+ // Check that the boxes' i:j ranges do not overlap, and their total length
+ // equals f.len.
+ nText, ijRanges := 0, []ijRange{}
+ for _, bb := range f.boxes {
+ if bb.i < bb.j {
+ nText += int(bb.j - bb.i)
+ ijRanges = append(ijRanges, ijRange{i: bb.i, j: bb.j})
+ }
+ }
+ sort.Sort(byI(ijRanges))
+ for x := range ijRanges {
+ if x == 0 {
+ continue
+ }
+ if ijRanges[x-1].j > ijRanges[x].i {
+ return fmt.Errorf("overlapping Box i:j ranges: %v and %v", ijRanges[x-1], ijRanges[x])
+ }
+ }
+ if nText != f.len {
+ return fmt.Errorf("text length: got %d, want %d", nText, f.len)
+ }
+
+ // Check that every Paragraph, Line and Box, other than the 0th of each, is
+ // either used or free.
+ if len(f.paragraphs) != 1+nUsedParagraphs+nFreeParagraphs {
+ return fmt.Errorf("#paragraphs (%d) != 1 + #used (%d) + #free (%d)",
+ len(f.paragraphs), nUsedParagraphs, nFreeParagraphs)
+ }
+ if len(f.lines) != 1+nUsedLines+nFreeLines {
+ return fmt.Errorf("#lines (%d) != 1 + #used (%d) + #free (%d)", len(f.lines), nUsedLines, nFreeLines)
+ }
+ if len(f.boxes) != 1+nUsedBoxes+nFreeBoxes {
+ return fmt.Errorf("#boxes (%d) != 1 + #used (%d) + #free (%d)", len(f.boxes), nUsedBoxes, nFreeBoxes)
+ }
+
+ // Check that each Caret's pos is in the Frame's 0:len range. If the
+ // Caret's cached {p,l,b,k} values are valid, also check that the Caret's k
+ // is in its Box's i:j range, and its Box b is in its Line l is in its
+ // Paragraph p.
+ for i, c := range f.carets {
+ if c.pos < 0 || f.len < int(c.pos) {
+ return fmt.Errorf("caret[%d]: pos %d outside range [0, %d]", i, c.pos, f.len)
+ }
+ if c.seqNum != f.seqNum {
+ continue
+ }
+
+ if c.b < 1 || len(f.boxes) < int(c.b) {
+ return fmt.Errorf("caret[%d]: c.b %d outside range [1, %d]", i, c.b, len(f.boxes))
+ }
+ bb := &f.boxes[c.b]
+ if c.k < bb.i || bb.j < c.k {
+ return fmt.Errorf("caret[%d]: c.k %d outside range [%d, %d]", i, c.k, bb.i, bb.j)
+ }
+
+ if c.l < 1 || len(f.lines) < int(c.l) {
+ return fmt.Errorf("caret[%d]: c.l %d outside range [1, %d]", i, c.l, len(f.lines))
+ }
+ if !f.lines[c.l].contains(f, c.b) {
+ return fmt.Errorf("caret[%d]: Line %d does not contain Box %d", i, c.l, c.b)
+ }
+
+ if c.p < 1 || len(f.paragraphs) < int(c.p) {
+ return fmt.Errorf("caret[%d]: c.p %d outside range [1, %d]", i, c.p, len(f.paragraphs))
+ }
+ if !f.paragraphs[c.p].contains(f, c.l) {
+ return fmt.Errorf("caret[%d]: Paragraph %d does not contain Line %d", i, c.p, c.l)
+ }
+ }
+
+ return nil
+}
+
+func (f *Frame) nUsedParagraphs() (n int) {
+ for p, pp := range f.paragraphs {
+ // The 0th index is a special case. A negative prev means that the
+ // Paragraph is in the free-list.
+ if p != 0 && pp.prev >= 0 {
+ n++
+ }
+ }
+ return n
+}
+
+func (f *Frame) nUsedLines() (n int) {
+ for l, ll := range f.lines {
+ // The 0th index is a special case. A negative prev means that the
+ // Line is in the free-list.
+ if l != 0 && ll.prev >= 0 {
+ n++
+ }
+ }
+ return n
+}
+
+func (f *Frame) nUsedBoxes() (n int) {
+ for b, bb := range f.boxes {
+ // The 0th index is a special case. A negative prev means that the
+ // Box is in the free-list.
+ if b != 0 && bb.prev >= 0 {
+ n++
+ }
+ }
+ return n
+}
+
+func (p *Paragraph) contains(f *Frame, l int32) bool {
+ for x := p.firstL; x != 0; x = f.lines[x].next {
+ if x == l {
+ return true
+ }
+ }
+ return false
+}
+
+func (l *Line) contains(f *Frame, b int32) bool {
+ for x := l.firstB; x != 0; x = f.boxes[x].next {
+ if x == b {
+ return true
+ }
+ }
+ return false
+}
+
+// dump is used for debugging.
+func dump(w io.Writer, f *Frame) {
+ for p := f.FirstParagraph(); p != nil; p = p.Next(f) {
+ for l := p.FirstLine(f); l != nil; l = l.Next(f) {
+ for b := l.FirstBox(f); b != nil; b = b.Next(f) {
+ fmt.Fprintf(w, "[%s]", b.TrimmedText(f))
+ }
+ fmt.Fprintln(w)
+ }
+ }
+}
diff --git a/shiny/unit/unit.go b/shiny/unit/unit.go
new file mode 100644
index 0000000..a30eb22
--- /dev/null
+++ b/shiny/unit/unit.go
@@ -0,0 +1,137 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package unit defines units of length such as inches or pixels.
+//
+// Functions like Inches and Pixels return a Value in the corresponding unit.
+// For example:
+//
+// v := unit.Inches(4.5)
+//
+// represents four and a half inches.
+//
+// Converting between pixels (px), physical units (dp, pt, in, mm) and
+// font-face-relative measures (em, ex, ch) depends on the context, such as the
+// screen's DPI resolution and the active font face. That context is
+// represented by the Converter type.
+//
+// Conversions may be lossy. Converting 4.5 inches to pixels and back may
+// result in something slightly different than 4.5. Similarly, converting 4
+// inches and 0.5 inches to pixels and then adding the results won't
+// necessarily equal the conversion of 4.5 inches to pixels.
+//
+// Note that what CSS (Cascading Style Sheets) calls "px" differs from what
+// this package calls "px". For legacy reasons, the CSS semantics are that 1
+// inch should roughly equal 96csspx regardless of the actual DPI resolution,
+// as per https://developer.mozilla.org/en/docs/Web/CSS/length. This package's
+// semantics are that 1px means exactly one physical pixel, always. This
+// package represents 1csspx as 1.666666667dp, since there are 160 density
+// independent pixels per inch, the same definition as Android.
+package unit
+
+import (
+ "fmt"
+
+ "golang.org/x/image/math/fixed"
+)
+
+const (
+ DensityIndependentPixelsPerInch = 160
+ MillimetresPerInch = 25.4
+ PointsPerInch = 72
+)
+
+// Converter converts values from one unit to another. Conversions may be
+// lossy.
+type Converter interface {
+ // Convert converts v to the given unit.
+ Convert(v Value, to Unit) Value
+
+ // Pixels converts v to a 26.6 fixed-point number of physical pixels.
+ Pixels(v Value) fixed.Int26_6
+}
+
+// Value is a number and a unit.
+type Value struct {
+ F float64
+ U Unit
+}
+
+// String implements the fmt.Stringer interface.
+func (v Value) String() string {
+ return fmt.Sprintf("%f%s", v.F, names[v.U])
+}
+
+var names = [...]string{
+ Px: "px",
+ Dp: "dp",
+ Pt: "pt",
+ In: "in",
+ Mm: "mm",
+ Em: "em",
+ Ex: "ex",
+ Ch: "ch",
+}
+
+// Unit is a unit of length, such as inches or pixels.
+type Unit uint8
+
+const (
+ // Px is a physical pixel, regardless of the DPI resolution.
+ Px Unit = iota
+
+ // Dp is 1 density independent pixel: 1/160th of an inch.
+ Dp
+ // Pt is 1 point: 1/72th of an inch.
+ Pt
+ // Mm is 1 millimetre: 1/25.4th of an inch.
+ Mm
+ // In is 1 inch.
+ //
+ // If the context does not specify a DPI resolution, the recommended
+ // fallback value for conversion is 72 pixels per inch.
+ In
+
+ // Em is the height of the active font face, disregarding extra leading
+ // such as from double-spaced lines of text.
+ //
+ // If the context does not specify an active font face, the recommended
+ // fallback value for conversion is 12pt.
+ Em
+ // Ex is the x-height of the active font face.
+ //
+ // If the context does not specify an x-height, the recommended fallback
+ // value for conversion is 0.5em.
+ Ex
+ // Ch is the character width of the numeral zero glyph '0' of the active
+ // font face.
+ //
+ // If the context does not specify a '0' glyph, the recommended fallback
+ // value for conversion is 0.5em.
+ Ch
+)
+
+// Pixels returns the given number of Px as a Value.
+func Pixels(f float64) Value { return Value{f, Px} }
+
+// DIPs returns the given number of Dp as a Value.
+func DIPs(f float64) Value { return Value{f, Dp} }
+
+// Points returns the given number of Pt as a Value.
+func Points(f float64) Value { return Value{f, Pt} }
+
+// Millimetres returns the given number of Mm as a Value.
+func Millimetres(f float64) Value { return Value{f, Mm} }
+
+// Inches returns the given number of In as a Value.
+func Inches(f float64) Value { return Value{f, In} }
+
+// Ems returns the given number of Em as a Value.
+func Ems(f float64) Value { return Value{f, Em} }
+
+// Exs returns the given number of Ex as a Value.
+func Exs(f float64) Value { return Value{f, Ex} }
+
+// Chs returns the given number of Ch as a Value.
+func Chs(f float64) Value { return Value{f, Ch} }
diff --git a/shiny/widget/flex/flex.go b/shiny/widget/flex/flex.go
new file mode 100644
index 0000000..22bd019
--- /dev/null
+++ b/shiny/widget/flex/flex.go
@@ -0,0 +1,663 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package flex provides a container widget that lays out its children
+// following the CSS flexbox algorithm.
+//
+// As the shiny widget model does not provide all of the layout features
+// of CSS, the flex package diverges in several ways. There is no item
+// inline-axis, no item margins or padding to be accounted for, and the
+// container size provided by the outer widget is taken as gospel and
+// never expanded.
+package flex
+
+import (
+ "fmt"
+ "image"
+ "math"
+
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+// Direction is the direction in which flex items are laid out.
+//
+// https://www.w3.org/TR/css-flexbox-1/#flex-direction-property
+type Direction uint8
+
+const (
+ Row Direction = iota
+ RowReverse
+ Column
+ ColumnReverse
+)
+
+// FlexWrap controls whether the container is single- or multi-line,
+// and the direction in which the lines are laid out.
+//
+// https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property
+type FlexWrap uint8
+
+const (
+ NoWrap FlexWrap = iota
+ Wrap
+ WrapReverse
+)
+
+// Justify aligns items along the main axis.
+//
+// https://www.w3.org/TR/css-flexbox-1/#justify-content-property
+type Justify uint8
+
+const (
+ JustifyStart Justify = iota // pack to start of line
+ JustifyEnd // pack to end of line
+ JustifyCenter // pack to center of line
+ JustifySpaceBetween // even spacing
+ JustifySpaceAround // even spacing, half-size on each end
+)
+
+// AlignItem aligns items along the cross axis.
+//
+// It is the 'align-items' property when applied to a Flex container,
+// and the 'align-self' property when applied to an item in LayoutData.
+//
+// https://www.w3.org/TR/css-flexbox-1/#align-items-property
+// https://www.w3.org/TR/css-flexbox-1/#propdef-align-self
+type AlignItem uint8
+
+const (
+ AlignItemAuto AlignItem = iota
+ AlignItemStart
+ AlignItemEnd
+ AlignItemCenter
+ AlignItemBaseline // TODO requires introducing inline-axis concept
+ AlignItemStretch
+)
+
+// AlignContent is the 'align-content' property.
+// It aligns container lines when there is extra space on the cross-axis.
+//
+// https://www.w3.org/TR/css-flexbox-1/#align-content-property
+type AlignContent uint8
+
+const (
+ AlignContentStretch AlignContent = iota
+ AlignContentStart
+ AlignContentEnd
+ AlignContentCenter
+ AlignContentSpaceBetween
+ AlignContentSpaceAround
+)
+
+// Basis sets the base size of a flex item.
+//
+// A default basis of Auto means the flex container uses the
+// MeasuredSize of an item. Otherwise a Definite Basis will
+// override the MeasuredSize with BasisPx.
+//
+// TODO: do we (or will we )have a useful notion of Content in the
+// widget layout model that is separate from MeasuredSize? If not,
+// we could consider completely removing this concept from this
+// flex implementation.
+type Basis uint8
+
+const (
+ Auto Basis = iota
+ Definite
+)
+
+// Flex is a container widget that lays out its children following the
+// CSS flexbox algorithm.
+type Flex struct {
+ node.ContainerEmbed
+
+ Direction Direction
+ Wrap FlexWrap
+ Justify Justify
+ AlignItems AlignItem
+ AlignContent AlignContent
+}
+
+// NewFlex returns a new Flex widget containing the given children.
+func NewFlex(children ...node.Node) *Flex {
+ w := new(Flex)
+ w.Wrapper = w
+ for _, c := range children {
+ w.Insert(c, nil)
+ }
+ return w
+}
+
+func (w *Flex) Measure(t *theme.Theme, widthHint, heightHint int) {
+ // As Measure is a bottom-up calculation of natural size, we have no
+ // hint yet as to how we should flex. So we ignore Wrap, Justify,
+ // AlignItem, AlignContent.
+ for c := w.FirstChild; c != nil; c = c.NextSibling {
+ // TODO: pass down width/height hints?
+ c.Wrapper.Measure(t, node.NoHint, node.NoHint)
+ if d, ok := c.LayoutData.(LayoutData); ok {
+ _ = d
+ // TODO Measure
+ }
+ }
+}
+
+func (w *Flex) Layout(t *theme.Theme) {
+ var children []element
+ for c := w.FirstChild; c != nil; c = c.NextSibling {
+ children = append(children, element{
+ flexBaseSize: float64(w.flexBaseSize(c)),
+ n: c,
+ })
+ }
+
+ containerMainSize := float64(w.mainSize(w.Rect.Size()))
+ containerCrossSize := float64(w.crossSize(w.Rect.Size()))
+
+ // Β§9.3.5 collect children into flex lines
+ var lines []flexLine
+ if w.Wrap == NoWrap {
+ line := flexLine{child: make([]*element, len(children))}
+ for i := range children {
+ child := &children[i]
+ line.child[i] = child
+ line.mainSize += child.flexBaseSize
+ }
+ lines = []flexLine{line}
+ } else {
+ var line flexLine
+
+ for i := range children {
+ child := &children[i]
+
+ hypotheticalMainSize := w.clampSize(child.flexBaseSize, child.n)
+
+ if line.mainSize > 0 && line.mainSize+hypotheticalMainSize > containerMainSize {
+ lines = append(lines, line)
+ line = flexLine{}
+ }
+ line.child = append(line.child, child)
+ line.mainSize += hypotheticalMainSize
+
+ if d, ok := child.n.LayoutData.(LayoutData); ok && d.BreakAfter {
+ lines = append(lines, line)
+ line = flexLine{}
+ }
+ }
+ if len(line.child) > 0 || len(children) == 0 {
+ lines = append(lines, line)
+ }
+
+ if w.Wrap == WrapReverse {
+ for i := 0; i < len(lines)/2; i++ {
+ lines[i], lines[len(lines)-i-1] = lines[len(lines)-i-1], lines[i]
+ }
+ }
+ }
+
+ // Β§9.3.6 resolve flexible lengths (details in section Β§9.7)
+ for l := range lines {
+ line := &lines[l]
+ grow := line.mainSize < containerMainSize // Β§9.7.1
+
+ // Β§9.7.2 freeze inflexible children.
+ for _, child := range line.child {
+ mainSize := float64(w.mainSize(child.n.MeasuredSize))
+ hypotheticalMainSize := w.clampSize(mainSize, child.n)
+ if grow {
+ if growFactor(child.n) == 0 || float64(w.flexBaseSize(child.n)) > hypotheticalMainSize {
+ child.frozen = true
+ child.mainSize = hypotheticalMainSize
+ }
+ } else {
+ if shrinkFactor(child.n) == 0 || float64(w.flexBaseSize(child.n)) < hypotheticalMainSize {
+ child.frozen = true
+ child.mainSize = hypotheticalMainSize
+ }
+ }
+ }
+
+ // Β§9.7.3 calculate initial free space
+ initFreeSpace := float64(w.mainSize(w.Rect.Size()))
+ for _, child := range line.child {
+ if child.frozen {
+ initFreeSpace -= child.mainSize
+ } else {
+ initFreeSpace -= float64(w.flexBaseSize(child.n))
+ }
+ }
+
+ // Β§9.7.4 flex loop
+ for {
+ // Check for flexible items.
+ allFrozen := true
+ for _, child := range line.child {
+ if !child.frozen {
+ allFrozen = false
+ break
+ }
+ }
+ if allFrozen {
+ break
+ }
+
+ // Calculate remaining free space.
+ remFreeSpace := float64(w.mainSize(w.Rect.Size()))
+ unfrozenFlexFactor := 0.0
+ for _, child := range line.child {
+ if child.frozen {
+ remFreeSpace -= child.mainSize
+ } else {
+ remFreeSpace -= float64(w.flexBaseSize(child.n))
+ if grow {
+ unfrozenFlexFactor += growFactor(child.n)
+ } else {
+ unfrozenFlexFactor += shrinkFactor(child.n)
+ }
+ }
+ }
+ if unfrozenFlexFactor < 1 {
+ p := initFreeSpace * unfrozenFlexFactor
+ if math.Abs(p) < math.Abs(remFreeSpace) {
+ remFreeSpace = p
+ }
+ }
+
+ // Distribute free space proportional to flex factors.
+ if grow {
+ for _, child := range line.child {
+ if child.frozen {
+ continue
+ }
+ r := growFactor(child.n) / unfrozenFlexFactor
+ child.mainSize = float64(w.flexBaseSize(child.n)) + r*remFreeSpace
+ }
+ } else {
+ sumScaledShrinkFactor := 0.0
+ for _, child := range line.child {
+ if child.frozen {
+ continue
+ }
+ scaledShrinkFactor := float64(w.flexBaseSize(child.n)) * shrinkFactor(child.n)
+ sumScaledShrinkFactor += scaledShrinkFactor
+ }
+ for _, child := range line.child {
+ if child.frozen {
+ continue
+ }
+ scaledShrinkFactor := float64(w.flexBaseSize(child.n)) * shrinkFactor(child.n)
+ r := float64(scaledShrinkFactor) / sumScaledShrinkFactor
+ child.mainSize = float64(w.flexBaseSize(child.n)) - r*math.Abs(float64(remFreeSpace))
+ }
+ }
+
+ // Fix min/max violations.
+ sumClampDiff := 0.0
+ for _, child := range line.child {
+ // TODO: we work in whole pixels but flex calculations are done in
+ // fractional pixels. Take this oppertunity to clamp us to whole
+ // pixels and make sure we sum correctly.
+ if child.frozen {
+ continue
+ }
+ child.unclamped = child.mainSize
+ child.mainSize = w.clampSize(child.mainSize, child.n)
+
+ sumClampDiff += child.mainSize - child.unclamped
+ }
+
+ // Freeze over-flexed items.
+ switch {
+ case sumClampDiff == 0:
+ for _, child := range line.child {
+ child.frozen = true
+ }
+ case sumClampDiff > 0:
+ for _, child := range line.child {
+ if child.mainSize > child.unclamped {
+ child.frozen = true
+ }
+ }
+ case sumClampDiff < 0:
+ for _, child := range line.child {
+ if child.mainSize < child.unclamped {
+ child.frozen = true
+ }
+ }
+ }
+ }
+
+ // Β§9.7.5 set main size
+ // At this point, child.mainSize is right.
+ }
+
+ // Β§9.4 determine cross size
+ // Β§9.4.7 calculate hypothetical cross size of each element
+ for l := range lines {
+ for _, child := range lines[l].child {
+ child.crossSize = float64(w.crossSize(child.n.MeasuredSize))
+ if child.mainSize < float64(w.mainSize(child.n.MeasuredSize)) {
+ if r, ok := aspectRatio(child.n); ok {
+ child.crossSize = child.mainSize / r
+ }
+ }
+ if d, ok := child.n.LayoutData.(LayoutData); ok {
+ minSize := float64(w.crossSize(d.MinSize))
+ if minSize > child.crossSize {
+ child.crossSize = minSize
+ } else if d.MaxSize != nil {
+ maxSize := float64(w.crossSize(*d.MaxSize))
+ if child.crossSize > maxSize {
+ child.crossSize = maxSize
+ }
+ }
+ }
+ }
+ }
+ if len(lines) == 1 {
+ // Β§9.4.8 single line
+ switch w.Direction {
+ case Row, RowReverse:
+ lines[0].crossSize = float64(w.Rect.Size().Y)
+ case Column, ColumnReverse:
+ lines[0].crossSize = float64(w.Rect.Size().X)
+ }
+ } else {
+ // Β§9.4.8 multi-line
+ for l := range lines {
+ line := &lines[l]
+ // TODO Β§9.4.8.1, no concept of inline-axis yet
+ max := 0.0
+ for _, child := range line.child {
+ if child.crossSize > max {
+ max = child.crossSize
+ }
+ }
+ line.crossSize = max
+ }
+ }
+ off := 0.0
+ for l := range lines {
+ line := &lines[l]
+ line.crossOffset = off
+ off += line.crossSize
+ }
+ // Β§9.4.9 align-content: stretch
+ remCrossSize := containerCrossSize - off
+ if w.AlignContent == AlignContentStretch && remCrossSize > 0 {
+ add := remCrossSize / float64(len(lines))
+ for l := range lines {
+ line := &lines[l]
+ line.crossOffset += float64(l) * add
+ line.crossSize += add
+ }
+ }
+ // Note: no equiv. to Β§9.4.10 "visibility: collapse".
+ // Β§9.4.11 align-item: stretch
+ for l := range lines {
+ line := &lines[l]
+ for _, child := range line.child {
+ align := w.alignItem(child.n)
+ if align == AlignItemStretch && child.crossSize < line.crossSize {
+ child.crossSize = line.crossSize
+ }
+ }
+ }
+
+ // Β§9.5 main axis alignment
+ for l := range lines {
+ line := &lines[l]
+ total := 0.0
+ for _, child := range line.child {
+ total += child.mainSize
+ }
+ remFree := containerMainSize - total
+ off, spacing := 0.0, 0.0
+ switch w.Justify {
+ case JustifyStart:
+ case JustifyEnd:
+ off = remFree
+ case JustifyCenter:
+ off = remFree / 2
+ case JustifySpaceBetween:
+ spacing = remFree / float64(len(line.child)-1)
+ case JustifySpaceAround:
+ spacing = remFree / float64(len(line.child))
+ off = spacing / 2
+ }
+ for _, child := range line.child {
+ child.mainOffset = off
+ off += spacing + child.mainSize
+ }
+ }
+
+ // Β§9.6 cross axis alignment
+ // Β§9.6.13 no 'auto' margins
+ // Β§9.6.14 align items inside line, 'align-self'.
+ for l := range lines {
+ line := &lines[l]
+ for _, child := range line.child {
+ child.crossOffset = line.crossOffset
+ if child.crossSize == line.crossSize {
+ continue
+ }
+ diff := line.crossSize - child.crossSize
+ switch w.alignItem(child.n) {
+ case AlignItemStart:
+ // already laid out correctly
+ case AlignItemEnd:
+ child.crossOffset = line.crossOffset + diff
+ case AlignItemCenter:
+ child.crossOffset = line.crossOffset + diff/2
+ case AlignItemBaseline:
+ // TODO requires introducing inline-axis concept
+ case AlignItemStretch:
+ // handled earlier, so child.crossSize == line.crossSize
+ }
+ }
+ }
+ // Β§9.6.15 determine container cross size used
+ crossSize := lines[len(lines)-1].crossOffset + lines[len(lines)-1].crossSize
+ remFree := containerCrossSize - crossSize
+
+ // Β§9.6.16 align flex lines, 'align-content'.
+ if remFree > 0 {
+ spacing, off := 0.0, 0.0
+ switch w.AlignContent {
+ case AlignContentStart:
+ // already laid out correctly
+ case AlignContentEnd:
+ off = remFree
+ case AlignContentCenter:
+ off = remFree / 2
+ case AlignContentSpaceBetween:
+ spacing = remFree / float64(len(lines)-1)
+ case AlignContentSpaceAround:
+ spacing = remFree / float64(len(lines))
+ off = spacing / 2
+ case AlignContentStretch:
+ // handled earlier, why is remFree > 0?
+ }
+ if w.AlignContent != AlignContentStart && w.AlignContent != AlignContentStretch {
+ for l := range lines {
+ line := &lines[l]
+ line.crossOffset += off
+ for _, child := range line.child {
+ child.crossOffset += off
+ }
+ off += spacing
+ }
+ }
+ }
+
+ switch w.Direction {
+ case RowReverse, ColumnReverse:
+ // Invert main-start and main-end.
+ for l := range lines {
+ line := &lines[l]
+ for _, child := range line.child {
+ child.mainOffset = containerMainSize - child.mainOffset - child.mainSize
+ }
+ }
+ }
+
+ // Layout complete. Generate child Rect values.
+ for l := range lines {
+ line := &lines[l]
+ for _, child := range line.child {
+ switch w.Direction {
+ case Row, RowReverse:
+ child.n.Rect.Min.X = round(child.mainOffset)
+ child.n.Rect.Max.X = round(child.mainOffset + child.mainSize)
+ child.n.Rect.Min.Y = round(child.crossOffset)
+ child.n.Rect.Max.Y = round(child.crossOffset + child.crossSize)
+ case Column, ColumnReverse:
+ child.n.Rect.Min.Y = round(child.mainOffset)
+ child.n.Rect.Max.Y = round(child.mainOffset + child.mainSize)
+ child.n.Rect.Min.X = round(child.crossOffset)
+ child.n.Rect.Max.X = round(child.crossOffset + child.crossSize)
+ default:
+ panic(fmt.Sprint("flex: bad direction ", w.Direction))
+ }
+ child.n.Wrapper.Layout(t)
+ }
+ }
+}
+
+func round(f float64) int {
+ return int(math.Floor(f + .5))
+}
+
+type element struct {
+ n *node.Embed
+ flexBaseSize float64
+ frozen bool
+ unclamped float64
+ mainSize float64
+ mainOffset float64
+ crossSize float64
+ crossOffset float64
+}
+
+type flexLine struct {
+ mainSize float64
+ crossSize float64
+ crossOffset float64
+ child []*element
+}
+
+func (w *Flex) alignItem(n *node.Embed) AlignItem {
+ align := w.AlignItems
+ if d, ok := n.LayoutData.(LayoutData); ok {
+ align = d.Align
+ }
+ return align
+}
+
+// flexBaseSize calculates flex base size as per Β§9.2.3
+func (w *Flex) flexBaseSize(n *node.Embed) int {
+ basis := Auto
+ if d, ok := n.LayoutData.(LayoutData); ok {
+ basis = d.Basis
+ }
+ // TODO Content Β§9.2.3.B, C, D
+ switch basis {
+ case Definite: // A
+ return n.LayoutData.(LayoutData).BasisPx
+ case Auto: // E
+ return w.mainSize(n.MeasuredSize)
+ default:
+ panic(fmt.Sprintf("flex: unknown flex-basis %v", basis))
+ }
+}
+
+func growFactor(n *node.Embed) float64 {
+ if d, ok := n.LayoutData.(LayoutData); ok {
+ return d.Grow
+ }
+ return 0
+}
+
+func shrinkFactor(n *node.Embed) float64 {
+ if d, ok := n.LayoutData.(LayoutData); ok && d.Shrink != nil {
+ return *d.Shrink
+ }
+ return 1
+}
+
+func aspectRatio(n *node.Embed) (ratio float64, ok bool) {
+ // TODO: source a formal description of "intrinsic aspect ratio"
+ d, ok := n.LayoutData.(LayoutData)
+ if ok && d.MinSize.X != 0 && d.MinSize.Y != 0 {
+ return float64(d.MinSize.X) / float64(d.MinSize.Y), true
+ }
+ return 0, false
+}
+
+func (w *Flex) clampSize(size float64, n *node.Embed) float64 {
+ if d, ok := n.LayoutData.(LayoutData); ok {
+ minSize := float64(w.mainSize(d.MinSize))
+ if minSize > size {
+ size = minSize
+ } else if d.MaxSize != nil {
+ maxSize := float64(w.mainSize(*d.MaxSize))
+ if size > maxSize {
+ size = maxSize
+ }
+ }
+ }
+ if size < 0 {
+ return 0
+ }
+ return size
+}
+
+func (w *Flex) mainSize(p image.Point) int {
+ switch w.Direction {
+ case Row, RowReverse:
+ return p.X
+ case Column, ColumnReverse:
+ return p.Y
+ default:
+ panic(fmt.Sprint("flex: bad direction ", w.Direction))
+ }
+}
+
+func (w *Flex) crossSize(p image.Point) int {
+ switch w.Direction {
+ case Row, RowReverse:
+ return p.Y
+ case Column, ColumnReverse:
+ return p.X
+ default:
+ panic(fmt.Sprint("flex: bad direction ", w.Direction))
+ }
+}
+
+// LayoutData is the node LayoutData type for a Flex's children.
+type LayoutData struct {
+ MinSize image.Point // TODO use unit.Value
+ MaxSize *image.Point // TODO use unit.Value
+
+ // Grow determines how much a node will grow relative to its siblings.
+ Grow float64
+
+ // Shrink is the flex shrink factor which determines how much a node
+ // will shrink relative to its siblings. If nil, a default shrink
+ // factor of 1 is used.
+ Shrink *float64
+
+ // Basis determines the initial size of the node in the direction
+ // of the flex container (the main axis).
+ //
+ // If set to Definite, the value stored in BasisPx is used.
+ Basis Basis
+ BasisPx int // TODO use unit package?
+
+ Align AlignItem
+
+ // BreakAfter forces the next node onto the next flex line.
+ BreakAfter bool
+}
diff --git a/shiny/widget/flex/flex_test.go b/shiny/widget/flex/flex_test.go
new file mode 100644
index 0000000..4007c32
--- /dev/null
+++ b/shiny/widget/flex/flex_test.go
@@ -0,0 +1,420 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package flex
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "image/color"
+ "testing"
+
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+type layoutTest struct {
+ desc string
+ direction Direction
+ wrap FlexWrap
+ alignContent AlignContent
+ justify Justify
+ size image.Point // size of container
+ measured [][2]float64 // MeasuredSize of child elements
+ layoutData []LayoutData // LayoutData of child elements
+ want []image.Rectangle // final Rect of child elements
+}
+
+func (t *layoutTest) html() string {
+ buf := new(bytes.Buffer)
+ fmt.Fprintf(buf, `<style>
+#container {
+ display: flex;
+ width: %dpx;
+ height: %dpx;
+`, t.size.X, t.size.Y)
+
+ switch t.direction {
+ case Row:
+ case RowReverse:
+ fmt.Fprintf(buf, "\tflex-direction: row-reverse;\n")
+ case Column:
+ fmt.Fprintf(buf, "\tflex-direction: column;\n")
+ case ColumnReverse:
+ fmt.Fprintf(buf, "\tflex-direction: column-reverse;\n")
+ }
+ switch t.wrap {
+ case NoWrap:
+ case Wrap:
+ fmt.Fprintf(buf, "\tflex-wrap: wrap;\n")
+ case WrapReverse:
+ fmt.Fprintf(buf, "\tflex-wrap: wrap-reverse;\n")
+ }
+ switch t.alignContent {
+ case AlignContentStart:
+ case AlignContentEnd:
+ fmt.Fprintf(buf, "\talign-content: flex-end;\n")
+ case AlignContentCenter:
+ fmt.Fprintf(buf, "\talign-content: center;\n")
+ case AlignContentSpaceBetween:
+ fmt.Fprintf(buf, "\talign-content: space-between;\n")
+ case AlignContentSpaceAround:
+ fmt.Fprintf(buf, "\talign-content: space-around;\n")
+ case AlignContentStretch:
+ fmt.Fprintf(buf, "\talign-content: stretch;\n")
+ }
+ switch t.justify {
+ case JustifyStart:
+ case JustifyEnd:
+ fmt.Fprintf(buf, "\tjustify-content: flex-end;\n")
+ case JustifyCenter:
+ fmt.Fprintf(buf, "\tjustify-content: center;\n")
+ case JustifySpaceBetween:
+ fmt.Fprintf(buf, "\tjustify-content: space-between;\n")
+ case JustifySpaceAround:
+ fmt.Fprintf(buf, "\tjustify-content: space-around;\n")
+ }
+ fmt.Fprintf(buf, "}\n")
+
+ for i, m := range t.measured {
+ fmt.Fprintf(buf, `#child%d {
+ width: %.2fpx;
+ height: %.2fpx;
+`, i, m[0], m[1])
+ c := colors[i%len(colors)]
+ fmt.Fprintf(buf, "\tbackground-color: rgb(%d, %d, %d);\n", c.R, c.G, c.B)
+ if t.layoutData != nil {
+ d := t.layoutData[i]
+ if d.MinSize.X != 0 {
+ fmt.Fprintf(buf, "\tmin-width: %dpx;\n", d.MinSize.X)
+ }
+ if d.MinSize.Y != 0 {
+ fmt.Fprintf(buf, "\tmin-height: %dpx;\n", d.MinSize.Y)
+ }
+ if d.MaxSize != nil {
+ fmt.Fprintf(buf, "\tmax-width: %dpx;\n", d.MaxSize.X)
+ fmt.Fprintf(buf, "\tmax-height: %dpx;\n", d.MaxSize.Y)
+ }
+ if d.Grow != 0 {
+ fmt.Fprintf(buf, "\tflex-grow: %f;\n", d.Grow)
+ }
+ if d.Shrink != nil {
+ fmt.Fprintf(buf, "\tflex-shrink: %f;\n", *d.Shrink)
+ }
+ // TODO: Basis, Align, BreakAfter
+ }
+ fmt.Fprintf(buf, "}\n")
+ }
+ fmt.Fprintf(buf, `</style>
+<div id="container">
+`)
+ for i := range t.measured {
+ fmt.Fprintf(buf, "\t<div id=\"child%d\"></div>\n", i)
+ }
+ fmt.Fprintf(buf, `</div>
+<pre id="out"></pre>
+<script>
+var out = document.getElementById("out");
+var container = document.getElementById("container");
+for (var i = 0; i < container.children.length; i++) {
+ var c = container.children[i];
+ var ctop = c.offsetTop - container.offsetTop;
+ var cleft = c.offsetLeft - container.offsetLeft;
+ var cbottom = ctop + c.offsetHeight;
+ var cright = cleft + c.offsetWidth;
+
+ out.innerHTML += "\timage.Rect(" + cleft + ", " + ctop + ", " + cright + ", " + cbottom + "),\n";
+}
+</script>
+`)
+
+ return buf.String()
+}
+
+var colors = []color.RGBA{
+ {0x00, 0x7f, 0x7f, 0xff}, // Cyan
+ {0x7f, 0x00, 0x7f, 0xff}, // Magenta
+ {0x7f, 0x7f, 0x00, 0xff}, // Yellow
+ {0xff, 0x00, 0x00, 0xff}, // Red
+ {0x00, 0xff, 0x00, 0xff}, // Green
+ {0x00, 0x00, 0xff, 0xff}, // Blue
+}
+
+var layoutTests = []layoutTest{{
+ desc: "no children",
+}, {
+ desc: "no children wrapped",
+ wrap: Wrap,
+}, {
+ desc: "single unflexed child",
+ size: image.Point{100, 100},
+ measured: [][2]float64{{100, 100}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 100, 100),
+ },
+}, {
+ desc: "unflexed children",
+ size: image.Point{350, 100},
+ measured: [][2]float64{{100, 100}, {100, 100}, {100, 100}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 100, 100),
+ image.Rect(100, 0, 200, 100),
+ image.Rect(200, 0, 300, 100),
+ },
+}, {
+ desc: "final child that grows",
+ size: image.Point{300, 100},
+ measured: [][2]float64{{100, 100}, {100, 100}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 100, 100),
+ image.Rect(100, 0, 300, 100),
+ },
+ layoutData: []LayoutData{{}, {Grow: 1}},
+}, {
+ desc: "share growth equally",
+ size: image.Point{300, 100},
+ measured: [][2]float64{{50, 50}, {100, 100}, {100, 100}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 50, 50),
+ image.Rect(50, 0, 175, 100),
+ image.Rect(175, 0, 300, 100),
+ },
+ layoutData: []LayoutData{{}, {Grow: 1}, {Grow: 1}},
+}, {
+ desc: "share growth inequally",
+ size: image.Point{300, 100},
+ measured: [][2]float64{{20, 100}, {20, 100}, {20, 100}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 30, 100),
+ image.Rect(30, 0, 130, 100),
+ image.Rect(130, 0, 300, 100),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 4},
+ },
+}, {
+ desc: "wrap",
+ size: image.Point{300, 200},
+ wrap: Wrap,
+ measured: [][2]float64{{150, 100}, {280, 100}, {20, 100}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 30, 100),
+ image.Rect(0, 100, 280, 200),
+ image.Rect(280, 100, 300, 200),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 1},
+ },
+}, {
+ desc: "align-content default",
+ size: image.Point{300, 200},
+ direction: Column,
+ wrap: Wrap,
+ measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 30, 100),
+ image.Rect(0, 100, 160, 200),
+ image.Rect(220, 0, 240, 195),
+ image.Rect(220, 195, 225, 200),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 1},
+ {MaxSize: &image.Point{5, 5}},
+ },
+}, {
+ desc: "align-content: space-around",
+ size: image.Point{300, 200},
+ direction: Column,
+ wrap: Wrap,
+ alignContent: AlignContentSpaceAround,
+ measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
+ want: []image.Rectangle{
+ image.Rect(30, 0, 60, 100),
+ image.Rect(30, 100, 190, 200),
+ image.Rect(250, 0, 270, 195),
+ image.Rect(250, 195, 255, 200),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 1},
+ {MaxSize: &image.Point{5, 5}},
+ },
+}, {
+ desc: "align-content: space-between",
+ size: image.Point{300, 200},
+ direction: Column,
+ wrap: Wrap,
+ alignContent: AlignContentSpaceBetween,
+ measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
+ want: []image.Rectangle{
+ image.Rect(0, 0, 30, 100),
+ image.Rect(0, 100, 160, 200),
+ image.Rect(280, 0, 300, 195),
+ image.Rect(280, 195, 285, 200),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 1},
+ {MaxSize: &image.Point{5, 5}},
+ },
+}, {
+ desc: "align-content: end",
+ size: image.Point{300, 200},
+ direction: Column,
+ wrap: Wrap,
+ alignContent: AlignContentEnd,
+ measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
+ want: []image.Rectangle{
+ image.Rect(120, 0, 150, 100),
+ image.Rect(120, 100, 280, 200),
+ image.Rect(280, 0, 300, 195),
+ image.Rect(280, 195, 285, 200),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 1},
+ {MaxSize: &image.Point{5, 5}},
+ },
+}, {
+ desc: "align-content: center",
+ size: image.Point{300, 200},
+ direction: Column,
+ wrap: Wrap,
+ alignContent: AlignContentCenter,
+ measured: [][2]float64{{150, 100}, {160, 100}, {20, 100}, {300, 300}},
+ want: []image.Rectangle{
+ image.Rect(60, 0, 90, 100),
+ image.Rect(60, 100, 220, 200),
+ image.Rect(220, 0, 240, 195),
+ image.Rect(220, 195, 225, 200),
+ },
+ layoutData: []LayoutData{
+ {MaxSize: &image.Point{30, 100}, Grow: 1},
+ {MinSize: image.Point{100, 0}, Grow: 1},
+ {Grow: 1},
+ {MaxSize: &image.Point{5, 5}},
+ },
+}, {
+ desc: "column-reverse",
+ size: image.Point{300, 60},
+ direction: ColumnReverse,
+ wrap: Wrap,
+ measured: [][2]float64{{25, 25}, {25, 25}, {25, 25}, {25, 25}, {25, 25}},
+ want: []image.Rectangle{
+ image.Rect(0, 35, 25, 60),
+ image.Rect(0, 0, 25, 35),
+ image.Rect(100, 35, 125, 60),
+ image.Rect(100, 10, 125, 35),
+ image.Rect(200, 0, 225, 60),
+ },
+ layoutData: []LayoutData{
+ {},
+ {Grow: 1},
+ {},
+ {},
+ {Grow: 1},
+ },
+}, {
+ desc: "justify-content: flex-start",
+ size: image.Point{90, 90},
+ measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
+ justify: JustifyStart,
+ want: []image.Rectangle{
+ image.Rect(0, 0, 5, 10),
+ image.Rect(5, 0, 10, 10),
+ image.Rect(10, 0, 20, 10),
+ },
+}, {
+ desc: "justify-content: flex-end",
+ size: image.Point{90, 90},
+ measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
+ justify: JustifyEnd,
+ want: []image.Rectangle{
+ image.Rect(70, 0, 75, 10),
+ image.Rect(75, 0, 80, 10),
+ image.Rect(80, 0, 90, 10),
+ },
+}, {
+ desc: "justify-content: center",
+ size: image.Point{90, 90},
+ measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
+ justify: JustifyCenter,
+ want: []image.Rectangle{
+ image.Rect(35, 0, 40, 10),
+ image.Rect(40, 0, 45, 10),
+ image.Rect(45, 0, 55, 10),
+ },
+}, {
+ desc: "justify-content: space-between",
+ size: image.Point{90, 90},
+ measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
+ justify: JustifySpaceBetween,
+ want: []image.Rectangle{
+ image.Rect(0, 0, 5, 10),
+ image.Rect(40, 0, 45, 10),
+ image.Rect(80, 0, 90, 10),
+ },
+}, {
+ desc: "justify-content: space-around",
+ size: image.Point{90, 90},
+ measured: [][2]float64{{5, 10}, {5, 10}, {10, 10}},
+ justify: JustifySpaceAround,
+ want: []image.Rectangle{
+ image.Rect(12, 0, 17, 10),
+ image.Rect(40, 0, 45, 10),
+ image.Rect(68, 0, 78, 10),
+ },
+}}
+
+func TestLayout(t *testing.T) {
+ for testNum, test := range layoutTests {
+ var children []node.Node
+ for i, sz := range test.measured {
+ u := widget.NewUniform(theme.StaticColor(colors[i]), nil)
+ n := widget.NewSizer(unit.Pixels(sz[0]), unit.Pixels(sz[1]), u)
+ if test.layoutData != nil {
+ n.LayoutData = test.layoutData[i]
+ }
+ children = append(children, n)
+ }
+
+ w := NewFlex(children...)
+ w.Direction = test.direction
+ w.Wrap = test.wrap
+ w.AlignContent = test.alignContent
+ w.Justify = test.justify
+
+ w.Measure(nil, node.NoHint, node.NoHint)
+ w.Rect = image.Rectangle{Max: test.size}
+ w.Layout(nil)
+
+ bad := false
+ for i, n := range children {
+ if n.Wrappee().Rect != test.want[i] {
+ bad = true
+ break
+ }
+ }
+ if bad {
+ t.Logf("Bad test %d, %q:\n%s", testNum, test.desc, test.html())
+ }
+ for i, n := range children {
+ if got, want := n.Wrappee().Rect, test.want[i]; got != want {
+ t.Errorf("[%d].Rect=%v, want %v", i, got, want)
+ }
+ }
+ }
+}
diff --git a/shiny/widget/flow.go b/shiny/widget/flow.go
new file mode 100644
index 0000000..9dcc5a2
--- /dev/null
+++ b/shiny/widget/flow.go
@@ -0,0 +1,207 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "image"
+
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+// TODO: padding, alignment.
+
+// Flow is a container widget that lays out its children in sequence along an
+// axis, either horizontally or vertically. The children's laid out size may
+// differ from their natural size, along or across that axis, if a child's
+// LayoutData is a FlowLayoutData.
+type Flow struct {
+ node.ContainerEmbed
+ Axis Axis
+}
+
+// NewFlow returns a new Flow widget containing the given children.
+func NewFlow(a Axis, children ...node.Node) *Flow {
+ w := &Flow{
+ Axis: a,
+ }
+ w.Wrapper = w
+ for _, c := range children {
+ w.Insert(c, nil)
+ }
+ return w
+}
+
+func (w *Flow) Measure(t *theme.Theme, widthHint, heightHint int) {
+ if w.Axis != AxisHorizontal && w.Axis != AxisVertical {
+ w.ContainerEmbed.Measure(t, widthHint, heightHint)
+ return
+ }
+
+ if w.Axis == AxisHorizontal {
+ widthHint = node.NoHint
+ }
+ if w.Axis == AxisVertical {
+ heightHint = node.NoHint
+ }
+
+ mSize := image.Point{}
+ for c := w.FirstChild; c != nil; c = c.NextSibling {
+ c.Wrapper.Measure(t, widthHint, heightHint)
+ if w.Axis == AxisHorizontal {
+ mSize.X += c.MeasuredSize.X
+ if mSize.Y < c.MeasuredSize.Y {
+ mSize.Y = c.MeasuredSize.Y
+ }
+ } else {
+ mSize.Y += c.MeasuredSize.Y
+ if mSize.X < c.MeasuredSize.X {
+ mSize.X = c.MeasuredSize.X
+ }
+ }
+ }
+ w.MeasuredSize = mSize
+}
+
+func (w *Flow) Layout(t *theme.Theme) {
+ if w.Axis != AxisHorizontal && w.Axis != AxisVertical {
+ w.ContainerEmbed.Layout(t)
+ return
+ }
+
+ extra, totalExpandWeight, totalShrinkWeight := 0, 0, 0
+ if w.Axis == AxisHorizontal {
+ extra = w.Rect.Dx()
+ } else {
+ extra = w.Rect.Dy()
+ }
+ for c := w.FirstChild; c != nil; c = c.NextSibling {
+ if d, ok := c.LayoutData.(FlowLayoutData); ok && d.AlongWeight > 0 {
+ if d.AlongWeight <= 0 {
+ continue
+ }
+ if d.ExpandAlong {
+ totalExpandWeight += d.AlongWeight
+ }
+ if d.ShrinkAlong {
+ totalShrinkWeight += d.AlongWeight
+ }
+ }
+ if w.Axis == AxisHorizontal {
+ extra -= c.MeasuredSize.X
+ } else {
+ extra -= c.MeasuredSize.Y
+ }
+ }
+ expand, shrink, totalWeight := extra > 0, extra < 0, 0
+ if expand {
+ if totalExpandWeight == 0 {
+ expand = false
+ } else {
+ totalWeight = totalExpandWeight
+ }
+ }
+ if shrink {
+ if totalShrinkWeight == 0 {
+ shrink = false
+ } else {
+ totalWeight = totalShrinkWeight
+ }
+ }
+
+ p := image.Point{}
+ for c := w.FirstChild; c != nil; c = c.NextSibling {
+ q := p.Add(c.MeasuredSize)
+ if d, ok := c.LayoutData.(FlowLayoutData); ok {
+ if d.AlongWeight > 0 {
+ if (expand && d.ExpandAlong) || (shrink && d.ShrinkAlong) {
+ delta := extra * d.AlongWeight / totalWeight
+ extra -= delta
+ totalWeight -= d.AlongWeight
+ if w.Axis == AxisHorizontal {
+ q.X += delta
+ if q.X < p.X {
+ q.X = p.X
+ }
+ } else {
+ q.Y += delta
+ if q.Y < p.Y {
+ q.Y = p.Y
+ }
+ }
+ }
+ }
+
+ if w.Axis == AxisHorizontal {
+ q.Y = stretchAcross(q.Y, w.Rect.Dy(), d.ExpandAcross, d.ShrinkAcross)
+ } else {
+ q.X = stretchAcross(q.X, w.Rect.Dx(), d.ExpandAcross, d.ShrinkAcross)
+ }
+ }
+ c.Rect = image.Rectangle{
+ Min: p,
+ Max: q,
+ }
+ c.Wrapper.Layout(t)
+ if w.Axis == AxisHorizontal {
+ p.X = q.X
+ } else {
+ p.Y = q.Y
+ }
+ }
+}
+
+func stretchAcross(child, parent int, expand, shrink bool) int {
+ if (expand && child < parent) || (shrink && child > parent) {
+ return parent
+ }
+ return child
+}
+
+// FlowLayoutData is the node LayoutData type for a Flow's children.
+type FlowLayoutData struct {
+ // AlongWeight is the relative weight for distributing any space surplus or
+ // deficit along the Flow's axis. For example, if an AxisHorizontal Flow's
+ // Rect width was 100 pixels greater than the sum of its children's natural
+ // widths, and three children had non-zero FlowLayoutData.AlongWeight
+ // values 6, 3 and 1 (and their FlowLayoutData.ExpandAlong values were
+ // true) then those children's laid out widths would be larger than their
+ // natural widths by 60, 30 and 10 pixels.
+ //
+ // A negative AlongWeight is equivalent to zero.
+ AlongWeight int
+
+ // ExpandAlong is whether the child's laid out size should increase along
+ // the Flow's axis, based on AlongWeight, if there is a space surplus (the
+ // children's measured size total less than the parent's size). To allow
+ // size decreases as well as increases, set ShrinkAlong.
+ ExpandAlong bool
+
+ // ShrinkAlong is whether the child's laid out size should decrease along
+ // the Flow's axis, based on AlongWeight, if there is a space deficit (the
+ // children's measured size total more than the parent's size). To allow
+ // size increases as well as decreases, set ExpandAlong.
+ ShrinkAlong bool
+
+ // ExpandAcross is whether the child's laid out size should increase along
+ // the Flow's cross-axis if there is a space surplus (the child's measured
+ // size is less than the parent's size). To allow size decreases as well as
+ // increases, set ShrinkAcross.
+ //
+ // For example, if an AxisHorizontal Flow's Rect height was 80 pixels, any
+ // child whose FlowLayoutData.ExpandAcross was true would also be laid out
+ // with at least an 80 pixel height.
+ ExpandAcross bool
+
+ // ShrinkAcross is whether the child's laid out size should decrease along
+ // the Flow's cross-axis if there is a space deficit (the child's measured
+ // size is more than the parent's size). To allow size increases as well as
+ // decreases, set ExpandAcross.
+ //
+ // For example, if an AxisHorizontal Flow's Rect height was 80 pixels, any
+ // child whose FlowLayoutData.ShrinkAcross was true would also be laid out
+ // with at most an 80 pixel height.
+ ShrinkAcross bool
+}
diff --git a/shiny/widget/glwidget/glwidget.go b/shiny/widget/glwidget/glwidget.go
new file mode 100644
index 0000000..63b9a6a
--- /dev/null
+++ b/shiny/widget/glwidget/glwidget.go
@@ -0,0 +1,108 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !android
+
+// Package glwidget provides a widget containing a GL ES framebuffer.
+package glwidget
+
+import (
+ "fmt"
+ "image"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/driver/gldriver"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/mobile/gl"
+)
+
+// GL is a widget that maintains an OpenGL ES context.
+//
+// The Draw function is responsible for configuring the GL viewport
+// and for publishing the result to the widget by calling the Publish
+// method when the frame is complete. A typical draw function:
+//
+// func(w *glwidget.GL) {
+// w.Ctx.Viewport(0, 0, w.Rect.Dx(), w.Rect.Dy())
+// w.Ctx.ClearColor(0, 0, 0, 1)
+// w.Ctx.Clear(gl.COLOR_BUFFER_BIT)
+// // ... draw the frame
+// w.Publish()
+// }
+//
+// The GL context is separate from the one used by the gldriver to
+// render the window, and is only used by the glwidget package during
+// initialization and for the duration of the Publish call. This means
+// a glwidget user is free to use Ctx as a background GL context
+// concurrently with the primary UI drawing done by the gldriver.
+type GL struct {
+ node.LeafEmbed
+
+ Ctx gl.Context
+
+ draw func(*GL)
+ framebuffer gl.Framebuffer
+ tex gl.Texture
+ dst *image.RGBA
+ origin image.Point
+}
+
+// NewGL creates a GL widget with a Draw function called when painted.
+func NewGL(drawFunc func(*GL)) *GL {
+ // TODO: use the size of the monitor as a bound for texture size.
+ const maxWidth, maxHeight = 4096, 3072
+
+ glctx, err := gldriver.NewContext()
+ if err != nil {
+ panic(fmt.Sprintf("glwidget: %v", err)) // TODO: return error?
+ }
+ w := &GL{
+ Ctx: glctx,
+ draw: drawFunc,
+ }
+ w.tex = w.Ctx.CreateTexture()
+ w.Ctx.BindTexture(gl.TEXTURE_2D, w.tex)
+
+ w.Ctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, maxWidth, maxHeight, gl.RGBA, gl.UNSIGNED_BYTE, nil)
+ w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
+ w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
+ w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+ w.Ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
+
+ w.framebuffer = w.Ctx.CreateFramebuffer()
+ w.Ctx.BindFramebuffer(gl.FRAMEBUFFER, w.framebuffer)
+ w.Ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, w.tex, 0)
+
+ // TODO: delete the framebuffer, texture, and gl.Context.
+ // TODO: explicit or finalizer cleanup?
+
+ w.Wrapper = w
+
+ return w
+}
+
+func (w *GL) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ if w.Rect.Empty() {
+ return nil
+ }
+ w.dst = ctx.Dst
+ w.origin = origin
+ w.draw(w)
+ w.dst = nil
+ return nil
+}
+
+// Publish renders the default framebuffer of Ctx onto the area of the
+// window occupied by the widget.
+func (w *GL) Publish() {
+ if w.dst == nil {
+ panic("glwidget: no destination, Publish called outside of Draw")
+ }
+ // TODO: draw the widget texture directly into the window framebuffer.
+ m := image.NewRGBA(image.Rect(0, 0, w.Rect.Dx(), w.Rect.Dy()))
+ w.Ctx.PixelStorei(gl.PACK_ALIGNMENT, 1)
+ w.Ctx.ReadPixels(m.Pix, 0, 0, w.Rect.Dx(), w.Rect.Dy(), gl.RGBA, gl.UNSIGNED_BYTE)
+ draw.Draw(w.dst, w.Rect.Add(w.origin), m, image.Point{}, draw.Over)
+}
diff --git a/shiny/widget/image.go b/shiny/widget/image.go
new file mode 100644
index 0000000..b705edd
--- /dev/null
+++ b/shiny/widget/image.go
@@ -0,0 +1,62 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "image"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+// TODO: mask and maskPoint, not just src and srcRect.
+
+// TODO: be able to specify the draw operator: Src instead of Over.
+
+// TODO: if the measured size differs from the actual size, specify a
+// background color (or tile-able image like a checkerboard)? Specify a
+// draw.Scaler from the golang.org/x/image/draw package? Be able to center the
+// source image within the widget?
+
+// Image is a leaf widget that paints an image.Image.
+type Image struct {
+ node.LeafEmbed
+ Src image.Image
+ SrcRect image.Rectangle
+}
+
+// NewImage returns a new Image widget for the part of a source image defined
+// by src and srcRect.
+func NewImage(src image.Image, srcRect image.Rectangle) *Image {
+ w := &Image{
+ Src: src,
+ SrcRect: srcRect,
+ }
+ w.Wrapper = w
+ return w
+}
+
+func (w *Image) Measure(t *theme.Theme, widthHint, heightHint int) {
+ w.MeasuredSize = w.SrcRect.Size()
+}
+
+func (w *Image) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ if w.Src == nil {
+ return nil
+ }
+
+ // wRect is the widget's layout rectangle, in dst's coordinate space.
+ wRect := w.Rect.Add(origin)
+
+ // sRect is the source image rectangle, in dst's coordinate space, so that
+ // the upper-left corner of the source image rectangle aligns with the
+ // upper-left corner of wRect.
+ sRect := w.SrcRect.Add(wRect.Min.Sub(w.SrcRect.Min))
+
+ draw.Draw(ctx.Dst, wRect.Intersect(sRect), w.Src, w.SrcRect.Min, draw.Over)
+ return nil
+}
diff --git a/shiny/widget/label.go b/shiny/widget/label.go
new file mode 100644
index 0000000..120f6ac
--- /dev/null
+++ b/shiny/widget/label.go
@@ -0,0 +1,71 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "image"
+
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+)
+
+// Label is a leaf widget that holds a text label.
+type Label struct {
+ node.LeafEmbed
+ Text string
+ ThemeColor theme.Color
+}
+
+// NewLabel returns a new Label widget.
+func NewLabel(text string) *Label {
+ w := &Label{
+ Text: text,
+ }
+ w.Wrapper = w
+ return w
+}
+
+func (w *Label) Measure(t *theme.Theme, widthHint, heightHint int) {
+ face := t.AcquireFontFace(theme.FontFaceOptions{})
+ defer t.ReleaseFontFace(theme.FontFaceOptions{}, face)
+ m := face.Metrics()
+
+ // TODO: padding, to match a Text widget?
+
+ w.MeasuredSize.X = font.MeasureString(face, w.Text).Ceil()
+ w.MeasuredSize.Y = m.Ascent.Ceil() + m.Descent.Ceil()
+}
+
+func (w *Label) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ dst := ctx.Dst.SubImage(w.Rect.Add(origin)).(*image.RGBA)
+ if dst.Bounds().Empty() {
+ return nil
+ }
+
+ face := ctx.Theme.AcquireFontFace(theme.FontFaceOptions{})
+ defer ctx.Theme.ReleaseFontFace(theme.FontFaceOptions{}, face)
+ m := face.Metrics()
+ ascent := m.Ascent.Ceil()
+
+ tc := w.ThemeColor
+ if tc == nil {
+ tc = theme.Foreground
+ }
+
+ d := font.Drawer{
+ Dst: dst,
+ Src: tc.Uniform(ctx.Theme),
+ Face: face,
+ Dot: fixed.Point26_6{
+ X: fixed.I(origin.X + w.Rect.Min.X),
+ Y: fixed.I(origin.Y + w.Rect.Min.Y + ascent),
+ },
+ }
+ d.DrawString(w.Text)
+ return nil
+}
diff --git a/shiny/widget/node/node.go b/shiny/widget/node/node.go
new file mode 100644
index 0000000..89893f5
--- /dev/null
+++ b/shiny/widget/node/node.go
@@ -0,0 +1,511 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package node provides the structure for a tree of heterogenous widget nodes.
+//
+// Most programmers should not need to import this package, only the top-level
+// widget package. Only those that write custom widgets need to explicitly
+// refer to the Node, Embed and related types.
+//
+// The Node interface is usually implemented by struct types that embed one of
+// LeafEmbed, ShellEmbed or ContainerEmbed (all of which themselves embed an
+// Embed), providing default implementations of all of Node's methods.
+//
+// The split between an outer wrapper (Node) interface type and an inner
+// wrappee (Embed) struct type enables heterogenous nodes, such as a buttons
+// and labels, in a widget tree where every node contains common fields such as
+// position, size and tree structure links (parent, siblings and children).
+//
+// In a traditional object-oriented type system, this might be represented by
+// the Button and Label types both subclassing the Node type. Go does not have
+// inheritance, so the outer / inner split is composed explicitly. For example,
+// the concrete Button type is a struct type that embeds an XxxEmbed (such as
+// LeafEmbed), and the NewButton function sets the inner Embed's Wrapper field
+// to point back to the outer value.
+//
+// There are three layers here (Button embeds LeafEmbed embeds Embed) instead
+// of two. The intermediate layer exists because there needs to be a place to
+// provide default implementations of methods like Measure, but that place
+// shouldn't be the inner-most type (Embed), otherwise it'd be too easy to
+// write subtly incorrect code like:
+//
+// for c := w.FirstChild; c != nil; c = c.NextSibling {
+// c.Measure(etc) // This should instead be c.Wrapper.Measure(etc).
+// }
+//
+// In any case, most programmers that want to construct a widget tree should
+// not need to know this detail. It usually suffices to call functions such as
+// widget.NewButton or widget.NewLabel, and then parent.Insert(button, nil).
+//
+// See the example/gallery program for some example code for a custom widget.
+package node // import "golang.org/x/exp/shiny/widget/node"
+
+import (
+ "image"
+
+ "golang.org/x/exp/shiny/gesture"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+)
+
+// EventHandled is whether or not an input event (a key, mouse, touch or
+// gesture event) was handled by a widget. If it was not handled, the event is
+// propagated along the widget tree.
+type EventHandled bool
+
+const (
+ NotHandled = EventHandled(false)
+ Handled = EventHandled(true)
+)
+
+// NoHint means that there is no width or height hint in a Measure call.
+const NoHint = -1
+
+// Node is a node in the widget tree.
+type Node interface {
+ // Wrappee returns the inner (embedded) type that is wrapped by this type.
+ Wrappee() *Embed
+
+ // Insert adds a node c as a child of this node. If nextSibling is nil, c
+ // will be inserted at the end of this node's children. Otherwise, c will
+ // be inserted such that its next sibling is nextSibling.
+ //
+ // It will panic if c already has a parent or siblings.
+ Insert(c, nextSibling Node)
+
+ // Remove removes a node c that is a child of this node. Afterwards, c will
+ // have no parent and no siblings.
+ //
+ // It will panic if c's parent is not this node.
+ Remove(c Node)
+
+ // Measure sets this node's Embed.MeasuredSize to its natural size, taking
+ // its children into account.
+ //
+ // Some nodes' natural height might depend on their imposed width, such as
+ // a text widget word-wrapping its contents. The caller may provide hints
+ // that the parent can override the child's natural size in the width,
+ // height or both directions. A negative value means that there is no hint.
+ // For example, a container might lay out its children to all have the same
+ // width, and could pass that width as the widthHint argument.
+ Measure(t *theme.Theme, widthHint, heightHint int)
+
+ // Layout lays out this node (and its children), setting the Embed.Rect
+ // fields of each child. This node's Embed.Rect field should have
+ // previously been set during the parent node's layout.
+ Layout(t *theme.Theme)
+
+ // Paint paints this node (and its children). Painting is split into two
+ // passes: a base pass and an effects pass. The effects pass is often a
+ // no-op, and the bulk of the work is typically done in the base pass.
+ //
+ // The base pass paints onto an *image.RGBA pixel buffer and ancestor nodes
+ // may choose to re-use the result. For example, re-painting a text widget
+ // after scrolling may copy cached buffers at different offsets, instead of
+ // painting the text's glyphs onto a fresh buffer. Similarly, animating the
+ // scale and opacity of an overlay can re-use the buffer from a previous
+ // base pass.
+ //
+ // The effects pass paints that part of the widget that can not or should
+ // not be cached. For example, the border of a text widget shouldn't move
+ // on the screen when that text widget is scrolled. The effects pass does
+ // not have a destination RGBA pixel buffer, and is limited to what a
+ // screen.Drawer provides: affine-transformed textures and uniform fills.
+ //
+ // TODO: app-specific OpenGL, if available, should be part of the effects
+ // pass. Is that exposed via the screen.Drawer or by another mechanism?
+ //
+ // The Paint method may create base pass RGBA pixel buffers, by calling
+ // ctx.Screen.NewBuffer. Many implementations won't, and instead assume
+ // that PaintBase is recursively triggered by an ancestor node such as a
+ // widget.Sheet. If it does create those RGBA pixel buffers, it is also
+ // responsible for calling PaintBase on this node (and its children). In
+ // any case, the Paint method should then paint any effects. Many widgets
+ // will neither create their own buffers nor have any effects, so their
+ // Paint methods will simply be the default implemention: do nothing except
+ // call Paint on its children. As mentioned above, the bulk of the work is
+ // typically done in PaintBase.
+ //
+ // origin is the parent widget's origin with respect to the ctx.Src2Dst
+ // transformation matrix; this node's Embed.Rect.Add(origin) will be its
+ // position and size in pre-transformed coordinate space.
+ Paint(ctx *PaintContext, origin image.Point) error
+
+ // PaintBase paints the base pass of this node (and its children) onto an
+ // RGBA pixel buffer.
+ //
+ // origin is the parent widget's origin with respect to the ctx.Dst image's
+ // origin; this node's Embed.Rect.Add(origin) will be its position and size
+ // in ctx.Dst's coordinate space.
+ PaintBase(ctx *PaintBaseContext, origin image.Point) error
+
+ // Mark adds the given marks to this node. It calls OnChildMarked on its
+ // parent if new marks were added.
+ Mark(m Marks)
+
+ // OnChildMarked handles a child being given new marks. By default, marks
+ // are propagated up the node tree towards the root. For example, a child
+ // being marked for needing paint will cause the parent being marked for
+ // needing paint.
+ OnChildMarked(child Node, newMarks Marks)
+
+ // OnLifecycleEvent propagates a lifecycle event to a node (and its
+ // children).
+ OnLifecycleEvent(e lifecycle.Event)
+
+ // OnInputEvent handles a key, mouse, touch or gesture event.
+ //
+ // origin is the parent widget's origin with respect to the event origin;
+ // this node's Embed.Rect.Add(origin) will be its position and size in
+ // event coordinate space.
+ OnInputEvent(e interface{}, origin image.Point) EventHandled
+
+ // TODO: other OnXxxEvent methods?
+
+}
+
+// PaintContext is the context for the Node.Paint method.
+type PaintContext struct {
+ Theme *theme.Theme
+ Screen screen.Screen
+ Drawer screen.Drawer
+ Src2Dst f64.Aff3
+
+ // TODO: add a clip rectangle?
+
+ // TODO: add the DrawContext from the lifecycle event?
+}
+
+// PaintBaseContext is the context for the Node.PaintBase method.
+type PaintBaseContext struct {
+ Theme *theme.Theme
+ Dst *image.RGBA
+
+ // TODO: add a clip rectangle? Or rely on the RGBA.SubImage method to pass
+ // smaller Dst images?
+}
+
+// LeafEmbed is designed to be embedded in struct types for nodes with no
+// children.
+type LeafEmbed struct{ Embed }
+
+func (m *LeafEmbed) Insert(c, nextSibling Node) {
+ panic("node: Insert called for a leaf parent")
+}
+
+func (m *LeafEmbed) Remove(c Node) { m.remove(c) }
+
+func (m *LeafEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
+ m.MeasuredSize = image.Point{}
+}
+
+func (m *LeafEmbed) Layout(t *theme.Theme) {}
+
+func (m *LeafEmbed) Paint(ctx *PaintContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaint()
+ return nil
+}
+
+func (m *LeafEmbed) PaintBase(ctx *PaintBaseContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaintBase()
+ return nil
+}
+
+func (m *LeafEmbed) OnChildMarked(child Node, newMarks Marks) {}
+
+func (m *LeafEmbed) OnLifecycleEvent(e lifecycle.Event) {}
+
+func (m *LeafEmbed) OnInputEvent(e interface{}, origin image.Point) EventHandled { return NotHandled }
+
+// ShellEmbed is designed to be embedded in struct types for nodes with at most
+// one child.
+type ShellEmbed struct{ Embed }
+
+func (m *ShellEmbed) Insert(c, nextSibling Node) {
+ if m.FirstChild != nil {
+ panic("node: Insert called for a shell parent that already has a child")
+ }
+ m.insert(c, nextSibling)
+}
+
+func (m *ShellEmbed) Remove(c Node) { m.remove(c) }
+
+func (m *ShellEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
+ if c := m.FirstChild; c != nil {
+ c.Wrapper.Measure(t, widthHint, heightHint)
+ m.MeasuredSize = c.MeasuredSize
+ } else {
+ m.MeasuredSize = image.Point{}
+ }
+}
+
+func (m *ShellEmbed) Layout(t *theme.Theme) {
+ if c := m.FirstChild; c != nil {
+ c.Rect = m.Rect.Sub(m.Rect.Min)
+ c.Wrapper.Layout(t)
+ }
+}
+
+func (m *ShellEmbed) Paint(ctx *PaintContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaint()
+ if c := m.FirstChild; c != nil {
+ return c.Wrapper.Paint(ctx, origin.Add(m.Rect.Min))
+ }
+ return nil
+}
+
+func (m *ShellEmbed) PaintBase(ctx *PaintBaseContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaintBase()
+ if c := m.FirstChild; c != nil {
+ return c.Wrapper.PaintBase(ctx, origin.Add(m.Rect.Min))
+ }
+ return nil
+}
+
+func (m *ShellEmbed) OnChildMarked(child Node, newMarks Marks) {
+ m.Mark(newMarks)
+}
+
+func (m *ShellEmbed) OnLifecycleEvent(e lifecycle.Event) {
+ if c := m.FirstChild; c != nil {
+ c.Wrapper.OnLifecycleEvent(e)
+ }
+}
+
+func (m *ShellEmbed) OnInputEvent(e interface{}, origin image.Point) EventHandled {
+ if c := m.FirstChild; c != nil {
+ return c.Wrapper.OnInputEvent(e, origin.Add(m.Rect.Min))
+ }
+ return NotHandled
+}
+
+// ContainerEmbed is designed to be embedded in struct types for nodes with any
+// number of children.
+type ContainerEmbed struct{ Embed }
+
+func (m *ContainerEmbed) Insert(c, nextSibling Node) { m.insert(c, nextSibling) }
+
+func (m *ContainerEmbed) Remove(c Node) { m.remove(c) }
+
+func (m *ContainerEmbed) Measure(t *theme.Theme, widthHint, heightHint int) {
+ mSize := image.Point{}
+ for c := m.FirstChild; c != nil; c = c.NextSibling {
+ c.Wrapper.Measure(t, NoHint, NoHint)
+ if mSize.X < c.MeasuredSize.X {
+ mSize.X = c.MeasuredSize.X
+ }
+ if mSize.Y < c.MeasuredSize.Y {
+ mSize.Y = c.MeasuredSize.Y
+ }
+ }
+ m.MeasuredSize = mSize
+}
+
+func (m *ContainerEmbed) Layout(t *theme.Theme) {
+ for c := m.FirstChild; c != nil; c = c.NextSibling {
+ c.Rect = image.Rectangle{Max: c.MeasuredSize}
+ c.Wrapper.Layout(t)
+ }
+}
+
+func (m *ContainerEmbed) Paint(ctx *PaintContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaint()
+ origin = origin.Add(m.Rect.Min)
+ for c := m.FirstChild; c != nil; c = c.NextSibling {
+ if err := c.Wrapper.Paint(ctx, origin); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (m *ContainerEmbed) PaintBase(ctx *PaintBaseContext, origin image.Point) error {
+ m.Marks.UnmarkNeedsPaintBase()
+ origin = origin.Add(m.Rect.Min)
+ for c := m.FirstChild; c != nil; c = c.NextSibling {
+ if err := c.Wrapper.PaintBase(ctx, origin); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (m *ContainerEmbed) OnChildMarked(child Node, newMarks Marks) {
+ m.Mark(newMarks)
+}
+
+func (m *ContainerEmbed) OnLifecycleEvent(e lifecycle.Event) {
+ for c := m.FirstChild; c != nil; c = c.NextSibling {
+ c.Wrapper.OnLifecycleEvent(e)
+ }
+}
+
+func (m *ContainerEmbed) OnInputEvent(e interface{}, origin image.Point) EventHandled {
+ origin = origin.Add(m.Rect.Min)
+ var p image.Point
+ switch e := e.(type) {
+ case gesture.Event:
+ p = image.Point{
+ X: int(e.CurrentPos.X) - origin.X,
+ Y: int(e.CurrentPos.Y) - origin.Y,
+ }
+ case mouse.Event:
+ p = image.Point{
+ X: int(e.X) - origin.X,
+ Y: int(e.Y) - origin.Y,
+ }
+ }
+ // Iterate backwards. Later children have priority over earlier children,
+ // as later ones are usually drawn over earlier ones.
+ for c := m.LastChild; c != nil; c = c.PrevSibling {
+ if p.In(c.Rect) && c.Wrapper.OnInputEvent(e, origin) == Handled {
+ return Handled
+ }
+ }
+ return NotHandled
+}
+
+// Embed is the common data structure for each node in a widget tree.
+type Embed struct {
+ // Wrapper is the outer type that wraps (embeds) this type. It should not
+ // be nil.
+ Wrapper Node
+
+ // Parent, FirstChild, LastChild, PrevSibling and NextSibling describe the
+ // widget tree structure.
+ //
+ // These fields are exported to enable walking the node tree, but they
+ // should not be modified directly. Instead, call the Insert and Remove
+ // methods, which keeps the tree structure consistent.
+ Parent, FirstChild, LastChild, PrevSibling, NextSibling *Embed
+
+ // LayoutData is layout-specific data for this node. Its type is determined
+ // by its parent node's type. For example, each child of a Flow may hold a
+ // FlowLayoutData in this field.
+ LayoutData interface{}
+
+ // TODO: add commentary about the Measure / Layout / Paint model, and about
+ // the lifetime of the MeasuredSize and Rect fields, and when user code can
+ // access and/or modify them. At some point a new cycle begins, a call to
+ // measure is necessary, and using MeasuredSize is incorrect (unless you're
+ // trying to recall something about the past).
+
+ // MeasuredSize is the widget's natural size, in pixels, as calculated by
+ // the most recent Measure call.
+ MeasuredSize image.Point
+
+ // Rect is the widget's position and actual (as opposed to natural) size,
+ // in pixels, as calculated by the most recent Layout call on its parent
+ // node. A parent may lay out a child at a size different to its natural
+ // size in order to satisfy a layout constraint, such as a row of buttons
+ // expanding to fill a panel's width.
+ //
+ // The position (Rectangle.Min) is relative to its parent node. This is not
+ // necessarily the same as relative to the screen's, window's or image
+ // buffer's origin.
+ Rect image.Rectangle
+
+ // Marks are a bitfield of node state, such as whether it needs measure,
+ // layout or paint.
+ Marks Marks
+}
+
+func (m *Embed) Wrappee() *Embed { return m }
+
+// TODO: should insert and remove call Mark(MarkNeedsMeasureLayout | MarkNeedsPaint)?
+
+func (m *Embed) insert(c, nextSibling Node) {
+ n := c.Wrappee()
+ if n.Parent != nil || n.PrevSibling != nil || n.NextSibling != nil {
+ panic("node: Insert called for an attached child")
+ }
+ n.Parent = m
+
+ if nextSibling == nil {
+ last := m.LastChild
+ if last != nil {
+ last.NextSibling = n
+ } else {
+ m.FirstChild = n
+ }
+ m.LastChild = n
+ n.PrevSibling = last
+ return
+ }
+
+ o := nextSibling.Wrappee()
+ if o.Parent != m {
+ panic("node: Insert called for a non-sibling nextSibling node")
+ }
+ if o.PrevSibling == nil {
+ o.PrevSibling = n
+ n.NextSibling = o
+ m.FirstChild = n
+ return
+ }
+
+ o.PrevSibling.NextSibling = n
+ n.PrevSibling = o.PrevSibling
+ n.NextSibling = o
+ o.PrevSibling = n
+}
+
+func (m *Embed) remove(c Node) {
+ n := c.Wrappee()
+ if n.Parent != m {
+ panic("node: Remove called for a non-child node")
+ }
+ if m.FirstChild == n {
+ m.FirstChild = n.NextSibling
+ }
+ if n.NextSibling != nil {
+ n.NextSibling.PrevSibling = n.PrevSibling
+ }
+ if m.LastChild == n {
+ m.LastChild = n.PrevSibling
+ }
+ if n.PrevSibling != nil {
+ n.PrevSibling.NextSibling = n.NextSibling
+ }
+ n.Parent = nil
+ n.PrevSibling = nil
+ n.NextSibling = nil
+}
+
+func (m *Embed) Mark(marks Marks) {
+ oldMarks := m.Marks
+ m.Marks |= marks
+ changedMarks := m.Marks ^ oldMarks
+ if changedMarks != 0 && m.Parent != nil {
+ m.Parent.Wrapper.OnChildMarked(m.Wrapper, changedMarks)
+ }
+}
+
+// Marks are a bitfield of node state, such as whether it needs measure, layout
+// or paint.
+type Marks uint32
+
+const (
+ // MarkNeedsMeasureLayout marks this node as needing Measure and Layout
+ // calls.
+ MarkNeedsMeasureLayout = Marks(1 << 0)
+ // TODO: use this.
+
+ // MarkNeedsPaint marks this node as needing a Paint call.
+ MarkNeedsPaint = Marks(1 << 1)
+
+ // MarkNeedsPaintBase marks this node as needing a PaintBase call.
+ MarkNeedsPaintBase = Marks(1 << 2)
+)
+
+func (m Marks) NeedsMeasureLayout() bool { return m&MarkNeedsMeasureLayout != 0 }
+func (m Marks) NeedsPaint() bool { return m&MarkNeedsPaint != 0 }
+func (m Marks) NeedsPaintBase() bool { return m&MarkNeedsPaintBase != 0 }
+
+func (m *Marks) UnmarkNeedsMeasureLayout() { *m &^= MarkNeedsMeasureLayout }
+func (m *Marks) UnmarkNeedsPaint() { *m &^= MarkNeedsPaint }
+func (m *Marks) UnmarkNeedsPaintBase() { *m &^= MarkNeedsPaintBase }
diff --git a/shiny/widget/padder.go b/shiny/widget/padder.go
new file mode 100644
index 0000000..dcc7738
--- /dev/null
+++ b/shiny/widget/padder.go
@@ -0,0 +1,77 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+// Padder is a shell widget that adds a margin to the inner widget's measured
+// size. That margin may be added horizontally (left and right), vertically
+// (top and bottom) or both, determined by the Padder's axis.
+//
+// That marginal space is not considered part of the inner widget's geometry.
+// For example, to make that space 'clickable', construct the Padder inside of
+// an event handling widget instead of vice versa.
+type Padder struct {
+ node.ShellEmbed
+ Axis Axis
+ Margin unit.Value
+}
+
+// NewPadder returns a new Padder widget.
+func NewPadder(a Axis, margin unit.Value, inner node.Node) *Padder {
+ w := &Padder{
+ Axis: a,
+ Margin: margin,
+ }
+ w.Wrapper = w
+ if inner != nil {
+ w.Insert(inner, nil)
+ }
+ return w
+}
+
+func (w *Padder) Measure(t *theme.Theme, widthHint, heightHint int) {
+ margin2 := t.Pixels(w.Margin).Round() * 2
+ if w.Axis.Horizontal() && widthHint >= 0 {
+ widthHint -= margin2
+ if widthHint < 0 {
+ widthHint = 0
+ }
+ }
+ if w.Axis.Vertical() && heightHint >= 0 {
+ heightHint -= margin2
+ if heightHint < 0 {
+ heightHint = 0
+ }
+ }
+ w.ShellEmbed.Measure(t, widthHint, heightHint)
+ if w.Axis.Horizontal() {
+ w.MeasuredSize.X += margin2
+ }
+ if w.Axis.Vertical() {
+ w.MeasuredSize.Y += margin2
+ }
+}
+
+func (w *Padder) Layout(t *theme.Theme) {
+ if c := w.FirstChild; c != nil {
+ r := w.Rect.Sub(w.Rect.Min)
+ inset := r.Inset(t.Pixels(w.Margin).Round())
+ if w.Axis.Horizontal() {
+ r.Min.X = inset.Min.X
+ r.Max.X = inset.Max.X
+ }
+ if w.Axis.Vertical() {
+ r.Min.Y = inset.Min.Y
+ r.Max.Y = inset.Max.Y
+ }
+ c.Rect = r
+ c.Wrapper.Layout(t)
+ }
+}
diff --git a/shiny/widget/sheet.go b/shiny/widget/sheet.go
new file mode 100644
index 0000000..f55696b
--- /dev/null
+++ b/shiny/widget/sheet.go
@@ -0,0 +1,127 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "image"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/lifecycle"
+)
+
+// TODO: scrolling.
+
+// Sheet is a shell widget that provides *image.RGBA pixel buffers (analogous
+// to blank sheets of paper) for its descendent widgets to paint on, via their
+// PaintBase methods. Such buffers may be cached and their contents re-used for
+// multiple paints, which can make scrolling and animation smoother and more
+// efficient.
+//
+// A simple app may have only one Sheet, near the root of its widget tree. A
+// more complicated app may have multiple Sheets. For example, consider a text
+// editor consisting of a small header bar and a large text widget. Those two
+// nodes may be backed by two separate Sheets, since scrolling the latter
+// should not scroll the former.
+type Sheet struct {
+ node.ShellEmbed
+ buf screen.Buffer
+ tex screen.Texture
+}
+
+// NewSheet returns a new Sheet widget.
+func NewSheet(inner node.Node) *Sheet {
+ w := &Sheet{}
+ w.Wrapper = w
+ if inner != nil {
+ w.Insert(inner, nil)
+ }
+ return w
+}
+
+func (w *Sheet) release() {
+ if w.buf != nil {
+ w.buf.Release()
+ w.buf = nil
+ }
+ if w.tex != nil {
+ w.tex.Release()
+ w.tex = nil
+ }
+}
+
+func (w *Sheet) Paint(ctx *node.PaintContext, origin image.Point) (retErr error) {
+ w.Marks.UnmarkNeedsPaint()
+ c := w.FirstChild
+ if c == nil {
+ w.release()
+ return nil
+ }
+
+ fresh, size := false, w.Rect.Size()
+ if w.buf != nil && w.buf.Size() != size {
+ w.release()
+ }
+ if w.buf == nil {
+ w.buf, retErr = ctx.Screen.NewBuffer(size)
+ if retErr != nil {
+ w.release()
+ return retErr
+ }
+ w.tex, retErr = ctx.Screen.NewTexture(size)
+ if retErr != nil {
+ w.release()
+ return retErr
+ }
+ fresh = true
+ }
+ if fresh || c.Marks.NeedsPaintBase() {
+ c.Wrapper.PaintBase(&node.PaintBaseContext{
+ Theme: ctx.Theme,
+ Dst: w.buf.RGBA(),
+ }, image.Point{})
+ }
+
+ w.tex.Upload(image.Point{}, w.buf, w.buf.Bounds())
+
+ src2dst := ctx.Src2Dst
+ translate(&src2dst,
+ float64(origin.X+w.Rect.Min.X),
+ float64(origin.Y+w.Rect.Min.Y),
+ )
+ // TODO: should draw.Over be configurable?
+ ctx.Drawer.Draw(src2dst, w.tex, w.tex.Bounds(), draw.Over, nil)
+
+ return c.Wrapper.Paint(ctx, origin.Add(w.Rect.Min))
+}
+
+func translate(a *f64.Aff3, tx, ty float64) {
+ a[2] += a[0]*tx + a[1]*ty
+ a[5] += a[3]*tx + a[4]*ty
+}
+
+func (w *Sheet) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ // Do not recursively call PaintBase on our children. We create our own
+ // buffers, and Sheet.Paint will call PaintBase with our PaintBaseContext
+ // instead of our ancestor's.
+ return nil
+}
+
+func (w *Sheet) OnChildMarked(child node.Node, newMarks node.Marks) {
+ if newMarks&node.MarkNeedsPaintBase != 0 {
+ newMarks &^= node.MarkNeedsPaintBase
+ newMarks |= node.MarkNeedsPaint
+ }
+ w.Mark(newMarks)
+}
+
+func (w *Sheet) OnLifecycleEvent(e lifecycle.Event) {
+ if e.Crosses(lifecycle.StageVisible) == lifecycle.CrossOff {
+ w.release()
+ }
+}
diff --git a/shiny/widget/sizer.go b/shiny/widget/sizer.go
new file mode 100644
index 0000000..69e430a
--- /dev/null
+++ b/shiny/widget/sizer.go
@@ -0,0 +1,41 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+// Sizer is a shell widget that overrides its child's measured size.
+type Sizer struct {
+ node.ShellEmbed
+ NaturalWidth unit.Value
+ NaturalHeight unit.Value
+}
+
+// NewSizer returns a new Sizer widget of the given natural size. Its parent
+// widget may lay it out at a different size than its natural size, such as
+// expanding to fill a panel's width.
+func NewSizer(naturalWidth, naturalHeight unit.Value, inner node.Node) *Sizer {
+ w := &Sizer{
+ NaturalWidth: naturalWidth,
+ NaturalHeight: naturalHeight,
+ }
+ w.Wrapper = w
+ if inner != nil {
+ w.Insert(inner, nil)
+ }
+ return w
+}
+
+func (w *Sizer) Measure(t *theme.Theme, widthHint, heightHint int) {
+ w.MeasuredSize.X = t.Pixels(w.NaturalWidth).Round()
+ w.MeasuredSize.Y = t.Pixels(w.NaturalHeight).Round()
+ if c := w.FirstChild; c != nil {
+ c.Wrapper.Measure(t, w.MeasuredSize.X, w.MeasuredSize.Y)
+ }
+}
diff --git a/shiny/widget/space.go b/shiny/widget/space.go
new file mode 100644
index 0000000..20d5167
--- /dev/null
+++ b/shiny/widget/space.go
@@ -0,0 +1,25 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "golang.org/x/exp/shiny/widget/node"
+)
+
+// Space is leaf widget that occupies empty space. For example, aligning two
+// widgets to the left and right edges of a container can be achieved by
+// placing a third Space widget between them, whose LayoutData makes that Space
+// expand to occupy any excess space. Similarly, a widget can be centered in
+// its container by adding an expanding Space before and after.
+type Space struct {
+ node.LeafEmbed
+}
+
+// NewSpace returns a new Space widget.
+func NewSpace() *Space {
+ w := &Space{}
+ w.Wrapper = w
+ return w
+}
diff --git a/shiny/widget/text.go b/shiny/widget/text.go
new file mode 100644
index 0000000..6183ae3
--- /dev/null
+++ b/shiny/widget/text.go
@@ -0,0 +1,152 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "image"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/text"
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+)
+
+// Text is a leaf widget that holds a text label.
+type Text struct {
+ node.LeafEmbed
+ frame text.Frame
+ faceSet bool
+
+ // TODO: scrolling, although should that be the responsibility of this
+ // widget, the parent widget or something else?
+}
+
+// NewText returns a new Text widget.
+func NewText(text string) *Text {
+ w := &Text{}
+ w.Wrapper = w
+ if text != "" {
+ c := w.frame.NewCaret()
+ c.WriteString(text)
+ c.Close()
+ }
+ return w
+}
+
+func (w *Text) setFace(t *theme.Theme) {
+ // TODO: can a theme change at runtime, or can it be set only once, at
+ // start-up?
+ if !w.faceSet {
+ w.faceSet = true
+ // TODO: when is face released? Should we just unconditionally call
+ // SetFace for every Measure, Layout and Paint? How do we avoid
+ // excessive re-calculation of soft returns when re-using the same
+ // logical face (as in "Times New Roman 12pt") even if using different
+ // physical font.Face values (as each Face may have its own caches)?
+ face := t.AcquireFontFace(theme.FontFaceOptions{})
+ w.frame.SetFace(face)
+ }
+}
+
+// TODO: should padding (and/or margin and border) be a universal concept and
+// part of the node.Embed type instead of having each widget implement its own?
+
+func (w *Text) padding(t *theme.Theme) int {
+ return t.Pixels(unit.Ems(0.5)).Ceil()
+}
+
+func (w *Text) Measure(t *theme.Theme, widthHint, heightHint int) {
+ w.setFace(t)
+ padding := w.padding(t)
+
+ if widthHint < 0 {
+ w.frame.SetMaxWidth(0)
+ w.MeasuredSize = image.Point{
+ 0, // TODO: this isn't right.
+ w.frame.Height() + 2*padding,
+ }
+ return
+ }
+
+ maxWidth := fixed.I(widthHint - 2*padding)
+ if maxWidth <= 1 {
+ maxWidth = 1
+ }
+ w.frame.SetMaxWidth(maxWidth)
+
+ w.MeasuredSize = image.Point{
+ widthHint,
+ w.frame.Height() + 2*padding,
+ }
+}
+
+func (w *Text) Layout(t *theme.Theme) {
+ w.setFace(t)
+ padding := w.padding(t)
+ maxWidth := fixed.I(w.Rect.Dx() - 2*padding)
+ if maxWidth <= 1 {
+ maxWidth = 1
+ }
+ w.frame.SetMaxWidth(maxWidth)
+}
+
+func (w *Text) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ dst := ctx.Dst.SubImage(w.Rect.Add(origin)).(*image.RGBA)
+ if dst.Bounds().Empty() {
+ return nil
+ }
+
+ face := ctx.Theme.AcquireFontFace(theme.FontFaceOptions{})
+ defer ctx.Theme.ReleaseFontFace(theme.FontFaceOptions{}, face)
+ m := face.Metrics()
+ ascent := m.Ascent.Ceil()
+ descent := m.Descent.Ceil()
+ height := m.Height.Ceil()
+
+ padding := w.padding(ctx.Theme)
+
+ draw.Draw(dst, dst.Bounds(), ctx.Theme.GetPalette().Background(), image.Point{}, draw.Src)
+
+ minDotY := fixed.I(dst.Bounds().Min.Y - descent)
+ maxDotY := fixed.I(dst.Bounds().Max.Y + ascent)
+
+ x0 := fixed.I(origin.X + w.Rect.Min.X + padding)
+ d := font.Drawer{
+ Dst: dst,
+ Src: ctx.Theme.GetPalette().Foreground(),
+ Face: face,
+ Dot: fixed.Point26_6{
+ X: x0,
+ Y: fixed.I(origin.Y + w.Rect.Min.Y + padding + ascent),
+ },
+ }
+ f := &w.frame
+ for p := f.FirstParagraph(); p != nil; p = p.Next(f) {
+ for l := p.FirstLine(f); l != nil; l = l.Next(f) {
+ if d.Dot.Y > minDotY {
+ if d.Dot.Y >= maxDotY {
+ return nil
+ }
+ for b := l.FirstBox(f); b != nil; b = b.Next(f) {
+ d.DrawBytes(b.TrimmedText(f))
+ // TODO: adjust d.Dot.X for any ligatures?
+ }
+ d.Dot.X = x0
+ }
+ d.Dot.Y += fixed.I(height)
+ }
+ }
+ return nil
+}
+
+func (w *Text) Paint(ctx *node.PaintContext, origin image.Point) error {
+ // TODO: draw an optional border, whose color depends on whether w has the
+ // keyboard focus.
+ return w.LeafEmbed.Paint(ctx, origin)
+}
diff --git a/shiny/widget/theme/theme.go b/shiny/widget/theme/theme.go
new file mode 100644
index 0000000..073bace
--- /dev/null
+++ b/shiny/widget/theme/theme.go
@@ -0,0 +1,246 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package theme provides widget themes.
+package theme
+
+import (
+ "image"
+ "image/color"
+
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/image/font"
+ "golang.org/x/image/font/inconsolata"
+ "golang.org/x/image/math/fixed"
+)
+
+// FontFaceOptions allows asking for font face variants, such as style (e.g.
+// italic) or weight (e.g. bold).
+//
+// TODO: include font.Hinting and font.Stretch typed fields?
+//
+// TODO: include font size? If so, directly as "12pt" or indirectly as an enum
+// (Heading1, Heading2, Body, etc)?
+type FontFaceOptions struct {
+ Style font.Style
+ Weight font.Weight
+}
+
+// FontFaceCatalog provides a theme's font faces.
+//
+// AcquireFontFace returns a font.Face. ReleaseFontFace should be called, with
+// the same options, once a widget's measure, layout or paint is done with the
+// font.Face returned.
+//
+// A FontFaceCatalog is safe for use by multiple goroutines simultaneously, but
+// in general, a font.Face is not safe for concurrent use, as its methods may
+// re-use implementation-specific caches and mask image buffers.
+type FontFaceCatalog interface {
+ AcquireFontFace(FontFaceOptions) font.Face
+ ReleaseFontFace(FontFaceOptions, font.Face)
+
+ // TODO: add a "Metrics(FontFaceOptions) font.Metrics" method?
+}
+
+// Color is a theme-dependent color, such as "the foreground color". Combining
+// a Color with a Theme results in a color.Color in the sense of the standard
+// library's image/color package. It can also result in an *image.Uniform,
+// suitable for passing as the src argument to image/draw functions.
+type Color interface {
+ Color(*Theme) color.Color
+ Uniform(*Theme) *image.Uniform
+}
+
+// StaticColor adapts a color.Color to a theme Color.
+func StaticColor(c color.Color) Color { return staticColor{image.Uniform{c}} }
+
+type staticColor struct {
+ u image.Uniform
+}
+
+func (s staticColor) Color(*Theme) color.Color { return s.u.C }
+func (s staticColor) Uniform(*Theme) *image.Uniform { return &s.u }
+
+// Palette provides a theme's color palette. The array is indexed by
+// PaletteIndex constants such as Accent and Foreground.
+//
+// The colors are expressed as image.Uniform values so that they can be easily
+// passed as the src argument to image/draw functions.
+type Palette [PaletteLen]image.Uniform
+
+func (p *Palette) Light() *image.Uniform { return &p[Light] }
+func (p *Palette) Neutral() *image.Uniform { return &p[Neutral] }
+func (p *Palette) Dark() *image.Uniform { return &p[Dark] }
+func (p *Palette) Accent() *image.Uniform { return &p[Accent] }
+func (p *Palette) Foreground() *image.Uniform { return &p[Foreground] }
+func (p *Palette) Background() *image.Uniform { return &p[Background] }
+
+// PaletteIndex is both an integer index into a Palette array and a Color.
+type PaletteIndex int
+
+func (i PaletteIndex) Color(t *Theme) color.Color { return t.GetPalette()[i].C }
+func (i PaletteIndex) Uniform(t *Theme) *image.Uniform { return &t.GetPalette()[i] }
+
+const (
+ // Light, Neutral and Dark are three color tones used to fill in widgets
+ // such as buttons, menu bars and panels.
+ Light = PaletteIndex(0)
+ Neutral = PaletteIndex(1)
+ Dark = PaletteIndex(2)
+
+ // Accent is the color used to accentuate selections or suggestions.
+ Accent = PaletteIndex(3)
+
+ // Foreground is the color used for text, dividers and icons.
+ Foreground = PaletteIndex(4)
+
+ // Background is the color used behind large blocks of text. Short,
+ // non-editable label text will typically be on the Neutral color.
+ Background = PaletteIndex(5)
+
+ PaletteLen = 6
+)
+
+// DefaultDPI is the fallback value of a theme's DPI, if the underlying context
+// does not provide a DPI value.
+const DefaultDPI = 72.0
+
+var (
+ // DefaultFontFaceCatalog is a catalog for a basic font face.
+ DefaultFontFaceCatalog FontFaceCatalog = defaultFontFaceCatalog{}
+
+ // DefaultPalette is the default theme's palette.
+ DefaultPalette = Palette{
+ Light: image.Uniform{C: color.RGBA{0xf5, 0xf5, 0xf5, 0xff}}, // Material Design "Grey 100".
+ Neutral: image.Uniform{C: color.RGBA{0xee, 0xee, 0xee, 0xff}}, // Material Design "Grey 200".
+ Dark: image.Uniform{C: color.RGBA{0xe0, 0xe0, 0xe0, 0xff}}, // Material Design "Grey 300".
+ Accent: image.Uniform{C: color.RGBA{0x21, 0x96, 0xf3, 0xff}}, // Material Design "Blue 500".
+ Foreground: image.Uniform{C: color.RGBA{0x00, 0x00, 0x00, 0xff}}, // Material Design "Black".
+ Background: image.Uniform{C: color.RGBA{0xff, 0xff, 0xff, 0xff}}, // Material Design "White".
+ }
+
+ // Default uses the default DPI, FontFaceCatalog and Palette.
+ //
+ // The nil-valued pointer is a valid receiver for a Theme's methods.
+ Default *Theme
+)
+
+// Note that a *basicfont.Face such as inconsolata.Regular8x16 is stateless and
+// safe to use concurrently, so defaultFontFaceCatalog.ReleaseFontFace can be a
+// no-op.
+
+type defaultFontFaceCatalog struct{}
+
+func (defaultFontFaceCatalog) AcquireFontFace(FontFaceOptions) font.Face {
+ return inconsolata.Regular8x16
+}
+
+func (defaultFontFaceCatalog) ReleaseFontFace(FontFaceOptions, font.Face) {}
+
+// Theme is used for measuring, laying out and painting widgets. It consists of
+// a screen DPI resolution, a set of font faces and colors.
+type Theme struct {
+ // DPI is the screen resolution, in dots (i.e. pixels) per inch.
+ //
+ // A zero value means to use the DefaultDPI.
+ DPI float64
+
+ // FontFaceCatalog provides a theme's font faces.
+ //
+ // A zero value means to use the DefaultFontFaceCatalog.
+ FontFaceCatalog FontFaceCatalog
+
+ // Palette provides a theme's color palette.
+ //
+ // A zero value means to use the DefaultPalette.
+ Palette *Palette
+}
+
+// GetDPI returns the theme's DPI, or the default DPI if the field value is
+// zero.
+func (t *Theme) GetDPI() float64 {
+ if t != nil && t.DPI != 0 {
+ return t.DPI
+ }
+ return DefaultDPI
+}
+
+// GetFontFaceCatalog returns the theme's font face catalog, or the default
+// catalog if the field value is zero.
+func (t *Theme) GetFontFaceCatalog() FontFaceCatalog {
+ if t != nil && t.FontFaceCatalog != nil {
+ return t.FontFaceCatalog
+ }
+ return DefaultFontFaceCatalog
+}
+
+// GetPalette returns the theme's palette, or the default palette if the field
+// value is zero.
+func (t *Theme) GetPalette() *Palette {
+ if t != nil && t.Palette != nil {
+ return t.Palette
+ }
+ return &DefaultPalette
+}
+
+// AcquireFontFace calls the same method on the result of GetFontFaceCatalog.
+func (t *Theme) AcquireFontFace(o FontFaceOptions) font.Face {
+ return t.GetFontFaceCatalog().AcquireFontFace(o)
+}
+
+// ReleaseFontFace calls the same method on the result of GetFontFaceCatalog.
+func (t *Theme) ReleaseFontFace(o FontFaceOptions, f font.Face) {
+ t.GetFontFaceCatalog().ReleaseFontFace(o, f)
+}
+
+// Pixels implements the unit.Converter interface.
+func (t *Theme) Pixels(v unit.Value) fixed.Int26_6 {
+ c := t.Convert(v, unit.Px)
+ return fixed.Int26_6(c.F * 64)
+}
+
+// Convert implements the unit.Converter interface.
+func (t *Theme) Convert(v unit.Value, to unit.Unit) unit.Value {
+ if v.U == to {
+ return v
+ }
+ return unit.Value{
+ F: v.F * t.pixelsPer(v.U) / t.pixelsPer(to),
+ U: to,
+ }
+}
+
+// pixelsPer returns the number of pixels in the unit u.
+func (t *Theme) pixelsPer(u unit.Unit) float64 {
+ switch u {
+ case unit.Px:
+ return 1
+ case unit.Dp:
+ return t.GetDPI() / unit.DensityIndependentPixelsPerInch
+ case unit.Pt:
+ return t.GetDPI() / unit.PointsPerInch
+ case unit.Mm:
+ return t.GetDPI() / unit.MillimetresPerInch
+ case unit.In:
+ return t.GetDPI()
+ }
+
+ f := t.AcquireFontFace(FontFaceOptions{})
+ defer t.ReleaseFontFace(FontFaceOptions{}, f)
+
+ // The 64 is because Height is in 26.6 fixed-point units.
+ h := float64(f.Metrics().Height) / 64
+ switch u {
+ case unit.Em:
+ return h
+ case unit.Ex:
+ return h / 2
+ case unit.Ch:
+ if advance, ok := f.GlyphAdvance('0'); ok {
+ return float64(advance) / 64
+ }
+ return h / 2
+ }
+ return 1
+}
diff --git a/shiny/widget/theme/theme_test.go b/shiny/widget/theme/theme_test.go
new file mode 100644
index 0000000..1e384c6
--- /dev/null
+++ b/shiny/widget/theme/theme_test.go
@@ -0,0 +1,42 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package theme
+
+import (
+ "testing"
+
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/image/math/fixed"
+)
+
+func approxEqual(x, y, tolerance float64) bool {
+ delta := x - y
+ return -tolerance < delta && delta < +tolerance
+}
+
+func TestThemeIsAUnitConverter(t *testing.T) {
+ // 1.5 inches (at the default 72 DPI) should be 108 pixels.
+ c := unit.Converter(Default)
+ got := c.Pixels(unit.Inches(1.5))
+ want := fixed.I(108)
+ if got != want {
+ t.Errorf("1 inch in pixels: got %v, want %v", got, want)
+ }
+
+ // 3 em (at inconsolata.Regular8x16's 16 pixel em-height) should be 48
+ // pixels, regardless of the DPI. That font face is based on a bitmap font,
+ // not a vector font, so its height does not depend on the DPI. 48 pixels
+ // is 48 points at 72 DPI, and 48 pixels is 21.6 points at 160 DPI.
+ for _, dpi := range []float64{72, 160} {
+ c := unit.Converter(&Theme{
+ DPI: dpi,
+ })
+ got := c.Convert(unit.Ems(3), unit.Pt)
+ want := unit.Points(3 * 16 * unit.PointsPerInch / dpi)
+ if got.U != want.U || !approxEqual(got.F, want.F, 1e-10) {
+ t.Errorf("dpi=%v: 3 em in points: got %v, want %v", dpi, got, want)
+ }
+ }
+}
diff --git a/shiny/widget/uniform.go b/shiny/widget/uniform.go
new file mode 100644
index 0000000..c8fadec
--- /dev/null
+++ b/shiny/widget/uniform.go
@@ -0,0 +1,45 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package widget
+
+import (
+ "image"
+ "image/draw"
+
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+)
+
+// Uniform is a shell widget that paints a uniform color, analogous to an
+// image.Uniform.
+type Uniform struct {
+ node.ShellEmbed
+ ThemeColor theme.Color
+}
+
+// NewUniform returns a new Uniform widget of the given color.
+func NewUniform(c theme.Color, inner node.Node) *Uniform {
+ w := &Uniform{
+ ThemeColor: c,
+ }
+ w.Wrapper = w
+ if inner != nil {
+ w.Insert(inner, nil)
+ }
+ return w
+}
+
+func (w *Uniform) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error {
+ w.Marks.UnmarkNeedsPaintBase()
+ if w.ThemeColor != nil {
+ src := w.ThemeColor.Uniform(ctx.Theme)
+ // TODO: should draw.Src be draw.Over?
+ draw.Draw(ctx.Dst, w.Rect.Add(origin), src, image.Point{}, draw.Src)
+ }
+ if c := w.FirstChild; c != nil {
+ return c.Wrapper.PaintBase(ctx, origin.Add(w.Rect.Min))
+ }
+ return nil
+}
diff --git a/shiny/widget/widget.go b/shiny/widget/widget.go
new file mode 100644
index 0000000..0dcbf63
--- /dev/null
+++ b/shiny/widget/widget.go
@@ -0,0 +1,154 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package widget provides graphical user interface widgets.
+//
+// TODO: give an overview and some example code.
+package widget // import "golang.org/x/exp/shiny/widget"
+
+import (
+ "image"
+
+ "golang.org/x/exp/shiny/gesture"
+ "golang.org/x/exp/shiny/screen"
+ "golang.org/x/exp/shiny/unit"
+ "golang.org/x/exp/shiny/widget/node"
+ "golang.org/x/exp/shiny/widget/theme"
+ "golang.org/x/image/math/f64"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/mouse"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/event/size"
+)
+
+// Axis is zero, one or both of the horizontal and vertical axes. For example,
+// a widget may be scrollable in one of the four AxisXxx values.
+type Axis uint8
+
+const (
+ AxisNone = Axis(0)
+ AxisHorizontal = Axis(1)
+ AxisVertical = Axis(2)
+ AxisBoth = Axis(3) // AxisBoth equals AxisHorizontal | AxisVertical.
+)
+
+func (a Axis) Horizontal() bool { return a&AxisHorizontal != 0 }
+func (a Axis) Vertical() bool { return a&AxisVertical != 0 }
+
+// WithLayoutData returns the given node after setting its embedded LayoutData
+// field.
+func WithLayoutData(n node.Node, layoutData interface{}) node.Node {
+ n.Wrappee().LayoutData = layoutData
+ return n
+}
+
+// RunWindowOptions are optional arguments to RunWindow.
+type RunWindowOptions struct {
+ NewWindowOptions screen.NewWindowOptions
+ Theme theme.Theme
+
+ // TODO: some mechanism to process, filter and inject events. Perhaps a
+ // screen.EventFilter interface, and note that the zero value in this
+ // RunWindowOptions implicitly includes the gesture.EventFilter?
+}
+
+// TODO: how does RunWindow's caller inject or process events (whether general
+// like lifecycle events or app-specific)? How does it stop the event loop when
+// the app's work is done?
+
+// TODO: how do widgets signal that they need repaint or relayout?
+
+// TODO: propagate keyboard / mouse / touch events.
+
+// RunWindow creates a new window for s, with the given widget tree, and runs
+// its event loop.
+//
+// A nil opts is valid and means to use the default option values.
+func RunWindow(s screen.Screen, root node.Node, opts *RunWindowOptions) error {
+ var (
+ nwo *screen.NewWindowOptions
+ t *theme.Theme
+ )
+ if opts != nil {
+ nwo = &opts.NewWindowOptions
+ t = &opts.Theme
+ }
+ w, err := s.NewWindow(nwo)
+ if err != nil {
+ return err
+ }
+ defer w.Release()
+
+ // paintPending batches up multiple NeedsPaint observations so that we
+ // paint only once (which can be relatively expensive) even when there are
+ // multiple input events in the queue, such as from a rapidly moving mouse
+ // or from the user typing many keys.
+ //
+ // TODO: determine somehow if there's an external paint event in the queue,
+ // not just internal paint events?
+ //
+ // TODO: if every package that uses package screen should basically
+ // throttle like this, should it be provided at a lower level?
+ paintPending := false
+
+ gef := gesture.EventFilter{EventDeque: w}
+ for {
+ e := w.NextEvent()
+
+ if e = gef.Filter(e); e == nil {
+ continue
+ }
+
+ switch e := e.(type) {
+ case lifecycle.Event:
+ root.OnLifecycleEvent(e)
+ if e.To == lifecycle.StageDead {
+ return nil
+ }
+
+ case gesture.Event, mouse.Event:
+ root.OnInputEvent(e, image.Point{})
+
+ case paint.Event:
+ ctx := &node.PaintContext{
+ Theme: t,
+ Screen: s,
+ Drawer: w,
+ Src2Dst: f64.Aff3{
+ 1, 0, 0,
+ 0, 1, 0,
+ },
+ }
+ if err := root.Paint(ctx, image.Point{}); err != nil {
+ return err
+ }
+ w.Publish()
+ paintPending = false
+
+ case size.Event:
+ if dpi := float64(e.PixelsPerPt) * unit.PointsPerInch; dpi != t.GetDPI() {
+ newT := new(theme.Theme)
+ if t != nil {
+ *newT = *t
+ }
+ newT.DPI = dpi
+ t = newT
+ }
+
+ size := e.Size()
+ root.Measure(t, size.X, size.Y)
+ root.Wrappee().Rect = e.Bounds()
+ root.Layout(t)
+ // TODO: call Mark(node.MarkNeedsPaint)?
+
+ case error:
+ return e
+ }
+
+ if !paintPending && root.Wrappee().Marks.NeedsPaint() {
+ paintPending = true
+ w.Send(paint.Event{})
+ }
+ }
+}
diff --git a/shootout/README b/shootout/README
new file mode 100644
index 0000000..18ad8fd
--- /dev/null
+++ b/shootout/README
@@ -0,0 +1,4 @@
+This directory holds programs from
+"The Computer Language Benchmarks Game".
+They used to be in the main Go distribution in test/bench/shootout
+but are now only of historical value.
diff --git a/shootout/binary-tree-freelist.go b/shootout/binary-tree-freelist.go
new file mode 100644
index 0000000..e4c3e8a
--- /dev/null
+++ b/shootout/binary-tree-freelist.go
@@ -0,0 +1,132 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * based on C program by Kevin Carson
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+)
+
+var n = flag.Int("n", 15, "depth")
+
+type Node struct {
+ item int
+ left, right *Node
+}
+
+type Arena struct {
+ head *Node
+}
+
+var arena Arena
+
+func (n *Node) free() {
+ if n.left != nil {
+ n.left.free()
+ }
+ if n.right != nil {
+ n.right.free()
+ }
+ n.left = arena.head
+ arena.head = n
+}
+
+func (a *Arena) New(item int, left, right *Node) *Node {
+ if a.head == nil {
+ nodes := make([]Node, 3<<uint(*n))
+ for i := 0; i < len(nodes)-1; i++ {
+ nodes[i].left = &nodes[i+1]
+ }
+ a.head = &nodes[0]
+ }
+ n := a.head
+ a.head = a.head.left
+ n.item = item
+ n.left = left
+ n.right = right
+ return n
+}
+
+func bottomUpTree(item, depth int) *Node {
+ if depth <= 0 {
+ return arena.New(item, nil, nil)
+ }
+ return arena.New(item, bottomUpTree(2*item-1, depth-1), bottomUpTree(2*item, depth-1))
+}
+
+func (n *Node) itemCheck() int {
+ if n.left == nil {
+ return n.item
+ }
+ return n.item + n.left.itemCheck() - n.right.itemCheck()
+}
+
+const minDepth = 4
+
+func main() {
+ flag.Parse()
+
+ maxDepth := *n
+ if minDepth+2 > *n {
+ maxDepth = minDepth + 2
+ }
+ stretchDepth := maxDepth + 1
+
+ check := bottomUpTree(0, stretchDepth).itemCheck()
+ fmt.Printf("stretch tree of depth %d\t check: %d\n", stretchDepth, check)
+
+ longLivedTree := bottomUpTree(0, maxDepth)
+
+ for depth := minDepth; depth <= maxDepth; depth += 2 {
+ iterations := 1 << uint(maxDepth-depth+minDepth)
+ check = 0
+
+ for i := 1; i <= iterations; i++ {
+ t := bottomUpTree(i, depth)
+ check += t.itemCheck()
+ t.free()
+ t = bottomUpTree(-i, depth)
+ check += t.itemCheck()
+ t.free()
+ }
+ fmt.Printf("%d\t trees of depth %d\t check: %d\n", iterations*2, depth, check)
+ }
+ fmt.Printf("long lived tree of depth %d\t check: %d\n", maxDepth, longLivedTree.itemCheck())
+}
diff --git a/shootout/binary-tree-freelist.txt b/shootout/binary-tree-freelist.txt
new file mode 100644
index 0000000..f8286dd
--- /dev/null
+++ b/shootout/binary-tree-freelist.txt
@@ -0,0 +1,8 @@
+stretch tree of depth 16 check: -1
+65536 trees of depth 4 check: -65536
+16384 trees of depth 6 check: -16384
+4096 trees of depth 8 check: -4096
+1024 trees of depth 10 check: -1024
+256 trees of depth 12 check: -256
+64 trees of depth 14 check: -64
+long lived tree of depth 15 check: -1
diff --git a/shootout/binary-tree.c b/shootout/binary-tree.c
new file mode 100644
index 0000000..a909246
--- /dev/null
+++ b/shootout/binary-tree.c
@@ -0,0 +1,166 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Shootout Benchmarks
+ http://shootout.alioth.debian.org/
+
+ contributed by Kevin Carson
+ compilation:
+ gcc -O3 -fomit-frame-pointer -funroll-loops -static binary-trees.c -lm
+ icc -O3 -ip -unroll -static binary-trees.c -lm
+*/
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+typedef struct tn {
+ struct tn* left;
+ struct tn* right;
+ long item;
+} treeNode;
+
+
+treeNode* NewTreeNode(treeNode* left, treeNode* right, long item)
+{
+ treeNode* new;
+
+ new = (treeNode*)malloc(sizeof(treeNode));
+
+ new->left = left;
+ new->right = right;
+ new->item = item;
+
+ return new;
+} /* NewTreeNode() */
+
+
+long ItemCheck(treeNode* tree)
+{
+ if (tree->left == NULL)
+ return tree->item;
+ else
+ return tree->item + ItemCheck(tree->left) - ItemCheck(tree->right);
+} /* ItemCheck() */
+
+
+treeNode* BottomUpTree(long item, unsigned depth)
+{
+ if (depth > 0)
+ return NewTreeNode
+ (
+ BottomUpTree(2 * item - 1, depth - 1),
+ BottomUpTree(2 * item, depth - 1),
+ item
+ );
+ else
+ return NewTreeNode(NULL, NULL, item);
+} /* BottomUpTree() */
+
+
+void DeleteTree(treeNode* tree)
+{
+ if (tree->left != NULL)
+ {
+ DeleteTree(tree->left);
+ DeleteTree(tree->right);
+ }
+
+ free(tree);
+} /* DeleteTree() */
+
+
+int main(int argc, char* argv[])
+{
+ unsigned N, depth, minDepth, maxDepth, stretchDepth;
+ treeNode *stretchTree, *longLivedTree, *tempTree;
+
+ N = atol(argv[1]);
+
+ minDepth = 4;
+
+ if ((minDepth + 2) > N)
+ maxDepth = minDepth + 2;
+ else
+ maxDepth = N;
+
+ stretchDepth = maxDepth + 1;
+
+ stretchTree = BottomUpTree(0, stretchDepth);
+ printf
+ (
+ "stretch tree of depth %u\t check: %li\n",
+ stretchDepth,
+ ItemCheck(stretchTree)
+ );
+
+ DeleteTree(stretchTree);
+
+ longLivedTree = BottomUpTree(0, maxDepth);
+
+ for (depth = minDepth; depth <= maxDepth; depth += 2)
+ {
+ long i, iterations, check;
+
+ iterations = pow(2, maxDepth - depth + minDepth);
+
+ check = 0;
+
+ for (i = 1; i <= iterations; i++)
+ {
+ tempTree = BottomUpTree(i, depth);
+ check += ItemCheck(tempTree);
+ DeleteTree(tempTree);
+
+ tempTree = BottomUpTree(-i, depth);
+ check += ItemCheck(tempTree);
+ DeleteTree(tempTree);
+ } /* for(i = 1...) */
+
+ printf
+ (
+ "%li\t trees of depth %u\t check: %li\n",
+ iterations * 2,
+ depth,
+ check
+ );
+ } /* for(depth = minDepth...) */
+
+ printf
+ (
+ "long lived tree of depth %u\t check: %li\n",
+ maxDepth,
+ ItemCheck(longLivedTree)
+ );
+
+ return 0;
+} /* main() */
diff --git a/shootout/binary-tree.go b/shootout/binary-tree.go
new file mode 100644
index 0000000..5f6cb34
--- /dev/null
+++ b/shootout/binary-tree.go
@@ -0,0 +1,95 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * based on C program by Kevin Carson
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+)
+
+var n = flag.Int("n", 15, "depth")
+
+type Node struct {
+ item int
+ left, right *Node
+}
+
+func bottomUpTree(item, depth int) *Node {
+ if depth <= 0 {
+ return &Node{item: item}
+ }
+ return &Node{item, bottomUpTree(2*item-1, depth-1), bottomUpTree(2*item, depth-1)}
+}
+
+func (n *Node) itemCheck() int {
+ if n.left == nil {
+ return n.item
+ }
+ return n.item + n.left.itemCheck() - n.right.itemCheck()
+}
+
+const minDepth = 4
+
+func main() {
+ flag.Parse()
+
+ maxDepth := *n
+ if minDepth+2 > *n {
+ maxDepth = minDepth + 2
+ }
+ stretchDepth := maxDepth + 1
+
+ check := bottomUpTree(0, stretchDepth).itemCheck()
+ fmt.Printf("stretch tree of depth %d\t check: %d\n", stretchDepth, check)
+
+ longLivedTree := bottomUpTree(0, maxDepth)
+
+ for depth := minDepth; depth <= maxDepth; depth += 2 {
+ iterations := 1 << uint(maxDepth-depth+minDepth)
+ check = 0
+
+ for i := 1; i <= iterations; i++ {
+ check += bottomUpTree(i, depth).itemCheck()
+ check += bottomUpTree(-i, depth).itemCheck()
+ }
+ fmt.Printf("%d\t trees of depth %d\t check: %d\n", iterations*2, depth, check)
+ }
+ fmt.Printf("long lived tree of depth %d\t check: %d\n", maxDepth, longLivedTree.itemCheck())
+}
diff --git a/shootout/binary-tree.txt b/shootout/binary-tree.txt
new file mode 100644
index 0000000..f8286dd
--- /dev/null
+++ b/shootout/binary-tree.txt
@@ -0,0 +1,8 @@
+stretch tree of depth 16 check: -1
+65536 trees of depth 4 check: -65536
+16384 trees of depth 6 check: -16384
+4096 trees of depth 8 check: -4096
+1024 trees of depth 10 check: -1024
+256 trees of depth 12 check: -256
+64 trees of depth 14 check: -64
+long lived tree of depth 15 check: -1
diff --git a/shootout/chameneosredux.c b/shootout/chameneosredux.c
new file mode 100644
index 0000000..cb7d94d
--- /dev/null
+++ b/shootout/chameneosredux.c
@@ -0,0 +1,332 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ http://shootout.alioth.debian.org/
+
+ contributed by Michael Barker
+ based on a Java contribution by Luzius Meisser
+
+ convert to C by dualamd
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <pthread.h>
+
+
+enum Colour
+{
+ blue = 0,
+ red = 1,
+ yellow = 2,
+ Invalid = 3
+};
+
+const char* ColourName[] = {"blue", "red", "yellow"};
+const int STACK_SIZE = 32*1024;
+
+typedef unsigned int BOOL;
+const BOOL TRUE = 1;
+const BOOL FALSE = 0;
+
+int CreatureID = 0;
+
+
+enum Colour doCompliment(enum Colour c1, enum Colour c2)
+{
+ switch (c1)
+ {
+ case blue:
+ switch (c2)
+ {
+ case blue:
+ return blue;
+ case red:
+ return yellow;
+ case yellow:
+ return red;
+ default:
+ goto errlb;
+ }
+ case red:
+ switch (c2)
+ {
+ case blue:
+ return yellow;
+ case red:
+ return red;
+ case yellow:
+ return blue;
+ default:
+ goto errlb;
+ }
+ case yellow:
+ switch (c2)
+ {
+ case blue:
+ return red;
+ case red:
+ return blue;
+ case yellow:
+ return yellow;
+ default:
+ goto errlb;
+ }
+ default:
+ break;
+ }
+
+errlb:
+ printf("Invalid colour\n");
+ exit( 1 );
+}
+
+/* convert integer to number string: 1234 -> "one two three four" */
+char* formatNumber(int n, char* outbuf)
+{
+ int ochar = 0, ichar = 0;
+ int i;
+ char tmp[64];
+
+ const char* NUMBERS[] =
+ {
+ "zero", "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine"
+ };
+
+ ichar = sprintf(tmp, "%d", n);
+
+ for (i = 0; i < ichar; i++)
+ ochar += sprintf( outbuf + ochar, " %s", NUMBERS[ tmp[i] - '0' ] );
+
+ return outbuf;
+}
+
+
+struct MeetingPlace
+{
+ pthread_mutex_t mutex;
+ int meetingsLeft;
+ struct Creature* firstCreature;
+};
+
+struct Creature
+{
+ pthread_t ht;
+ pthread_attr_t stack_att;
+
+ struct MeetingPlace* place;
+ int count;
+ int sameCount;
+
+ enum Colour colour;
+ int id;
+
+ BOOL two_met;
+ BOOL sameid;
+};
+
+
+void MeetingPlace_Init(struct MeetingPlace* m, int meetings )
+{
+ pthread_mutex_init( &m->mutex, 0 );
+ m->meetingsLeft = meetings;
+ m->firstCreature = 0;
+}
+
+
+BOOL Meet( struct Creature* cr)
+{
+ BOOL retval = TRUE;
+
+ struct MeetingPlace* mp = cr->place;
+ pthread_mutex_lock( &(mp->mutex) );
+
+ if ( mp->meetingsLeft > 0 )
+ {
+ if ( mp->firstCreature == 0 )
+ {
+ cr->two_met = FALSE;
+ mp->firstCreature = cr;
+ }
+ else
+ {
+ struct Creature* first;
+ enum Colour newColour;
+
+ first = mp->firstCreature;
+ newColour = doCompliment( cr->colour, first->colour );
+
+ cr->sameid = cr->id == first->id;
+ cr->colour = newColour;
+ cr->two_met = TRUE;
+
+ first->sameid = cr->sameid;
+ first->colour = newColour;
+ first->two_met = TRUE;
+
+ mp->firstCreature = 0;
+ mp->meetingsLeft--;
+ }
+ }
+ else
+ retval = FALSE;
+
+ pthread_mutex_unlock( &(mp->mutex) );
+ return retval;
+}
+
+
+void* CreatureThreadRun(void* param)
+{
+ struct Creature* cr = (struct Creature*)param;
+
+ while (TRUE)
+ {
+ if ( Meet(cr) )
+ {
+ while (cr->two_met == FALSE)
+ sched_yield();
+
+ if (cr->sameid)
+ cr->sameCount++;
+ cr->count++;
+ }
+ else
+ break;
+ }
+
+ return 0;
+}
+
+void Creature_Init( struct Creature *cr, struct MeetingPlace* place, enum Colour colour )
+{
+ cr->place = place;
+ cr->count = cr->sameCount = 0;
+
+ cr->id = ++CreatureID;
+ cr->colour = colour;
+ cr->two_met = FALSE;
+
+ pthread_attr_init( &cr->stack_att );
+ pthread_attr_setstacksize( &cr->stack_att, STACK_SIZE );
+ pthread_create( &cr->ht, &cr->stack_att, &CreatureThreadRun, (void*)(cr) );
+}
+
+/* format meeting times of each creature to string */
+char* Creature_getResult(struct Creature* cr, char* str)
+{
+ char numstr[256];
+ formatNumber(cr->sameCount, numstr);
+
+ sprintf( str, "%u%s", cr->count, numstr );
+ return str;
+}
+
+
+void runGame( int n_meeting, int ncolor, const enum Colour* colours )
+{
+ int i;
+ int total = 0;
+ char str[256];
+
+ struct MeetingPlace place;
+ struct Creature *creatures = (struct Creature*) calloc( ncolor, sizeof(struct Creature) );
+
+ MeetingPlace_Init( &place, n_meeting );
+
+ /* print initial color of each creature */
+ for (i = 0; i < ncolor; i++)
+ {
+ printf( "%s ", ColourName[ colours[i] ] );
+ Creature_Init( &(creatures[i]), &place, colours[i] );
+ }
+ printf("\n");
+
+ /* wait for them to meet */
+ for (i = 0; i < ncolor; i++)
+ pthread_join( creatures[i].ht, 0 );
+
+ /* print meeting times of each creature */
+ for (i = 0; i < ncolor; i++)
+ {
+ printf( "%s\n", Creature_getResult(&(creatures[i]), str) );
+ total += creatures[i].count;
+ }
+
+ /* print total meeting times, should equal n_meeting */
+ printf( "%s\n\n", formatNumber(total, str) );
+
+ /* cleaup & quit */
+ pthread_mutex_destroy( &place.mutex );
+ free( creatures );
+}
+
+
+void printColours( enum Colour c1, enum Colour c2 )
+{
+ printf( "%s + %s -> %s\n",
+ ColourName[c1],
+ ColourName[c2],
+ ColourName[doCompliment(c1, c2)] );
+}
+
+void printColoursTable(void)
+{
+ printColours(blue, blue);
+ printColours(blue, red);
+ printColours(blue, yellow);
+ printColours(red, blue);
+ printColours(red, red);
+ printColours(red, yellow);
+ printColours(yellow, blue);
+ printColours(yellow, red);
+ printColours(yellow, yellow);
+}
+
+int main(int argc, char** argv)
+{
+ int n = (argc == 2) ? atoi(argv[1]) : 600;
+
+ printColoursTable();
+ printf("\n");
+
+ const enum Colour r1[] = { blue, red, yellow };
+ const enum Colour r2[] = { blue, red, yellow,
+ red, yellow, blue,
+ red, yellow, red, blue };
+
+ runGame( n, sizeof(r1) / sizeof(r1[0]), r1 );
+ runGame( n, sizeof(r2) / sizeof(r2[0]), r2 );
+
+ return 0;
+}
diff --git a/shootout/chameneosredux.go b/shootout/chameneosredux.go
new file mode 100644
index 0000000..80bea32
--- /dev/null
+++ b/shootout/chameneosredux.go
@@ -0,0 +1,183 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "strconv"
+)
+
+const (
+ blue = iota
+ red
+ yellow
+ ncol
+)
+
+var complement = [...]int{
+ red | red<<2: red,
+ red | yellow<<2: blue,
+ red | blue<<2: yellow,
+ yellow | red<<2: blue,
+ yellow | yellow<<2: yellow,
+ yellow | blue<<2: red,
+ blue | red<<2: yellow,
+ blue | yellow<<2: red,
+ blue | blue<<2: blue,
+}
+
+var colname = [...]string{
+ blue: "blue",
+ red: "red",
+ yellow: "yellow",
+}
+
+// information about the current state of a creature.
+type info struct {
+ colour int // creature's current colour.
+ name int // creature's name.
+}
+
+// exclusive access data-structure kept inside meetingplace.
+// if mate is nil, it indicates there's no creature currently waiting;
+// otherwise the creature's info is stored in info, and
+// it is waiting to receive its mate's information on the mate channel.
+type rendez struct {
+ n int // current number of encounters.
+ mate chan<- info // creature waiting when non-nil.
+ info info // info about creature waiting.
+}
+
+// result sent by each creature at the end of processing.
+type result struct {
+ met int
+ same int
+}
+
+var n = 600
+
+func main() {
+ flag.Parse()
+ if flag.NArg() > 0 {
+ n, _ = strconv.Atoi(flag.Arg(0))
+ }
+
+ for c0 := 0; c0 < ncol; c0++ {
+ for c1 := 0; c1 < ncol; c1++ {
+ fmt.Printf("%s + %s -> %s\n", colname[c0], colname[c1], colname[complement[c0|c1<<2]])
+ }
+ }
+ fmt.Print("\n")
+
+ pallmall([]int{blue, red, yellow})
+ pallmall([]int{blue, red, yellow, red, yellow, blue, red, yellow, red, blue})
+}
+
+func pallmall(cols []int) {
+
+ // invariant: meetingplace always contains a value unless a creature
+ // is currently dealing with it (whereupon it must put it back).
+ meetingplace := make(chan rendez, 1)
+ meetingplace <- rendez{n: 0}
+
+ ended := make(chan result)
+ msg := ""
+ for i, col := range cols {
+ go creature(info{col, i}, meetingplace, ended)
+ msg += " " + colname[col]
+ }
+ fmt.Println(msg)
+ tot := 0
+ // wait for all results
+ for range cols {
+ result := <-ended
+ tot += result.met
+ fmt.Printf("%v%v\n", result.met, spell(result.same, true))
+ }
+ fmt.Printf("%v\n\n", spell(tot, true))
+}
+
+// in this function, variables ending in 0 refer to the local creature,
+// variables ending in 1 to the creature we've met.
+func creature(info0 info, meetingplace chan rendez, ended chan result) {
+ c0 := make(chan info)
+ met := 0
+ same := 0
+ for {
+ var othername int
+ // get access to rendez data and decide what to do.
+ switch r := <-meetingplace; {
+ case r.n >= n:
+ // if no more meetings left, then send our result data and exit.
+ meetingplace <- rendez{n: r.n}
+ ended <- result{met, same}
+ return
+ case r.mate == nil:
+ // no creature waiting; wait for someone to meet us,
+ // get their info and send our info in reply.
+ meetingplace <- rendez{n: r.n, info: info0, mate: c0}
+ info1 := <-c0
+ othername = info1.name
+ info0.colour = complement[info0.colour|info1.colour<<2]
+ default:
+ // another creature is waiting for us with its info;
+ // increment meeting count,
+ // send them our info in reply.
+ r.n++
+ meetingplace <- rendez{n: r.n, mate: nil}
+ r.mate <- info0
+ othername = r.info.name
+ info0.colour = complement[info0.colour|r.info.colour<<2]
+ }
+ if othername == info0.name {
+ same++
+ }
+ met++
+ }
+}
+
+var digits = [...]string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}
+
+func spell(n int, required bool) string {
+ if n == 0 && !required {
+ return ""
+ }
+ return spell(n/10, false) + " " + digits[n%10]
+}
diff --git a/shootout/chameneosredux.txt b/shootout/chameneosredux.txt
new file mode 100644
index 0000000..6016d59
--- /dev/null
+++ b/shootout/chameneosredux.txt
@@ -0,0 +1,29 @@
+blue + blue -> blue
+blue + red -> yellow
+blue + yellow -> red
+red + blue -> yellow
+red + red -> red
+red + yellow -> blue
+yellow + blue -> red
+yellow + red -> blue
+yellow + yellow -> yellow
+
+ blue red yellow
+400 zero
+400 zero
+400 zero
+ one two zero zero
+
+ blue red yellow red yellow blue red yellow red blue
+120 zero
+120 zero
+120 zero
+120 zero
+120 zero
+120 zero
+120 zero
+120 zero
+120 zero
+120 zero
+ one two zero zero
+
diff --git a/shootout/fannkuch-parallel.go b/shootout/fannkuch-parallel.go
new file mode 100644
index 0000000..a69b09c
--- /dev/null
+++ b/shootout/fannkuch-parallel.go
@@ -0,0 +1,227 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+ * The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * Based on fannkuch.scala by Rex Kerr
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "runtime"
+)
+
+var n = flag.Int("n", 7, "count")
+var nCPU = flag.Int("ncpu", 4, "number of cpus")
+
+type Job struct {
+ start []int
+ n int
+}
+
+type Found struct {
+ who *Kucher
+ k int
+}
+
+type Kucher struct {
+ perm []int
+ temp []int
+ flip []int
+ in chan Job
+}
+
+func NewKucher(length int) *Kucher {
+ return &Kucher{
+ perm: make([]int, length),
+ temp: make([]int, length),
+ flip: make([]int, length),
+ in: make(chan Job),
+ }
+}
+
+func (k *Kucher) permute(n int) bool {
+ i := 0
+ for ; i < n-1 && k.flip[i] == 0; i++ {
+ t := k.perm[0]
+ j := 0
+ for ; j <= i; j++ {
+ k.perm[j] = k.perm[j+1]
+ }
+ k.perm[j] = t
+ }
+ k.flip[i]--
+ for i > 0 {
+ i--
+ k.flip[i] = i
+ }
+ return k.flip[n-1] >= 0
+}
+
+func (k *Kucher) count() int {
+ K := 0
+ copy(k.temp, k.perm)
+ for k.temp[0] != 0 {
+ m := k.temp[0]
+ for i := 0; i < m; i++ {
+ k.temp[i], k.temp[m] = k.temp[m], k.temp[i]
+ m--
+ }
+ K++
+ }
+ return K
+}
+
+func (k *Kucher) Run(foreman chan<- Found) {
+ for job := range k.in {
+ verbose := 30
+ copy(k.perm, job.start)
+ for i, v := range k.perm {
+ if v != i {
+ verbose = 0
+ }
+ k.flip[i] = i
+ }
+ K := 0
+ for {
+ if verbose > 0 {
+ for _, p := range k.perm {
+ fmt.Print(p + 1)
+ }
+ fmt.Println()
+ verbose--
+ }
+ count := k.count()
+ if count > K {
+ K = count
+ }
+ if !k.permute(job.n) {
+ break
+ }
+ }
+ foreman <- Found{k, K}
+ }
+}
+
+type Fanner struct {
+ jobind int
+ jobsdone int
+ k int
+ jobs []Job
+ workers []*Kucher
+ in chan Found
+ result chan int
+}
+
+func NewFanner(jobs []Job, workers []*Kucher) *Fanner {
+ return &Fanner{
+ jobs: jobs, workers: workers,
+ in: make(chan Found),
+ result: make(chan int),
+ }
+}
+
+func (f *Fanner) Run(N int) {
+ for msg := range f.in {
+ if msg.k > f.k {
+ f.k = msg.k
+ }
+ if msg.k >= 0 {
+ f.jobsdone++
+ }
+ if f.jobind < len(f.jobs) {
+ msg.who.in <- f.jobs[f.jobind]
+ f.jobind++
+ } else if f.jobsdone == len(f.jobs) {
+ f.result <- f.k
+ return
+ }
+ }
+}
+
+func swapped(a []int, i, j int) []int {
+ b := make([]int, len(a))
+ copy(b, a)
+ b[i], b[j] = a[j], a[i]
+ return b
+}
+
+func main() {
+ flag.Parse()
+ runtime.GOMAXPROCS(*nCPU)
+ N := *n
+ base := make([]int, N)
+ for i := range base {
+ base[i] = i
+ }
+
+ njobs := 1
+ if N > 8 {
+ njobs += (N*(N-1))/2 - 28 // njobs = 1 + sum(8..N-1) = 1 + sum(1..N-1) - sum(1..7)
+ }
+ jobs := make([]Job, njobs)
+ jobsind := 0
+
+ firstN := N
+ if firstN > 8 {
+ firstN = 8
+ }
+ jobs[jobsind] = Job{base, firstN}
+ jobsind++
+ for i := N - 1; i >= 8; i-- {
+ for j := 0; j < i; j++ {
+ jobs[jobsind] = Job{swapped(base, i, j), i}
+ jobsind++
+ }
+ }
+
+ nworkers := *nCPU
+ if njobs < nworkers {
+ nworkers = njobs
+ }
+ workers := make([]*Kucher, nworkers)
+ foreman := NewFanner(jobs, workers)
+ go foreman.Run(N)
+ for i := range workers {
+ k := NewKucher(N)
+ workers[i] = k
+ go k.Run(foreman.in)
+ foreman.in <- Found{k, -1}
+ }
+ fmt.Printf("Pfannkuchen(%d) = %d\n", N, <-foreman.result)
+}
diff --git a/shootout/fannkuch-parallel.txt b/shootout/fannkuch-parallel.txt
new file mode 100644
index 0000000..e66f779
--- /dev/null
+++ b/shootout/fannkuch-parallel.txt
@@ -0,0 +1,31 @@
+1234567
+2134567
+2314567
+3214567
+3124567
+1324567
+2341567
+3241567
+3421567
+4321567
+4231567
+2431567
+3412567
+4312567
+4132567
+1432567
+1342567
+3142567
+4123567
+1423567
+1243567
+2143567
+2413567
+4213567
+2345167
+3245167
+3425167
+4325167
+4235167
+2435167
+Pfannkuchen(7) = 16
diff --git a/shootout/fannkuch.c b/shootout/fannkuch.c
new file mode 100644
index 0000000..a12d8bd
--- /dev/null
+++ b/shootout/fannkuch.c
@@ -0,0 +1,136 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+ * The Computer Language Shootout
+ * http://shootout.alioth.debian.org/
+ * Contributed by Heiner Marxen
+ *
+ * "fannkuch" for C gcc
+ *
+ * $Id: fannkuch.1.gcc.code,v 1.15 2009-04-28 15:39:31 igouy-guest Exp $
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define Int int
+#define Aint int
+
+ static long
+fannkuch( int n )
+{
+ Aint* perm;
+ Aint* perm1;
+ Aint* count;
+ long flips;
+ long flipsMax;
+ Int r;
+ Int i;
+ Int k;
+ Int didpr;
+ const Int n1 = n - 1;
+
+ if( n < 1 ) return 0;
+
+ perm = calloc(n, sizeof(*perm ));
+ perm1 = calloc(n, sizeof(*perm1));
+ count = calloc(n, sizeof(*count));
+
+ for( i=0 ; i<n ; ++i ) perm1[i] = i; /* initial (trivial) permu */
+
+ r = n; didpr = 0; flipsMax = 0;
+ for(;;) {
+ if( didpr < 30 ) {
+ for( i=0 ; i<n ; ++i ) printf("%d", (int)(1+perm1[i]));
+ printf("\n");
+ ++didpr;
+ }
+ for( ; r!=1 ; --r ) {
+ count[r-1] = r;
+ }
+
+#define XCH(x,y) { Aint t_mp; t_mp=(x); (x)=(y); (y)=t_mp; }
+
+ if( ! (perm1[0]==0 || perm1[n1]==n1) ) {
+ flips = 0;
+ for( i=1 ; i<n ; ++i ) { /* perm = perm1 */
+ perm[i] = perm1[i];
+ }
+ k = perm1[0]; /* cache perm[0] in k */
+ do { /* k!=0 ==> k>0 */
+ Int j;
+ for( i=1, j=k-1 ; i<j ; ++i, --j ) {
+ XCH(perm[i], perm[j])
+ }
+ ++flips;
+ /*
+ * Now exchange k (caching perm[0]) and perm[k]... with care!
+ * XCH(k, perm[k]) does NOT work!
+ */
+ j=perm[k]; perm[k]=k ; k=j;
+ }while( k );
+ if( flipsMax < flips ) {
+ flipsMax = flips;
+ }
+ }
+
+ for(;;) {
+ if( r == n ) {
+ return flipsMax;
+ }
+ /* rotate down perm[0..r] by one */
+ {
+ Int perm0 = perm1[0];
+ i = 0;
+ while( i < r ) {
+ k = i+1;
+ perm1[i] = perm1[k];
+ i = k;
+ }
+ perm1[r] = perm0;
+ }
+ if( (count[r] -= 1) > 0 ) {
+ break;
+ }
+ ++r;
+ }
+ }
+}
+
+ int
+main( int argc, char* argv[] )
+{
+ int n = (argc>1) ? atoi(argv[1]) : 0;
+
+ printf("Pfannkuchen(%d) = %ld\n", n, fannkuch(n));
+ return 0;
+}
diff --git a/shootout/fannkuch.go b/shootout/fannkuch.go
new file mode 100644
index 0000000..4f07dff
--- /dev/null
+++ b/shootout/fannkuch.go
@@ -0,0 +1,125 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+ * The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * Based on fannkuch.c by Heiner Marxen
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+)
+
+var n = flag.Int("n", 7, "count")
+
+func fannkuch(n int) int {
+ if n < 1 {
+ return 0
+ }
+
+ n1 := n - 1
+ perm := make([]int, n)
+ perm1 := make([]int, n)
+ count := make([]int, n)
+
+ for i := 0; i < n; i++ {
+ perm1[i] = i // initial (trivial) permutation
+ }
+
+ r := n
+ didpr := 0
+ flipsMax := 0
+ for {
+ if didpr < 30 {
+ for i := 0; i < n; i++ {
+ fmt.Printf("%d", 1+perm1[i])
+ }
+ fmt.Printf("\n")
+ didpr++
+ }
+ for ; r != 1; r-- {
+ count[r-1] = r
+ }
+
+ if perm1[0] != 0 && perm1[n1] != n1 {
+ flips := 0
+ for i := 1; i < n; i++ { // perm = perm1
+ perm[i] = perm1[i]
+ }
+ k := perm1[0] // cache perm[0] in k
+ for { // k!=0 ==> k>0
+ for i, j := 1, k-1; i < j; i, j = i+1, j-1 {
+ perm[i], perm[j] = perm[j], perm[i]
+ }
+ flips++
+ // Now exchange k (caching perm[0]) and perm[k]... with care!
+ j := perm[k]
+ perm[k] = k
+ k = j
+ if k == 0 {
+ break
+ }
+ }
+ if flipsMax < flips {
+ flipsMax = flips
+ }
+ }
+
+ for ; r < n; r++ {
+ // rotate down perm[0..r] by one
+ perm0 := perm1[0]
+ for i := 0; i < r; i++ {
+ perm1[i] = perm1[i+1]
+ }
+ perm1[r] = perm0
+ count[r]--
+ if count[r] > 0 {
+ break
+ }
+ }
+ if r == n {
+ return flipsMax
+ }
+ }
+ return 0
+}
+
+func main() {
+ flag.Parse()
+ fmt.Printf("Pfannkuchen(%d) = %d\n", *n, fannkuch(*n))
+}
diff --git a/shootout/fannkuch.txt b/shootout/fannkuch.txt
new file mode 100644
index 0000000..e66f779
--- /dev/null
+++ b/shootout/fannkuch.txt
@@ -0,0 +1,31 @@
+1234567
+2134567
+2314567
+3214567
+3124567
+1324567
+2341567
+3241567
+3421567
+4321567
+4231567
+2431567
+3412567
+4312567
+4132567
+1432567
+1342567
+3142567
+4123567
+1423567
+1243567
+2143567
+2413567
+4213567
+2345167
+3245167
+3425167
+4325167
+4235167
+2435167
+Pfannkuchen(7) = 16
diff --git a/shootout/fasta-1000.txt b/shootout/fasta-1000.txt
new file mode 100644
index 0000000..f1caba0
--- /dev/null
+++ b/shootout/fasta-1000.txt
@@ -0,0 +1,171 @@
+>ONE Homo sapiens alu
+GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA
+TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACT
+AAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAG
+GCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCG
+CCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGT
+GGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCA
+GGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAA
+TTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAG
+AATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCA
+GCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGT
+AATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACC
+AGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTG
+GTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACC
+CGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAG
+AGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTT
+TGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACA
+TGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCT
+GTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGG
+TTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGT
+CTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGG
+CGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCG
+TCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTA
+CTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCG
+AGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCG
+GGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACC
+TGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAA
+TACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGA
+GGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACT
+GCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTC
+ACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGT
+TCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGC
+CGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCG
+CTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTG
+GGCGACAGAGCGAGACTCCG
+>TWO IUB ambiguity codes
+cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg
+tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa
+NtactMcSMtYtcMgRtacttctWBacgaaatatagScDtttgaagacacatagtVgYgt
+cattHWtMMWcStgttaggKtSgaYaaccWStcgBttgcgaMttBYatcWtgacaYcaga
+gtaBDtRacttttcWatMttDBcatWtatcttactaBgaYtcttgttttttttYaaScYa
+HgtgttNtSatcMtcVaaaStccRcctDaataataStcYtRDSaMtDttgttSagtRRca
+tttHatSttMtWgtcgtatSSagactYaaattcaMtWatttaSgYttaRgKaRtccactt
+tattRggaMcDaWaWagttttgacatgttctacaaaRaatataataaMttcgDacgaSSt
+acaStYRctVaNMtMgtaggcKatcttttattaaaaagVWaHKYagtttttatttaacct
+tacgtVtcVaattVMBcttaMtttaStgacttagattWWacVtgWYagWVRctDattBYt
+gtttaagaagattattgacVatMaacattVctgtBSgaVtgWWggaKHaatKWcBScSWa
+accRVacacaaactaccScattRatatKVtactatatttHttaagtttSKtRtacaaagt
+RDttcaaaaWgcacatWaDgtDKacgaacaattacaRNWaatHtttStgttattaaMtgt
+tgDcgtMgcatBtgcttcgcgaDWgagctgcgaggggVtaaScNatttacttaatgacag
+cccccacatYScaMgtaggtYaNgttctgaMaacNaMRaacaaacaKctacatagYWctg
+ttWaaataaaataRattagHacacaagcgKatacBttRttaagtatttccgatctHSaat
+actcNttMaagtattMtgRtgaMgcataatHcMtaBSaRattagttgatHtMttaaKagg
+YtaaBataSaVatactWtataVWgKgttaaaacagtgcgRatatacatVtHRtVYataSa
+KtWaStVcNKHKttactatccctcatgWHatWaRcttactaggatctataDtDHBttata
+aaaHgtacVtagaYttYaKcctattcttcttaataNDaaggaaaDYgcggctaaWSctBa
+aNtgctggMBaKctaMVKagBaactaWaDaMaccYVtNtaHtVWtKgRtcaaNtYaNacg
+gtttNattgVtttctgtBaWgtaattcaagtcaVWtactNggattctttaYtaaagccgc
+tcttagHVggaYtgtNcDaVagctctctKgacgtatagYcctRYHDtgBattDaaDgccK
+tcHaaStttMcctagtattgcRgWBaVatHaaaataYtgtttagMDMRtaataaggatMt
+ttctWgtNtgtgaaaaMaatatRtttMtDgHHtgtcattttcWattRSHcVagaagtacg
+ggtaKVattKYagactNaatgtttgKMMgYNtcccgSKttctaStatatNVataYHgtNa
+BKRgNacaactgatttcctttaNcgatttctctataScaHtataRagtcRVttacDSDtt
+aRtSatacHgtSKacYagttMHtWataggatgactNtatSaNctataVtttRNKtgRacc
+tttYtatgttactttttcctttaaacatacaHactMacacggtWataMtBVacRaSaatc
+cgtaBVttccagccBcttaRKtgtgcctttttRtgtcagcRttKtaaacKtaaatctcac
+aattgcaNtSBaaccgggttattaaBcKatDagttactcttcattVtttHaaggctKKga
+tacatcBggScagtVcacattttgaHaDSgHatRMaHWggtatatRgccDttcgtatcga
+aacaHtaagttaRatgaVacttagattVKtaaYttaaatcaNatccRttRRaMScNaaaD
+gttVHWgtcHaaHgacVaWtgttScactaagSgttatcttagggDtaccagWattWtRtg
+ttHWHacgattBtgVcaYatcggttgagKcWtKKcaVtgaYgWctgYggVctgtHgaNcV
+taBtWaaYatcDRaaRtSctgaHaYRttagatMatgcatttNattaDttaattgttctaa
+ccctcccctagaWBtttHtBccttagaVaatMcBHagaVcWcagBVttcBtaYMccagat
+gaaaaHctctaacgttagNWRtcggattNatcRaNHttcagtKttttgWatWttcSaNgg
+gaWtactKKMaacatKatacNattgctWtatctaVgagctatgtRaHtYcWcttagccaa
+tYttWttaWSSttaHcaaaaagVacVgtaVaRMgattaVcDactttcHHggHRtgNcctt
+tYatcatKgctcctctatVcaaaaKaaaagtatatctgMtWtaaaacaStttMtcgactt
+taSatcgDataaactaaacaagtaaVctaggaSccaatMVtaaSKNVattttgHccatca
+cBVctgcaVatVttRtactgtVcaattHgtaaattaaattttYtatattaaRSgYtgBag
+aHSBDgtagcacRHtYcBgtcacttacactaYcgctWtattgSHtSatcataaatataHt
+cgtYaaMNgBaatttaRgaMaatatttBtttaaaHHKaatctgatWatYaacttMctctt
+ttVctagctDaaagtaVaKaKRtaacBgtatccaaccactHHaagaagaaggaNaaatBW
+attccgStaMSaMatBttgcatgRSacgttVVtaaDMtcSgVatWcaSatcttttVatag
+ttactttacgatcaccNtaDVgSRcgVcgtgaacgaNtaNatatagtHtMgtHcMtagaa
+attBgtataRaaaacaYKgtRccYtatgaagtaataKgtaaMttgaaRVatgcagaKStc
+tHNaaatctBBtcttaYaBWHgtVtgacagcaRcataWctcaBcYacYgatDgtDHccta
+>THREE Homo sapiens frequency
+aacacttcaccaggtatcgtgaaggctcaagattacccagagaacctttgcaatataaga
+atatgtatgcagcattaccctaagtaattatattctttttctgactcaaagtgacaagcc
+ctagtgtatattaaatcggtatatttgggaaattcctcaaactatcctaatcaggtagcc
+atgaaagtgatcaaaaaagttcgtacttataccatacatgaattctggccaagtaaaaaa
+tagattgcgcaaaattcgtaccttaagtctctcgccaagatattaggatcctattactca
+tatcgtgtttttctttattgccgccatccccggagtatctcacccatccttctcttaaag
+gcctaatattacctatgcaaataaacatatattgttgaaaattgagaacctgatcgtgat
+tcttatgtgtaccatatgtatagtaatcacgcgactatatagtgctttagtatcgcccgt
+gggtgagtgaatattctgggctagcgtgagatagtttcttgtcctaatatttttcagatc
+gaatagcttctatttttgtgtttattgacatatgtcgaaactccttactcagtgaaagtc
+atgaccagatccacgaacaatcttcggaatcagtctcgttttacggcggaatcttgagtc
+taacttatatcccgtcgcttactttctaacaccccttatgtatttttaaaattacgttta
+ttcgaacgtacttggcggaagcgttattttttgaagtaagttacattgggcagactcttg
+acattttcgatacgactttctttcatccatcacaggactcgttcgtattgatatcagaag
+ctcgtgatgattagttgtcttctttaccaatactttgaggcctattctgcgaaatttttg
+ttgccctgcgaacttcacataccaaggaacacctcgcaacatgccttcatatccatcgtt
+cattgtaattcttacacaatgaatcctaagtaattacatccctgcgtaaaagatggtagg
+ggcactgaggatatattaccaagcatttagttatgagtaatcagcaatgtttcttgtatt
+aagttctctaaaatagttacatcgtaatgttatctcgggttccgcgaataaacgagatag
+attcattatatatggccctaagcaaaaacctcctcgtattctgttggtaattagaatcac
+acaatacgggttgagatattaattatttgtagtacgaagagatataaaaagatgaacaat
+tactcaagtcaagatgtatacgggatttataataaaaatcgggtagagatctgctttgca
+attcagacgtgccactaaatcgtaatatgtcgcgttacatcagaaagggtaactattatt
+aattaataaagggcttaatcactacatattagatcttatccgatagtcttatctattcgt
+tgtatttttaagcggttctaattcagtcattatatcagtgctccgagttctttattattg
+ttttaaggatgacaaaatgcctcttgttataacgctgggagaagcagactaagagtcgga
+gcagttggtagaatgaggctgcaaaagacggtctcgacgaatggacagactttactaaac
+caatgaaagacagaagtagagcaaagtctgaagtggtatcagcttaattatgacaaccct
+taatacttccctttcgccgaatactggcgtggaaaggttttaaaagtcgaagtagttaga
+ggcatctctcgctcataaataggtagactactcgcaatccaatgtgactatgtaatactg
+ggaacatcagtccgcgatgcagcgtgtttatcaaccgtccccactcgcctggggagacat
+gagaccacccccgtggggattattagtccgcagtaatcgactcttgacaatccttttcga
+ttatgtcatagcaatttacgacagttcagcgaagtgactactcggcgaaatggtattact
+aaagcattcgaacccacatgaatgtgattcttggcaatttctaatccactaaagcttttc
+cgttgaatctggttgtagatatttatataagttcactaattaagatcacggtagtatatt
+gatagtgatgtctttgcaagaggttggccgaggaatttacggattctctattgatacaat
+ttgtctggcttataactcttaaggctgaaccaggcgtttttagacgacttgatcagctgt
+tagaatggtttggactccctctttcatgtcagtaacatttcagccgttattgttacgata
+tgcttgaacaatattgatctaccacacacccatagtatattttataggtcatgctgttac
+ctacgagcatggtattccacttcccattcaatgagtattcaacatcactagcctcagaga
+tgatgacccacctctaataacgtcacgttgcggccatgtgaaacctgaacttgagtagac
+gatatcaagcgctttaaattgcatataacatttgagggtaaagctaagcggatgctttat
+ataatcaatactcaataataagatttgattgcattttagagttatgacacgacatagttc
+actaacgagttactattcccagatctagactgaagtactgatcgagacgatccttacgtc
+gatgatcgttagttatcgacttaggtcgggtctctagcggtattggtacttaaccggaca
+ctatactaataacccatgatcaaagcataacagaatacagacgataatttcgccaacata
+tatgtacagaccccaagcatgagaagctcattgaaagctatcattgaagtcccgctcaca
+atgtgtcttttccagacggtttaactggttcccgggagtcctggagtttcgacttacata
+aatggaaacaatgtattttgctaatttatctatagcgtcatttggaccaatacagaatat
+tatgttgcctagtaatccactataacccgcaagtgctgatagaaaatttttagacgattt
+ataaatgccccaagtatccctcccgtgaatcctccgttatactaattagtattcgttcat
+acgtataccgcgcatatatgaacatttggcgataaggcgcgtgaattgttacgtgacaga
+gatagcagtttcttgtgatatggttaacagacgtacatgaagggaaactttatatctata
+gtgatgcttccgtagaaataccgccactggtctgccaatgatgaagtatgtagctttagg
+tttgtactatgaggctttcgtttgtttgcagagtataacagttgcgagtgaaaaaccgac
+gaatttatactaatacgctttcactattggctacaaaatagggaagagtttcaatcatga
+gagggagtatatggatgctttgtagctaaaggtagaacgtatgtatatgctgccgttcat
+tcttgaaagatacataagcgataagttacgacaattataagcaacatccctaccttcgta
+acgatttcactgttactgcgcttgaaatacactatggggctattggcggagagaagcaga
+tcgcgccgagcatatacgagacctataatgttgatgatagagaaggcgtctgaattgata
+catcgaagtacactttctttcgtagtatctctcgtcctctttctatctccggacacaaga
+attaagttatatatatagagtcttaccaatcatgttgaatcctgattctcagagttcttt
+ggcgggccttgtgatgactgagaaacaatgcaatattgctccaaatttcctaagcaaatt
+ctcggttatgttatgttatcagcaaagcgttacgttatgttatttaaatctggaatgacg
+gagcgaagttcttatgtcggtgtgggaataattcttttgaagacagcactccttaaataa
+tatcgctccgtgtttgtatttatcgaatgggtctgtaaccttgcacaagcaaatcggtgg
+tgtatatatcggataacaattaatacgatgttcatagtgacagtatactgatcgagtcct
+ctaaagtcaattacctcacttaacaatctcattgatgttgtgtcattcccggtatcgccc
+gtagtatgtgctctgattgaccgagtgtgaaccaaggaacatctactaatgcctttgtta
+ggtaagatctctctgaattccttcgtgccaacttaaaacattatcaaaatttcttctact
+tggattaactacttttacgagcatggcaaattcccctgtggaagacggttcattattatc
+ggaaaccttatagaaattgcgtgttgactgaaattagatttttattgtaagagttgcatc
+tttgcgattcctctggtctagcttccaatgaacagtcctcccttctattcgacatcgggt
+ccttcgtacatgtctttgcgatgtaataattaggttcggagtgtggccttaatgggtgca
+actaggaatacaacgcaaatttgctgacatgatagcaaatcggtatgccggcaccaaaac
+gtgctccttgcttagcttgtgaatgagactcagtagttaaataaatccatatctgcaatc
+gattccacaggtattgtccactatctttgaactactctaagagatacaagcttagctgag
+accgaggtgtatatgactacgctgatatctgtaaggtaccaatgcaggcaaagtatgcga
+gaagctaataccggctgtttccagctttataagattaaaatttggctgtcctggcggcct
+cagaattgttctatcgtaatcagttggttcattaattagctaagtacgaggtacaactta
+tctgtcccagaacagctccacaagtttttttacagccgaaacccctgtgtgaatcttaat
+atccaagcgcgttatctgattagagtttacaactcagtattttatcagtacgttttgttt
+ccaacattacccggtatgacaaaatgacgccacgtgtcgaataatggtctgaccaatgta
+ggaagtgaaaagataaatat
diff --git a/shootout/fasta.c b/shootout/fasta.c
new file mode 100644
index 0000000..cf0f2a7
--- /dev/null
+++ b/shootout/fasta.c
@@ -0,0 +1,221 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+ * http://shootout.alioth.debian.org/u32/program.php?test=fasta&lang=gcc&id=3
+ */
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by Petr Prokhorenkov
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef fwrite_unlocked
+// not available on OS X
+#define fwrite_unlocked fwrite
+#define fputc_unlocked fputc
+#define fputs_unlocked fputs
+#endif
+
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+#define unlikely(x) __builtin_expect((x), 0)
+
+#define IM 139968
+#define IA 3877
+#define IC 29573
+
+#define LINE_LEN 60
+#define LOOKUP_SIZE 4096
+#define LOOKUP_SCALE ((float)(LOOKUP_SIZE - 1))
+
+typedef unsigned random_t;
+
+void
+random_init(random_t *random) {
+ *random = 42;
+}
+
+// Special version with result rescaled to LOOKUP_SCALE.
+static inline
+float
+random_next_lookup(random_t *random) {
+ *random = (*random*IA + IC)%IM;
+
+ return (*random)*(LOOKUP_SCALE/IM);
+}
+
+struct amino_acid {
+ char sym;
+ float prob;
+ float cprob_lookup;
+};
+
+void
+repeat(const char *alu, const char *title, int n) {
+ int len = strlen(alu);
+ char buffer[len + LINE_LEN];
+ int pos = 0;
+
+ memcpy(buffer, alu, len);
+ memcpy(buffer + len, alu, LINE_LEN);
+
+ fputs_unlocked(title, stdout);
+ while (n > 0) {
+ int bytes = n > LINE_LEN ? LINE_LEN : n;
+
+ fwrite_unlocked(buffer + pos, bytes, 1, stdout);
+ pos += bytes;
+ if (pos > len) {
+ pos -= len;
+ }
+ fputc_unlocked('\n', stdout);
+ n -= bytes;
+ }
+}
+
+/*
+ * Lookup table contains mapping from real values to cumulative
+ * probabilities. Careful selection of table size allows lookup
+ * virtually in constant time.
+ *
+ * All cumulative probabilities are rescaled to LOOKUP_SCALE,
+ * this allows to save one multiplication operation on each iteration
+ * in randomize().
+ */
+
+void *
+fill_lookup(struct amino_acid **lookup, struct amino_acid *amino_acid, int amino_acid_size) {
+ float p = 0;
+ int i, j;
+
+ for (i = 0; i < amino_acid_size; i++) {
+ p += amino_acid[i].prob;
+ amino_acid[i].cprob_lookup = p*LOOKUP_SCALE;
+ }
+
+ // Prevent rounding error.
+ amino_acid[amino_acid_size - 1].cprob_lookup = LOOKUP_SIZE - 1;
+
+ for (i = 0, j = 0; i < LOOKUP_SIZE; i++) {
+ while (amino_acid[j].cprob_lookup < i) {
+ j++;
+ }
+ lookup[i] = &amino_acid[j];
+ }
+
+ return 0;
+}
+
+void
+randomize(struct amino_acid *amino_acid, int amino_acid_size,
+ const char *title, int n, random_t *rand) {
+ struct amino_acid *lookup[LOOKUP_SIZE];
+ char line_buffer[LINE_LEN + 1];
+ int i, j;
+
+ line_buffer[LINE_LEN] = '\n';
+
+ fill_lookup(lookup, amino_acid, amino_acid_size);
+
+ fputs_unlocked(title, stdout);
+
+ for (i = 0, j = 0; i < n; i++, j++) {
+ if (j == LINE_LEN) {
+ fwrite_unlocked(line_buffer, LINE_LEN + 1, 1, stdout);
+ j = 0;
+ }
+
+ float r = random_next_lookup(rand);
+ struct amino_acid *u = lookup[(short)r];
+ while (unlikely(u->cprob_lookup < r)) {
+ ++u;
+ }
+ line_buffer[j] = u->sym;
+ }
+ line_buffer[j] = '\n';
+ fwrite_unlocked(line_buffer, j + 1, 1, stdout);
+}
+
+struct amino_acid amino_acid[] = {
+ { 'a', 0.27 },
+ { 'c', 0.12 },
+ { 'g', 0.12 },
+ { 't', 0.27 },
+
+ { 'B', 0.02 },
+ { 'D', 0.02 },
+ { 'H', 0.02 },
+ { 'K', 0.02 },
+ { 'M', 0.02 },
+ { 'N', 0.02 },
+ { 'R', 0.02 },
+ { 'S', 0.02 },
+ { 'V', 0.02 },
+ { 'W', 0.02 },
+ { 'Y', 0.02 },
+};
+
+struct amino_acid homo_sapiens[] = {
+ { 'a', 0.3029549426680 },
+ { 'c', 0.1979883004921 },
+ { 'g', 0.1975473066391 },
+ { 't', 0.3015094502008 },
+};
+
+static const char alu[] =
+ "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTG"
+ "GGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGA"
+ "GACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAA"
+ "AATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAAT"
+ "CCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAAC"
+ "CCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTG"
+ "CACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAA";
+
+int
+main(int argc, const char **argv) {
+ int n = argc > 1 ? atoi( argv[1] ) : 512;
+ random_t rand;
+
+ random_init(&rand);
+
+ repeat(alu, ">ONE Homo sapiens alu\n", n*2);
+ randomize(amino_acid, ARRAY_SIZE(amino_acid),
+ ">TWO IUB ambiguity codes\n", n*3, &rand);
+ randomize(homo_sapiens, ARRAY_SIZE(homo_sapiens),
+ ">THREE Homo sapiens frequency\n", n*5, &rand);
+
+ return 0;
+}
diff --git a/shootout/fasta.go b/shootout/fasta.go
new file mode 100644
index 0000000..3890113
--- /dev/null
+++ b/shootout/fasta.go
@@ -0,0 +1,208 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * Based on C program by by Petr Prokhorenkov.
+ */
+
+package main
+
+import (
+ "flag"
+ "os"
+)
+
+var out = make(buffer, 0, 32768)
+
+var n = flag.Int("n", 1000, "length of result")
+
+const Line = 60
+
+func Repeat(alu []byte, n int) {
+ buf := append(alu, alu...)
+ off := 0
+ for n > 0 {
+ m := n
+ if m > Line {
+ m = Line
+ }
+ buf1 := out.NextWrite(m + 1)
+ copy(buf1, buf[off:])
+ buf1[m] = '\n'
+ if off += m; off >= len(alu) {
+ off -= len(alu)
+ }
+ n -= m
+ }
+}
+
+const (
+ IM = 139968
+ IA = 3877
+ IC = 29573
+
+ LookupSize = 4096
+ LookupScale float64 = LookupSize - 1
+)
+
+var rand uint32 = 42
+
+type Acid struct {
+ sym byte
+ prob float64
+ cprob float64
+ next *Acid
+}
+
+func computeLookup(acid []Acid) *[LookupSize]*Acid {
+ var lookup [LookupSize]*Acid
+ var p float64
+ for i := range acid {
+ p += acid[i].prob
+ acid[i].cprob = p * LookupScale
+ if i > 0 {
+ acid[i-1].next = &acid[i]
+ }
+ }
+ acid[len(acid)-1].cprob = 1.0 * LookupScale
+
+ j := 0
+ for i := range lookup {
+ for acid[j].cprob < float64(i) {
+ j++
+ }
+ lookup[i] = &acid[j]
+ }
+
+ return &lookup
+}
+
+func Random(acid []Acid, n int) {
+ lookup := computeLookup(acid)
+ for n > 0 {
+ m := n
+ if m > Line {
+ m = Line
+ }
+ buf := out.NextWrite(m + 1)
+ f := LookupScale / IM
+ myrand := rand
+ for i := 0; i < m; i++ {
+ myrand = (myrand*IA + IC) % IM
+ r := float64(int(myrand)) * f
+ a := lookup[int(r)]
+ for a.cprob < r {
+ a = a.next
+ }
+ buf[i] = a.sym
+ }
+ rand = myrand
+ buf[m] = '\n'
+ n -= m
+ }
+}
+
+func main() {
+ defer out.Flush()
+
+ flag.Parse()
+
+ iub := []Acid{
+ {prob: 0.27, sym: 'a'},
+ {prob: 0.12, sym: 'c'},
+ {prob: 0.12, sym: 'g'},
+ {prob: 0.27, sym: 't'},
+ {prob: 0.02, sym: 'B'},
+ {prob: 0.02, sym: 'D'},
+ {prob: 0.02, sym: 'H'},
+ {prob: 0.02, sym: 'K'},
+ {prob: 0.02, sym: 'M'},
+ {prob: 0.02, sym: 'N'},
+ {prob: 0.02, sym: 'R'},
+ {prob: 0.02, sym: 'S'},
+ {prob: 0.02, sym: 'V'},
+ {prob: 0.02, sym: 'W'},
+ {prob: 0.02, sym: 'Y'},
+ }
+
+ homosapiens := []Acid{
+ {prob: 0.3029549426680, sym: 'a'},
+ {prob: 0.1979883004921, sym: 'c'},
+ {prob: 0.1975473066391, sym: 'g'},
+ {prob: 0.3015094502008, sym: 't'},
+ }
+
+ alu := []byte(
+ "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGG" +
+ "GAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGA" +
+ "CCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAAT" +
+ "ACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCA" +
+ "GCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGG" +
+ "AGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCC" +
+ "AGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAA")
+
+ out.WriteString(">ONE Homo sapiens alu\n")
+ Repeat(alu, 2**n)
+ out.WriteString(">TWO IUB ambiguity codes\n")
+ Random(iub, 3**n)
+ out.WriteString(">THREE Homo sapiens frequency\n")
+ Random(homosapiens, 5**n)
+}
+
+type buffer []byte
+
+func (b *buffer) Flush() {
+ p := *b
+ if len(p) > 0 {
+ os.Stdout.Write(p)
+ }
+ *b = p[0:0]
+}
+
+func (b *buffer) WriteString(s string) {
+ p := b.NextWrite(len(s))
+ copy(p, s)
+}
+
+func (b *buffer) NextWrite(n int) []byte {
+ p := *b
+ if len(p)+n > cap(p) {
+ b.Flush()
+ p = *b
+ }
+ out := p[len(p) : len(p)+n]
+ *b = p[:len(p)+n]
+ return out
+}
diff --git a/shootout/fasta.txt b/shootout/fasta.txt
new file mode 100644
index 0000000..f1caba0
--- /dev/null
+++ b/shootout/fasta.txt
@@ -0,0 +1,171 @@
+>ONE Homo sapiens alu
+GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA
+TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACT
+AAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAG
+GCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCG
+CCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGT
+GGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCA
+GGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAA
+TTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAG
+AATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCA
+GCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGT
+AATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACC
+AGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTG
+GTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACC
+CGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAG
+AGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTT
+TGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACA
+TGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCT
+GTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGG
+TTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGT
+CTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGG
+CGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCG
+TCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTA
+CTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCG
+AGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCG
+GGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACC
+TGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAA
+TACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGA
+GGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACT
+GCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTC
+ACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGT
+TCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGC
+CGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCG
+CTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTG
+GGCGACAGAGCGAGACTCCG
+>TWO IUB ambiguity codes
+cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg
+tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa
+NtactMcSMtYtcMgRtacttctWBacgaaatatagScDtttgaagacacatagtVgYgt
+cattHWtMMWcStgttaggKtSgaYaaccWStcgBttgcgaMttBYatcWtgacaYcaga
+gtaBDtRacttttcWatMttDBcatWtatcttactaBgaYtcttgttttttttYaaScYa
+HgtgttNtSatcMtcVaaaStccRcctDaataataStcYtRDSaMtDttgttSagtRRca
+tttHatSttMtWgtcgtatSSagactYaaattcaMtWatttaSgYttaRgKaRtccactt
+tattRggaMcDaWaWagttttgacatgttctacaaaRaatataataaMttcgDacgaSSt
+acaStYRctVaNMtMgtaggcKatcttttattaaaaagVWaHKYagtttttatttaacct
+tacgtVtcVaattVMBcttaMtttaStgacttagattWWacVtgWYagWVRctDattBYt
+gtttaagaagattattgacVatMaacattVctgtBSgaVtgWWggaKHaatKWcBScSWa
+accRVacacaaactaccScattRatatKVtactatatttHttaagtttSKtRtacaaagt
+RDttcaaaaWgcacatWaDgtDKacgaacaattacaRNWaatHtttStgttattaaMtgt
+tgDcgtMgcatBtgcttcgcgaDWgagctgcgaggggVtaaScNatttacttaatgacag
+cccccacatYScaMgtaggtYaNgttctgaMaacNaMRaacaaacaKctacatagYWctg
+ttWaaataaaataRattagHacacaagcgKatacBttRttaagtatttccgatctHSaat
+actcNttMaagtattMtgRtgaMgcataatHcMtaBSaRattagttgatHtMttaaKagg
+YtaaBataSaVatactWtataVWgKgttaaaacagtgcgRatatacatVtHRtVYataSa
+KtWaStVcNKHKttactatccctcatgWHatWaRcttactaggatctataDtDHBttata
+aaaHgtacVtagaYttYaKcctattcttcttaataNDaaggaaaDYgcggctaaWSctBa
+aNtgctggMBaKctaMVKagBaactaWaDaMaccYVtNtaHtVWtKgRtcaaNtYaNacg
+gtttNattgVtttctgtBaWgtaattcaagtcaVWtactNggattctttaYtaaagccgc
+tcttagHVggaYtgtNcDaVagctctctKgacgtatagYcctRYHDtgBattDaaDgccK
+tcHaaStttMcctagtattgcRgWBaVatHaaaataYtgtttagMDMRtaataaggatMt
+ttctWgtNtgtgaaaaMaatatRtttMtDgHHtgtcattttcWattRSHcVagaagtacg
+ggtaKVattKYagactNaatgtttgKMMgYNtcccgSKttctaStatatNVataYHgtNa
+BKRgNacaactgatttcctttaNcgatttctctataScaHtataRagtcRVttacDSDtt
+aRtSatacHgtSKacYagttMHtWataggatgactNtatSaNctataVtttRNKtgRacc
+tttYtatgttactttttcctttaaacatacaHactMacacggtWataMtBVacRaSaatc
+cgtaBVttccagccBcttaRKtgtgcctttttRtgtcagcRttKtaaacKtaaatctcac
+aattgcaNtSBaaccgggttattaaBcKatDagttactcttcattVtttHaaggctKKga
+tacatcBggScagtVcacattttgaHaDSgHatRMaHWggtatatRgccDttcgtatcga
+aacaHtaagttaRatgaVacttagattVKtaaYttaaatcaNatccRttRRaMScNaaaD
+gttVHWgtcHaaHgacVaWtgttScactaagSgttatcttagggDtaccagWattWtRtg
+ttHWHacgattBtgVcaYatcggttgagKcWtKKcaVtgaYgWctgYggVctgtHgaNcV
+taBtWaaYatcDRaaRtSctgaHaYRttagatMatgcatttNattaDttaattgttctaa
+ccctcccctagaWBtttHtBccttagaVaatMcBHagaVcWcagBVttcBtaYMccagat
+gaaaaHctctaacgttagNWRtcggattNatcRaNHttcagtKttttgWatWttcSaNgg
+gaWtactKKMaacatKatacNattgctWtatctaVgagctatgtRaHtYcWcttagccaa
+tYttWttaWSSttaHcaaaaagVacVgtaVaRMgattaVcDactttcHHggHRtgNcctt
+tYatcatKgctcctctatVcaaaaKaaaagtatatctgMtWtaaaacaStttMtcgactt
+taSatcgDataaactaaacaagtaaVctaggaSccaatMVtaaSKNVattttgHccatca
+cBVctgcaVatVttRtactgtVcaattHgtaaattaaattttYtatattaaRSgYtgBag
+aHSBDgtagcacRHtYcBgtcacttacactaYcgctWtattgSHtSatcataaatataHt
+cgtYaaMNgBaatttaRgaMaatatttBtttaaaHHKaatctgatWatYaacttMctctt
+ttVctagctDaaagtaVaKaKRtaacBgtatccaaccactHHaagaagaaggaNaaatBW
+attccgStaMSaMatBttgcatgRSacgttVVtaaDMtcSgVatWcaSatcttttVatag
+ttactttacgatcaccNtaDVgSRcgVcgtgaacgaNtaNatatagtHtMgtHcMtagaa
+attBgtataRaaaacaYKgtRccYtatgaagtaataKgtaaMttgaaRVatgcagaKStc
+tHNaaatctBBtcttaYaBWHgtVtgacagcaRcataWctcaBcYacYgatDgtDHccta
+>THREE Homo sapiens frequency
+aacacttcaccaggtatcgtgaaggctcaagattacccagagaacctttgcaatataaga
+atatgtatgcagcattaccctaagtaattatattctttttctgactcaaagtgacaagcc
+ctagtgtatattaaatcggtatatttgggaaattcctcaaactatcctaatcaggtagcc
+atgaaagtgatcaaaaaagttcgtacttataccatacatgaattctggccaagtaaaaaa
+tagattgcgcaaaattcgtaccttaagtctctcgccaagatattaggatcctattactca
+tatcgtgtttttctttattgccgccatccccggagtatctcacccatccttctcttaaag
+gcctaatattacctatgcaaataaacatatattgttgaaaattgagaacctgatcgtgat
+tcttatgtgtaccatatgtatagtaatcacgcgactatatagtgctttagtatcgcccgt
+gggtgagtgaatattctgggctagcgtgagatagtttcttgtcctaatatttttcagatc
+gaatagcttctatttttgtgtttattgacatatgtcgaaactccttactcagtgaaagtc
+atgaccagatccacgaacaatcttcggaatcagtctcgttttacggcggaatcttgagtc
+taacttatatcccgtcgcttactttctaacaccccttatgtatttttaaaattacgttta
+ttcgaacgtacttggcggaagcgttattttttgaagtaagttacattgggcagactcttg
+acattttcgatacgactttctttcatccatcacaggactcgttcgtattgatatcagaag
+ctcgtgatgattagttgtcttctttaccaatactttgaggcctattctgcgaaatttttg
+ttgccctgcgaacttcacataccaaggaacacctcgcaacatgccttcatatccatcgtt
+cattgtaattcttacacaatgaatcctaagtaattacatccctgcgtaaaagatggtagg
+ggcactgaggatatattaccaagcatttagttatgagtaatcagcaatgtttcttgtatt
+aagttctctaaaatagttacatcgtaatgttatctcgggttccgcgaataaacgagatag
+attcattatatatggccctaagcaaaaacctcctcgtattctgttggtaattagaatcac
+acaatacgggttgagatattaattatttgtagtacgaagagatataaaaagatgaacaat
+tactcaagtcaagatgtatacgggatttataataaaaatcgggtagagatctgctttgca
+attcagacgtgccactaaatcgtaatatgtcgcgttacatcagaaagggtaactattatt
+aattaataaagggcttaatcactacatattagatcttatccgatagtcttatctattcgt
+tgtatttttaagcggttctaattcagtcattatatcagtgctccgagttctttattattg
+ttttaaggatgacaaaatgcctcttgttataacgctgggagaagcagactaagagtcgga
+gcagttggtagaatgaggctgcaaaagacggtctcgacgaatggacagactttactaaac
+caatgaaagacagaagtagagcaaagtctgaagtggtatcagcttaattatgacaaccct
+taatacttccctttcgccgaatactggcgtggaaaggttttaaaagtcgaagtagttaga
+ggcatctctcgctcataaataggtagactactcgcaatccaatgtgactatgtaatactg
+ggaacatcagtccgcgatgcagcgtgtttatcaaccgtccccactcgcctggggagacat
+gagaccacccccgtggggattattagtccgcagtaatcgactcttgacaatccttttcga
+ttatgtcatagcaatttacgacagttcagcgaagtgactactcggcgaaatggtattact
+aaagcattcgaacccacatgaatgtgattcttggcaatttctaatccactaaagcttttc
+cgttgaatctggttgtagatatttatataagttcactaattaagatcacggtagtatatt
+gatagtgatgtctttgcaagaggttggccgaggaatttacggattctctattgatacaat
+ttgtctggcttataactcttaaggctgaaccaggcgtttttagacgacttgatcagctgt
+tagaatggtttggactccctctttcatgtcagtaacatttcagccgttattgttacgata
+tgcttgaacaatattgatctaccacacacccatagtatattttataggtcatgctgttac
+ctacgagcatggtattccacttcccattcaatgagtattcaacatcactagcctcagaga
+tgatgacccacctctaataacgtcacgttgcggccatgtgaaacctgaacttgagtagac
+gatatcaagcgctttaaattgcatataacatttgagggtaaagctaagcggatgctttat
+ataatcaatactcaataataagatttgattgcattttagagttatgacacgacatagttc
+actaacgagttactattcccagatctagactgaagtactgatcgagacgatccttacgtc
+gatgatcgttagttatcgacttaggtcgggtctctagcggtattggtacttaaccggaca
+ctatactaataacccatgatcaaagcataacagaatacagacgataatttcgccaacata
+tatgtacagaccccaagcatgagaagctcattgaaagctatcattgaagtcccgctcaca
+atgtgtcttttccagacggtttaactggttcccgggagtcctggagtttcgacttacata
+aatggaaacaatgtattttgctaatttatctatagcgtcatttggaccaatacagaatat
+tatgttgcctagtaatccactataacccgcaagtgctgatagaaaatttttagacgattt
+ataaatgccccaagtatccctcccgtgaatcctccgttatactaattagtattcgttcat
+acgtataccgcgcatatatgaacatttggcgataaggcgcgtgaattgttacgtgacaga
+gatagcagtttcttgtgatatggttaacagacgtacatgaagggaaactttatatctata
+gtgatgcttccgtagaaataccgccactggtctgccaatgatgaagtatgtagctttagg
+tttgtactatgaggctttcgtttgtttgcagagtataacagttgcgagtgaaaaaccgac
+gaatttatactaatacgctttcactattggctacaaaatagggaagagtttcaatcatga
+gagggagtatatggatgctttgtagctaaaggtagaacgtatgtatatgctgccgttcat
+tcttgaaagatacataagcgataagttacgacaattataagcaacatccctaccttcgta
+acgatttcactgttactgcgcttgaaatacactatggggctattggcggagagaagcaga
+tcgcgccgagcatatacgagacctataatgttgatgatagagaaggcgtctgaattgata
+catcgaagtacactttctttcgtagtatctctcgtcctctttctatctccggacacaaga
+attaagttatatatatagagtcttaccaatcatgttgaatcctgattctcagagttcttt
+ggcgggccttgtgatgactgagaaacaatgcaatattgctccaaatttcctaagcaaatt
+ctcggttatgttatgttatcagcaaagcgttacgttatgttatttaaatctggaatgacg
+gagcgaagttcttatgtcggtgtgggaataattcttttgaagacagcactccttaaataa
+tatcgctccgtgtttgtatttatcgaatgggtctgtaaccttgcacaagcaaatcggtgg
+tgtatatatcggataacaattaatacgatgttcatagtgacagtatactgatcgagtcct
+ctaaagtcaattacctcacttaacaatctcattgatgttgtgtcattcccggtatcgccc
+gtagtatgtgctctgattgaccgagtgtgaaccaaggaacatctactaatgcctttgtta
+ggtaagatctctctgaattccttcgtgccaacttaaaacattatcaaaatttcttctact
+tggattaactacttttacgagcatggcaaattcccctgtggaagacggttcattattatc
+ggaaaccttatagaaattgcgtgttgactgaaattagatttttattgtaagagttgcatc
+tttgcgattcctctggtctagcttccaatgaacagtcctcccttctattcgacatcgggt
+ccttcgtacatgtctttgcgatgtaataattaggttcggagtgtggccttaatgggtgca
+actaggaatacaacgcaaatttgctgacatgatagcaaatcggtatgccggcaccaaaac
+gtgctccttgcttagcttgtgaatgagactcagtagttaaataaatccatatctgcaatc
+gattccacaggtattgtccactatctttgaactactctaagagatacaagcttagctgag
+accgaggtgtatatgactacgctgatatctgtaaggtaccaatgcaggcaaagtatgcga
+gaagctaataccggctgtttccagctttataagattaaaatttggctgtcctggcggcct
+cagaattgttctatcgtaatcagttggttcattaattagctaagtacgaggtacaactta
+tctgtcccagaacagctccacaagtttttttacagccgaaacccctgtgtgaatcttaat
+atccaagcgcgttatctgattagagtttacaactcagtattttatcagtacgttttgttt
+ccaacattacccggtatgacaaaatgacgccacgtgtcgaataatggtctgaccaatgta
+ggaagtgaaaagataaatat
diff --git a/shootout/k-nucleotide-parallel.go b/shootout/k-nucleotide-parallel.go
new file mode 100644
index 0000000..75e2c8f
--- /dev/null
+++ b/shootout/k-nucleotide-parallel.go
@@ -0,0 +1,160 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "sort"
+)
+
+func count(data string, n int) map[string]int {
+ counts := make(map[string]int)
+ top := len(data) - n
+ for i := 0; i <= top; i++ {
+ s := data[i : i+n]
+ counts[s]++
+ }
+ return counts
+}
+
+func countOne(data string, s string) int {
+ return count(data, len(s))[s]
+}
+
+type kNuc struct {
+ name string
+ count int
+}
+
+type kNucArray []kNuc
+
+func (kn kNucArray) Len() int { return len(kn) }
+func (kn kNucArray) Swap(i, j int) { kn[i], kn[j] = kn[j], kn[i] }
+func (kn kNucArray) Less(i, j int) bool {
+ if kn[i].count == kn[j].count {
+ return kn[i].name > kn[j].name // sort down
+ }
+ return kn[i].count > kn[j].count
+}
+
+func sortedArray(m map[string]int) kNucArray {
+ kn := make(kNucArray, len(m))
+ i := 0
+ for k, v := range m {
+ kn[i] = kNuc{k, v}
+ i++
+ }
+ sort.Sort(kn)
+ return kn
+}
+
+func printKnucs(a kNucArray) {
+ sum := 0
+ for _, kn := range a {
+ sum += kn.count
+ }
+ for _, kn := range a {
+ fmt.Printf("%s %.3f\n", kn.name, 100*float64(kn.count)/float64(sum))
+ }
+ fmt.Print("\n")
+}
+
+func main() {
+ runtime.GOMAXPROCS(4)
+ in := bufio.NewReader(os.Stdin)
+ three := []byte(">THREE ")
+ for {
+ line, err := in.ReadSlice('\n')
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "ReadLine err:", err)
+ os.Exit(2)
+ }
+ if line[0] == '>' && bytes.Equal(line[0:len(three)], three) {
+ break
+ }
+ }
+ data, err := io.ReadAll(in)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "ReadAll err:", err)
+ os.Exit(2)
+ }
+ // delete the newlines and convert to upper case
+ j := 0
+ for i := 0; i < len(data); i++ {
+ if data[i] != '\n' {
+ data[j] = data[i] &^ ' ' // upper case
+ j++
+ }
+ }
+ str := string(data[0:j])
+
+ var arr1, arr2 kNucArray
+ countsdone := make(chan bool)
+ go func() {
+ arr1 = sortedArray(count(str, 1))
+ countsdone <- true
+ }()
+ go func() {
+ arr2 = sortedArray(count(str, 2))
+ countsdone <- true
+ }()
+
+ interests := []string{"GGT", "GGTA", "GGTATT", "GGTATTTTAATT", "GGTATTTTAATTTATAGT"}
+ results := make([]chan string, len(interests))
+ for i, s := range interests {
+ ch := make(chan string)
+ results[i] = ch
+ go func(result chan string, ss string) {
+ result <- fmt.Sprintf("%d %s\n", countOne(str, ss), ss)
+ }(ch, s)
+ }
+ <-countsdone
+ <-countsdone
+ printKnucs(arr1)
+ printKnucs(arr2)
+ for _, rc := range results {
+ fmt.Print(<-rc)
+ }
+
+}
diff --git a/shootout/k-nucleotide-parallel.txt b/shootout/k-nucleotide-parallel.txt
new file mode 100644
index 0000000..84169b8
--- /dev/null
+++ b/shootout/k-nucleotide-parallel.txt
@@ -0,0 +1,27 @@
+T 31.520
+A 29.600
+C 19.480
+G 19.400
+
+AT 9.922
+TT 9.602
+TA 9.402
+AA 8.402
+GA 6.321
+TC 6.301
+TG 6.201
+GT 6.041
+CT 5.961
+AG 5.841
+CA 5.461
+AC 5.441
+CC 4.041
+CG 4.021
+GC 3.701
+GG 3.341
+
+54 GGT
+24 GGTA
+4 GGTATT
+0 GGTATTTTAATT
+0 GGTATTTTAATTTATAGT
diff --git a/shootout/k-nucleotide.c b/shootout/k-nucleotide.c
new file mode 100644
index 0000000..6f54dce
--- /dev/null
+++ b/shootout/k-nucleotide.c
@@ -0,0 +1,230 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <glib.h>
+
+typedef struct stat_s stat_t;
+struct stat_s
+{
+ const gchar *key;
+ long stat;
+};
+
+#define MAX_ELM (8192 / sizeof (stat_t))
+
+static int
+generate_frequencies (int fl, char *buffer, long buflen,
+ GHashTable *ht, GTrashStack **ts, GPtrArray *roots, GStringChunk *sc)
+{
+ gchar *key;
+ long i;
+
+ if (fl > buflen) return 0;
+ if (fl == 0) return 0;
+
+ for (i = 0; i < buflen - fl + 1; ++i)
+ {
+ char nulled;
+ stat_t *stat;
+
+ nulled = buffer[i + fl];
+ buffer[i + fl] = '\0';
+
+ key = g_string_chunk_insert_const(sc, buffer + i);
+
+ stat = g_hash_table_lookup(ht, key);
+ if (!stat)
+ {
+ stat = g_trash_stack_pop(ts);
+ if (!stat)
+ {
+ int j;
+
+ stat = malloc(sizeof (stat_t) * MAX_ELM);
+ g_ptr_array_add(roots, stat);
+
+ for (j = 1; j < MAX_ELM; ++j)
+ g_trash_stack_push(ts, stat + j);
+ }
+ stat->stat = 1;
+ stat->key = key;
+
+ g_hash_table_insert(ht, key, stat);
+ }
+ else
+ stat->stat++;
+
+ buffer[i + fl] = nulled;
+ }
+
+ return buflen - fl + 1;
+}
+
+static int
+cmp_func(gconstpointer a, gconstpointer b)
+{
+ const stat_t *left = a;
+ const stat_t *right = b;
+
+ return right->stat - left->stat;
+}
+
+static void
+sorted_list(gpointer key, gpointer value, gpointer user_data)
+{
+ stat_t *data = value;
+ GList **lst = user_data;
+
+ *lst = g_list_insert_sorted(*lst, data, cmp_func);
+}
+
+static void
+display_stat(gpointer data, gpointer user_data)
+{
+ long *total = user_data;
+ stat_t *st = data;
+
+ printf("%s %.3f\n", st->key, 100 * (float) st->stat / *total);
+}
+
+void
+write_frequencies (int fl, char *buffer, long buflen, GTrashStack **ts, GPtrArray *roots)
+{
+ GStringChunk *sc;
+ GHashTable *ht;
+ GList *lst;
+ long total;
+
+ ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL /* free key */, NULL /* free value */);
+ sc = g_string_chunk_new(buflen);
+ lst = NULL;
+
+ total = generate_frequencies (fl, buffer, buflen, ht, ts, roots, sc);
+
+ if (!total) goto on_error;
+
+ g_hash_table_foreach(ht, sorted_list, &lst);
+ g_list_foreach(lst, display_stat, &total);
+ g_list_free(lst);
+
+ on_error:
+ g_hash_table_destroy(ht);
+ g_string_chunk_free(sc);
+}
+
+void
+write_count (char *searchFor, char *buffer, long buflen, GTrashStack **ts, GPtrArray *roots)
+{
+ GStringChunk *sc;
+ GHashTable *ht;
+ stat_t *result;
+ GList *lst;
+ long total;
+ long fl;
+
+ fl = strlen(searchFor);
+
+ ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL /* free key */, NULL /* free value */);
+ sc = g_string_chunk_new(buflen);
+ lst = NULL;
+ result = NULL;
+
+ total = generate_frequencies (fl, buffer, buflen, ht, ts, roots, sc);
+
+ if (!total) goto on_error;
+
+ result = g_hash_table_lookup(ht, searchFor);
+
+ on_error:
+ printf("%ld\t%s\n", result ? result->stat : 0, searchFor);
+
+ g_hash_table_destroy(ht);
+ g_string_chunk_free(sc);
+}
+
+int
+main ()
+{
+ char buffer[4096];
+ GTrashStack *ts;
+ GPtrArray *roots;
+ GString *stuff;
+ gchar *s;
+ int len;
+
+ roots = g_ptr_array_new();
+ ts = NULL;
+
+ while (fgets(buffer, sizeof (buffer), stdin))
+ if (strncmp(buffer, ">THREE", 6) == 0)
+ break;
+
+ stuff = g_string_new(NULL);
+
+ while (fgets(buffer, sizeof (buffer), stdin))
+ {
+ size_t sz;
+
+ if (buffer[0] == '>')
+ break;
+
+ sz = strlen(buffer);
+ if (buffer[sz - 1] == '\n')
+ --sz;
+
+ stuff = g_string_append_len(stuff, buffer, sz);
+ }
+
+ stuff = g_string_ascii_up(stuff);
+ len = stuff->len;
+ s = g_string_free(stuff, FALSE);
+
+ write_frequencies(1, s, len, &ts, roots);
+ printf("\n");
+ write_frequencies(2, s, len, &ts, roots);
+ printf("\n");
+ write_count("GGT", s, len, &ts, roots);
+ write_count("GGTA", s, len, &ts, roots);
+ write_count("GGTATT", s, len, &ts, roots);
+ write_count("GGTATTTTAATT", s, len, &ts, roots);
+ write_count("GGTATTTTAATTTATAGT", s, len, &ts, roots);
+
+ free(s);
+
+ g_ptr_array_foreach(roots, (GFunc)free, NULL);
+ g_ptr_array_free(roots, TRUE);
+
+ return 0;
+}
diff --git a/shootout/k-nucleotide.go b/shootout/k-nucleotide.go
new file mode 100644
index 0000000..6fc535d
--- /dev/null
+++ b/shootout/k-nucleotide.go
@@ -0,0 +1,143 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "sort"
+)
+
+var in *bufio.Reader
+
+func count(data string, n int) map[string]int {
+ counts := make(map[string]int)
+ top := len(data) - n
+ for i := 0; i <= top; i++ {
+ s := data[i : i+n]
+ counts[s]++
+ }
+ return counts
+}
+
+func countOne(data string, s string) int {
+ return count(data, len(s))[s]
+}
+
+type kNuc struct {
+ name string
+ count int
+}
+
+type kNucArray []kNuc
+
+func (kn kNucArray) Len() int { return len(kn) }
+func (kn kNucArray) Swap(i, j int) { kn[i], kn[j] = kn[j], kn[i] }
+func (kn kNucArray) Less(i, j int) bool {
+ if kn[i].count == kn[j].count {
+ return kn[i].name > kn[j].name // sort down
+ }
+ return kn[i].count > kn[j].count
+}
+
+func sortedArray(m map[string]int) kNucArray {
+ kn := make(kNucArray, len(m))
+ i := 0
+ for k, v := range m {
+ kn[i].name = k
+ kn[i].count = v
+ i++
+ }
+ sort.Sort(kn)
+ return kn
+}
+
+func print(m map[string]int) {
+ a := sortedArray(m)
+ sum := 0
+ for _, kn := range a {
+ sum += kn.count
+ }
+ for _, kn := range a {
+ fmt.Printf("%s %.3f\n", kn.name, 100*float64(kn.count)/float64(sum))
+ }
+}
+
+func main() {
+ in = bufio.NewReader(os.Stdin)
+ three := []byte(">THREE ")
+ for {
+ line, err := in.ReadSlice('\n')
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "ReadLine err:", err)
+ os.Exit(2)
+ }
+ if line[0] == '>' && bytes.Equal(line[0:len(three)], three) {
+ break
+ }
+ }
+ data, err := io.ReadAll(in)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "ReadAll err:", err)
+ os.Exit(2)
+ }
+ // delete the newlines and convert to upper case
+ j := 0
+ for i := 0; i < len(data); i++ {
+ if data[i] != '\n' {
+ data[j] = data[i] &^ ' ' // upper case
+ j++
+ }
+ }
+ str := string(data[0:j])
+
+ print(count(str, 1))
+ fmt.Print("\n")
+
+ print(count(str, 2))
+ fmt.Print("\n")
+
+ interests := []string{"GGT", "GGTA", "GGTATT", "GGTATTTTAATT", "GGTATTTTAATTTATAGT"}
+ for _, s := range interests {
+ fmt.Printf("%d %s\n", countOne(str, s), s)
+ }
+}
diff --git a/shootout/k-nucleotide.txt b/shootout/k-nucleotide.txt
new file mode 100644
index 0000000..84169b8
--- /dev/null
+++ b/shootout/k-nucleotide.txt
@@ -0,0 +1,27 @@
+T 31.520
+A 29.600
+C 19.480
+G 19.400
+
+AT 9.922
+TT 9.602
+TA 9.402
+AA 8.402
+GA 6.321
+TC 6.301
+TG 6.201
+GT 6.041
+CT 5.961
+AG 5.841
+CA 5.461
+AC 5.441
+CC 4.041
+CG 4.021
+GC 3.701
+GG 3.341
+
+54 GGT
+24 GGTA
+4 GGTATT
+0 GGTATTTTAATT
+0 GGTATTTTAATTTATAGT
diff --git a/shootout/mandelbrot.c b/shootout/mandelbrot.c
new file mode 100644
index 0000000..9fac37a
--- /dev/null
+++ b/shootout/mandelbrot.c
@@ -0,0 +1,93 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Shootout
+ http://shootout.alioth.debian.org/
+
+ contributed by Greg Buchholz
+
+ for the debian (AMD) machine...
+ compile flags: -O3 -ffast-math -march=athlon-xp -funroll-loops
+
+ for the gp4 (Intel) machine...
+ compile flags: -O3 -ffast-math -march=pentium4 -funroll-loops
+*/
+
+#include<stdio.h>
+
+int main (int argc, char **argv)
+{
+ int w, h, bit_num = 0;
+ char byte_acc = 0;
+ int i, iter = 50;
+ double x, y, limit = 2.0;
+ double Zr, Zi, Cr, Ci, Tr, Ti;
+
+ w = h = atoi(argv[1]);
+
+ printf("P4\n%d %d\n",w,h);
+
+ for(y=0;y<h;++y)
+ {
+ for(x=0;x<w;++x)
+ {
+ Zr = Zi = Tr = Ti = 0.0;
+ Cr = (2.0*x/w - 1.5); Ci=(2.0*y/h - 1.0);
+
+ for (i=0;i<iter && (Tr+Ti <= limit*limit);++i)
+ {
+ Zi = 2.0*Zr*Zi + Ci;
+ Zr = Tr - Ti + Cr;
+ Tr = Zr * Zr;
+ Ti = Zi * Zi;
+ }
+
+ byte_acc <<= 1;
+ if(Tr+Ti <= limit*limit) byte_acc |= 0x01;
+
+ ++bit_num;
+
+ if(bit_num == 8)
+ {
+ putc(byte_acc,stdout);
+ byte_acc = 0;
+ bit_num = 0;
+ }
+ else if(x == w-1)
+ {
+ byte_acc <<= (8-w%8);
+ putc(byte_acc,stdout);
+ byte_acc = 0;
+ bit_num = 0;
+ }
+ }
+ }
+}
diff --git a/shootout/mandelbrot.go b/shootout/mandelbrot.go
new file mode 100644
index 0000000..2b82a2f
--- /dev/null
+++ b/shootout/mandelbrot.go
@@ -0,0 +1,98 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * Based on mandelbrot.c contributed by Greg Buchholz
+ */
+
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "os"
+)
+
+var n = flag.Int("n", 200, "size")
+
+func main() {
+ flag.Parse()
+ out := bufio.NewWriter(os.Stdout)
+ defer out.Flush()
+
+ w := float64(*n)
+ h := float64(*n)
+ bit_num := 0
+ byte_acc := byte(0)
+ const Iter = 50
+ const Zero float64 = 0
+ const Limit = 2.0
+
+ fmt.Fprintf(out, "P4\n%d %d\n", *n, *n)
+
+ for y := 0.0; y < h; y++ {
+ for x := 0.0; x < w; x++ {
+ Zr, Zi, Tr, Ti := Zero, Zero, Zero, Zero
+ Cr := (2*x/w - 1.5)
+ Ci := (2*y/h - 1.0)
+
+ for i := 0; i < Iter && (Tr+Ti <= Limit*Limit); i++ {
+ Zi = 2*Zr*Zi + Ci
+ Zr = Tr - Ti + Cr
+ Tr = Zr * Zr
+ Ti = Zi * Zi
+ }
+
+ byte_acc <<= 1
+ if Tr+Ti <= Limit*Limit {
+ byte_acc |= 0x01
+ }
+
+ bit_num++
+
+ if bit_num == 8 {
+ out.WriteByte(byte_acc)
+ byte_acc = 0
+ bit_num = 0
+ } else if x == w-1 {
+ byte_acc <<= uint(8 - uint(*n)%8)
+ out.WriteByte(byte_acc)
+ byte_acc = 0
+ bit_num = 0
+ }
+ }
+ }
+}
diff --git a/shootout/mandelbrot.txt b/shootout/mandelbrot.txt
new file mode 100644
index 0000000..2f7bbbc
--- /dev/null
+++ b/shootout/mandelbrot.txt
Binary files differ
diff --git a/shootout/meteor-contest.c b/shootout/meteor-contest.c
new file mode 100644
index 0000000..dda181b
--- /dev/null
+++ b/shootout/meteor-contest.c
@@ -0,0 +1,628 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by Christian Vosteen
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#define TRUE 1
+#define FALSE 0
+
+/* The board is a 50 cell hexagonal pattern. For . . . . .
+ * maximum speed the board will be implemented as . . . . .
+ * 50 bits, which will fit into a 64 bit long long . . . . .
+ * int. . . . . .
+ * . . . . .
+ * I will represent 0's as empty cells and 1's . . . . .
+ * as full cells. . . . . .
+ * . . . . .
+ * . . . . .
+ * . . . . .
+ */
+
+unsigned long long board = 0xFFFC000000000000ULL;
+
+/* The puzzle pieces must be specified by the path followed
+ * from one end to the other along 12 hexagonal directions.
+ *
+ * Piece 0 Piece 1 Piece 2 Piece 3 Piece 4
+ *
+ * O O O O O O O O O O O O O O O
+ * O O O O O O O
+ * O O O
+ *
+ * Piece 5 Piece 6 Piece 7 Piece 8 Piece 9
+ *
+ * O O O O O O O O O O O O O
+ * O O O O O O O O O
+ * O O O
+ *
+ * I had to make it 12 directions because I wanted all of the
+ * piece definitions to fit into the same size arrays. It is
+ * not possible to define piece 4 in terms of the 6 cardinal
+ * directions in 4 moves.
+ */
+
+#define E 0
+#define ESE 1
+#define SE 2
+#define S 3
+#define SW 4
+#define WSW 5
+#define W 6
+#define WNW 7
+#define NW 8
+#define N 9
+#define NE 10
+#define ENE 11
+#define PIVOT 12
+
+char piece_def[10][4] = {
+ { E, E, E, SE},
+ { SE, E, NE, E},
+ { E, E, SE, SW},
+ { E, E, SW, SE},
+ { SE, E, NE, S},
+ { E, E, SW, E},
+ { E, SE, SE, NE},
+ { E, SE, SE, W},
+ { E, SE, E, E},
+ { E, E, E, SW}
+};
+
+
+/* To minimize the amount of work done in the recursive solve function below,
+ * I'm going to allocate enough space for all legal rotations of each piece
+ * at each position on the board. That's 10 pieces x 50 board positions x
+ * 12 rotations. However, not all 12 rotations will fit on every cell, so
+ * I'll have to keep count of the actual number that do.
+ * The pieces are going to be unsigned long long ints just like the board so
+ * they can be bitwise-anded with the board to determine if they fit.
+ * I'm also going to record the next possible open cell for each piece and
+ * location to reduce the burden on the solve function.
+ */
+unsigned long long pieces[10][50][12];
+int piece_counts[10][50];
+char next_cell[10][50][12];
+
+/* Returns the direction rotated 60 degrees clockwise */
+char rotate(char dir) {
+ return (dir + 2) % PIVOT;
+}
+
+/* Returns the direction flipped on the horizontal axis */
+char flip(char dir) {
+ return (PIVOT - dir) % PIVOT;
+}
+
+
+/* Returns the new cell index from the specified cell in the
+ * specified direction. The index is only valid if the
+ * starting cell and direction have been checked by the
+ * out_of_bounds function first.
+ */
+char shift(char cell, char dir) {
+ switch(dir) {
+ case E:
+ return cell + 1;
+ case ESE:
+ if((cell / 5) % 2)
+ return cell + 7;
+ else
+ return cell + 6;
+ case SE:
+ if((cell / 5) % 2)
+ return cell + 6;
+ else
+ return cell + 5;
+ case S:
+ return cell + 10;
+ case SW:
+ if((cell / 5) % 2)
+ return cell + 5;
+ else
+ return cell + 4;
+ case WSW:
+ if((cell / 5) % 2)
+ return cell + 4;
+ else
+ return cell + 3;
+ case W:
+ return cell - 1;
+ case WNW:
+ if((cell / 5) % 2)
+ return cell - 6;
+ else
+ return cell - 7;
+ case NW:
+ if((cell / 5) % 2)
+ return cell - 5;
+ else
+ return cell - 6;
+ case N:
+ return cell - 10;
+ case NE:
+ if((cell / 5) % 2)
+ return cell - 4;
+ else
+ return cell - 5;
+ case ENE:
+ if((cell / 5) % 2)
+ return cell - 3;
+ else
+ return cell - 4;
+ default:
+ return cell;
+ }
+}
+
+/* Returns wether the specified cell and direction will land outside
+ * of the board. Used to determine if a piece is at a legal board
+ * location or not.
+ */
+char out_of_bounds(char cell, char dir) {
+ char i;
+ switch(dir) {
+ case E:
+ return cell % 5 == 4;
+ case ESE:
+ i = cell % 10;
+ return i == 4 || i == 8 || i == 9 || cell >= 45;
+ case SE:
+ return cell % 10 == 9 || cell >= 45;
+ case S:
+ return cell >= 40;
+ case SW:
+ return cell % 10 == 0 || cell >= 45;
+ case WSW:
+ i = cell % 10;
+ return i == 0 || i == 1 || i == 5 || cell >= 45;
+ case W:
+ return cell % 5 == 0;
+ case WNW:
+ i = cell % 10;
+ return i == 0 || i == 1 || i == 5 || cell < 5;
+ case NW:
+ return cell % 10 == 0 || cell < 5;
+ case N:
+ return cell < 10;
+ case NE:
+ return cell % 10 == 9 || cell < 5;
+ case ENE:
+ i = cell % 10;
+ return i == 4 || i == 8 || i == 9 || cell < 5;
+ default:
+ return FALSE;
+ }
+}
+
+/* Rotate a piece 60 degrees clockwise */
+void rotate_piece(int piece) {
+ int i;
+ for(i = 0; i < 4; i++)
+ piece_def[piece][i] = rotate(piece_def[piece][i]);
+}
+
+/* Flip a piece along the horizontal axis */
+void flip_piece(int piece) {
+ int i;
+ for(i = 0; i < 4; i++)
+ piece_def[piece][i] = flip(piece_def[piece][i]);
+}
+
+/* Convenience function to quickly calculate all of the indices for a piece */
+void calc_cell_indices(char *cell, int piece, char index) {
+ cell[0] = index;
+ cell[1] = shift(cell[0], piece_def[piece][0]);
+ cell[2] = shift(cell[1], piece_def[piece][1]);
+ cell[3] = shift(cell[2], piece_def[piece][2]);
+ cell[4] = shift(cell[3], piece_def[piece][3]);
+}
+
+/* Convenience function to quickly calculate if a piece fits on the board */
+int cells_fit_on_board(char *cell, int piece) {
+ return (!out_of_bounds(cell[0], piece_def[piece][0]) &&
+ !out_of_bounds(cell[1], piece_def[piece][1]) &&
+ !out_of_bounds(cell[2], piece_def[piece][2]) &&
+ !out_of_bounds(cell[3], piece_def[piece][3]));
+}
+
+/* Returns the lowest index of the cells of a piece.
+ * I use the lowest index that a piece occupies as the index for looking up
+ * the piece in the solve function.
+ */
+char minimum_of_cells(char *cell) {
+ char minimum = cell[0];
+ minimum = cell[1] < minimum ? cell[1] : minimum;
+ minimum = cell[2] < minimum ? cell[2] : minimum;
+ minimum = cell[3] < minimum ? cell[3] : minimum;
+ minimum = cell[4] < minimum ? cell[4] : minimum;
+ return minimum;
+}
+
+/* Calculate the lowest possible open cell if the piece is placed on the board.
+ * Used to later reduce the amount of time searching for open cells in the
+ * solve function.
+ */
+char first_empty_cell(char *cell, char minimum) {
+ char first_empty = minimum;
+ while(first_empty == cell[0] || first_empty == cell[1] ||
+ first_empty == cell[2] || first_empty == cell[3] ||
+ first_empty == cell[4])
+ first_empty++;
+ return first_empty;
+}
+
+/* Generate the unsigned long long int that will later be anded with the
+ * board to determine if it fits.
+ */
+unsigned long long bitmask_from_cells(char *cell) {
+ unsigned long long piece_mask = 0ULL;
+ int i;
+ for(i = 0; i < 5; i++)
+ piece_mask |= 1ULL << cell[i];
+ return piece_mask;
+}
+
+/* Record the piece and other important information in arrays that will
+ * later be used by the solve function.
+ */
+void record_piece(int piece, int minimum, char first_empty,
+ unsigned long long piece_mask) {
+ pieces[piece][minimum][piece_counts[piece][minimum]] = piece_mask;
+ next_cell[piece][minimum][piece_counts[piece][minimum]] = first_empty;
+ piece_counts[piece][minimum]++;
+}
+
+
+/* Fill the entire board going cell by cell. If any cells are "trapped"
+ * they will be left alone.
+ */
+void fill_contiguous_space(char *board, int index) {
+ if(board[index] == 1)
+ return;
+ board[index] = 1;
+ if(!out_of_bounds(index, E))
+ fill_contiguous_space(board, shift(index, E));
+ if(!out_of_bounds(index, SE))
+ fill_contiguous_space(board, shift(index, SE));
+ if(!out_of_bounds(index, SW))
+ fill_contiguous_space(board, shift(index, SW));
+ if(!out_of_bounds(index, W))
+ fill_contiguous_space(board, shift(index, W));
+ if(!out_of_bounds(index, NW))
+ fill_contiguous_space(board, shift(index, NW));
+ if(!out_of_bounds(index, NE))
+ fill_contiguous_space(board, shift(index, NE));
+}
+
+
+/* To thin the number of pieces, I calculate if any of them trap any empty
+ * cells at the edges. There are only a handful of exceptions where the
+ * the board can be solved with the trapped cells. For example: piece 8 can
+ * trap 5 cells in the corner, but piece 3 can fit in those cells, or piece 0
+ * can split the board in half where both halves are viable.
+ */
+int has_island(char *cell, int piece) {
+ char temp_board[50];
+ char c;
+ int i;
+ for(i = 0; i < 50; i++)
+ temp_board[i] = 0;
+ for(i = 0; i < 5; i++)
+ temp_board[((int)cell[i])] = 1;
+ i = 49;
+ while(temp_board[i] == 1)
+ i--;
+ fill_contiguous_space(temp_board, i);
+ c = 0;
+ for(i = 0; i < 50; i++)
+ if(temp_board[i] == 0)
+ c++;
+ if(c == 0 || (c == 5 && piece == 8) || (c == 40 && piece == 8) ||
+ (c % 5 == 0 && piece == 0))
+ return FALSE;
+ else
+ return TRUE;
+}
+
+
+/* Calculate all six rotations of the specified piece at the specified index.
+ * We calculate only half of piece 3's rotations. This is because any solution
+ * found has an identical solution rotated 180 degrees. Thus we can reduce the
+ * number of attempted pieces in the solve algorithm by not including the 180-
+ * degree-rotated pieces of ONE of the pieces. I chose piece 3 because it gave
+ * me the best time ;)
+ */
+ void calc_six_rotations(char piece, char index) {
+ char rotation, cell[5];
+ char minimum, first_empty;
+ unsigned long long piece_mask;
+
+ for(rotation = 0; rotation < 6; rotation++) {
+ if(piece != 3 || rotation < 3) {
+ calc_cell_indices(cell, piece, index);
+ if(cells_fit_on_board(cell, piece) && !has_island(cell, piece)) {
+ minimum = minimum_of_cells(cell);
+ first_empty = first_empty_cell(cell, minimum);
+ piece_mask = bitmask_from_cells(cell);
+ record_piece(piece, minimum, first_empty, piece_mask);
+ }
+ }
+ rotate_piece(piece);
+ }
+}
+
+/* Calculate every legal rotation for each piece at each board location. */
+void calc_pieces(void) {
+ char piece, index;
+
+ for(piece = 0; piece < 10; piece++) {
+ for(index = 0; index < 50; index++) {
+ calc_six_rotations(piece, index);
+ flip_piece(piece);
+ calc_six_rotations(piece, index);
+ }
+ }
+}
+
+
+
+/* Calculate all 32 possible states for a 5-bit row and all rows that will
+ * create islands that follow any of the 32 possible rows. These pre-
+ * calculated 5-bit rows will be used to find islands in a partially solved
+ * board in the solve function.
+ */
+#define ROW_MASK 0x1F
+#define TRIPLE_MASK 0x7FFF
+char all_rows[32] = {0, 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, 28, 29, 30, 31};
+int bad_even_rows[32][32];
+int bad_odd_rows[32][32];
+int bad_even_triple[32768];
+int bad_odd_triple[32768];
+
+int rows_bad(char row1, char row2, int even) {
+ /* even is referring to row1 */
+ int i, in_zeroes, group_okay;
+ char block, row2_shift;
+ /* Test for blockages at same index and shifted index */
+ if(even)
+ row2_shift = ((row2 << 1) & ROW_MASK) | 0x01;
+ else
+ row2_shift = (row2 >> 1) | 0x10;
+ block = ((row1 ^ row2) & row2) & ((row1 ^ row2_shift) & row2_shift);
+ /* Test for groups of 0's */
+ in_zeroes = FALSE;
+ group_okay = FALSE;
+ for(i = 0; i < 5; i++) {
+ if(row1 & (1 << i)) {
+ if(in_zeroes) {
+ if(!group_okay)
+ return TRUE;
+ in_zeroes = FALSE;
+ group_okay = FALSE;
+ }
+ } else {
+ if(!in_zeroes)
+ in_zeroes = TRUE;
+ if(!(block & (1 << i)))
+ group_okay = TRUE;
+ }
+ }
+ if(in_zeroes)
+ return !group_okay;
+ else
+ return FALSE;
+}
+
+/* Check for cases where three rows checked sequentially cause a false
+ * positive. One scenario is when 5 cells may be surrounded where piece 5
+ * or 7 can fit. The other scenario is when piece 2 creates a hook shape.
+ */
+int triple_is_okay(char row1, char row2, char row3, int even) {
+ if(even) {
+ /* There are four cases:
+ * row1: 00011 00001 11001 10101
+ * row2: 01011 00101 10001 10001
+ * row3: 011?? 00110 ????? ?????
+ */
+ return ((row1 == 0x03) && (row2 == 0x0B) && ((row3 & 0x1C) == 0x0C)) ||
+ ((row1 == 0x01) && (row2 == 0x05) && (row3 == 0x06)) ||
+ ((row1 == 0x19) && (row2 == 0x11)) ||
+ ((row1 == 0x15) && (row2 == 0x11));
+ } else {
+ /* There are two cases:
+ * row1: 10011 10101
+ * row2: 10001 10001
+ * row3: ????? ?????
+ */
+ return ((row1 == 0x13) && (row2 == 0x11)) ||
+ ((row1 == 0x15) && (row2 == 0x11));
+ }
+}
+
+
+void calc_rows(void) {
+ int row1, row2, row3;
+ int result1, result2;
+ for(row1 = 0; row1 < 32; row1++) {
+ for(row2 = 0; row2 < 32; row2++) {
+ bad_even_rows[row1][row2] = rows_bad(row1, row2, TRUE);
+ bad_odd_rows[row1][row2] = rows_bad(row1, row2, FALSE);
+ }
+ }
+ for(row1 = 0; row1 < 32; row1++) {
+ for(row2 = 0; row2 < 32; row2++) {
+ for(row3 = 0; row3 < 32; row3++) {
+ result1 = bad_even_rows[row1][row2];
+ result2 = bad_odd_rows[row2][row3];
+ if(result1 == FALSE && result2 == TRUE
+ && triple_is_okay(row1, row2, row3, TRUE))
+ bad_even_triple[row1+(row2*32)+(row3*1024)] = FALSE;
+ else
+ bad_even_triple[row1+(row2*32)+(row3*1024)] = result1 || result2;
+
+ result1 = bad_odd_rows[row1][row2];
+ result2 = bad_even_rows[row2][row3];
+ if(result1 == FALSE && result2 == TRUE
+ && triple_is_okay(row1, row2, row3, FALSE))
+ bad_odd_triple[row1+(row2*32)+(row3*1024)] = FALSE;
+ else
+ bad_odd_triple[row1+(row2*32)+(row3*1024)] = result1 || result2;
+ }
+ }
+ }
+}
+
+
+
+/* Calculate islands while solving the board.
+ */
+int boardHasIslands(char cell) {
+ /* Too low on board, don't bother checking */
+ if(cell >= 40)
+ return FALSE;
+ int current_triple = (board >> ((cell / 5) * 5)) & TRIPLE_MASK;
+ if((cell / 5) % 2)
+ return bad_odd_triple[current_triple];
+ else
+ return bad_even_triple[current_triple];
+}
+
+
+/* The recursive solve algorithm. Try to place each permutation in the upper-
+ * leftmost empty cell. Mark off available pieces as it goes along.
+ * Because the board is a bit mask, the piece number and bit mask must be saved
+ * at each successful piece placement. This data is used to create a 50 char
+ * array if a solution is found.
+ */
+short avail = 0x03FF;
+char sol_nums[10];
+unsigned long long sol_masks[10];
+signed char solutions[2100][50];
+int solution_count = 0;
+int max_solutions = 2100;
+
+void record_solution(void) {
+ int sol_no, index;
+ unsigned long long sol_mask;
+ for(sol_no = 0; sol_no < 10; sol_no++) {
+ sol_mask = sol_masks[sol_no];
+ for(index = 0; index < 50; index++) {
+ if(sol_mask & 1ULL) {
+ solutions[solution_count][index] = sol_nums[sol_no];
+ /* Board rotated 180 degrees is a solution too! */
+ solutions[solution_count+1][49-index] = sol_nums[sol_no];
+ }
+ sol_mask = sol_mask >> 1;
+ }
+ }
+ solution_count += 2;
+}
+
+void solve(int depth, int cell) {
+ int piece, rotation, max_rots;
+ unsigned long long *piece_mask;
+ short piece_no_mask;
+
+ if(solution_count >= max_solutions)
+ return;
+
+ while(board & (1ULL << cell))
+ cell++;
+
+ for(piece = 0; piece < 10; piece++) {
+ piece_no_mask = 1 << piece;
+ if(!(avail & piece_no_mask))
+ continue;
+ avail ^= piece_no_mask;
+ max_rots = piece_counts[piece][cell];
+ piece_mask = pieces[piece][cell];
+ for(rotation = 0; rotation < max_rots; rotation++) {
+ if(!(board & *(piece_mask + rotation))) {
+ sol_nums[depth] = piece;
+ sol_masks[depth] = *(piece_mask + rotation);
+ if(depth == 9) {
+ /* Solution found!!!!!11!!ONE! */
+ record_solution();
+ avail ^= piece_no_mask;
+ return;
+ }
+ board |= *(piece_mask + rotation);
+ if(!boardHasIslands(next_cell[piece][cell][rotation]))
+ solve(depth + 1, next_cell[piece][cell][rotation]);
+ board ^= *(piece_mask + rotation);
+ }
+ }
+ avail ^= piece_no_mask;
+ }
+}
+
+
+/* qsort comparator - used to find first and last solutions */
+int solution_sort(const void *elem1, const void *elem2) {
+ signed char *char1 = (signed char *) elem1;
+ signed char *char2 = (signed char *) elem2;
+ int i = 0;
+ while(i < 50 && char1[i] == char2[i])
+ i++;
+ return char1[i] - char2[i];
+}
+
+
+/* pretty print a board in the specified hexagonal format */
+void pretty(signed char *b) {
+ int i;
+ for(i = 0; i < 50; i += 10) {
+ printf("%c %c %c %c %c \n %c %c %c %c %c \n", b[i]+'0', b[i+1]+'0',
+ b[i+2]+'0', b[i+3]+'0', b[i+4]+'0', b[i+5]+'0', b[i+6]+'0',
+ b[i+7]+'0', b[i+8]+'0', b[i+9]+'0');
+ }
+ printf("\n");
+}
+
+int main(int argc, char **argv) {
+ if(argc > 1)
+ max_solutions = atoi(argv[1]);
+ calc_pieces();
+ calc_rows();
+ solve(0, 0);
+ printf("%d solutions found\n\n", solution_count);
+ qsort(solutions, solution_count, 50 * sizeof(signed char), solution_sort);
+ pretty(solutions[0]);
+ pretty(solutions[solution_count-1]);
+ return 0;
+}
diff --git a/shootout/meteor-contest.go b/shootout/meteor-contest.go
new file mode 100644
index 0000000..cfe49ec
--- /dev/null
+++ b/shootout/meteor-contest.go
@@ -0,0 +1,659 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * based on meteor-contest.c by Christian Vosteen
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+)
+
+var max_solutions = flag.Int("n", 2100, "maximum number of solutions")
+
+func boolInt(b bool) int8 {
+ if b {
+ return 1
+ }
+ return 0
+}
+
+/* The board is a 50 cell hexagonal pattern. For . . . . .
+ * maximum speed the board will be implemented as . . . . .
+ * 50 bits, which will fit into a 64 bit long long . . . . .
+ * int. . . . . .
+ * . . . . .
+ * I will represent 0's as empty cells and 1's . . . . .
+ * as full cells. . . . . .
+ * . . . . .
+ * . . . . .
+ * . . . . .
+ */
+
+var board uint64 = 0xFFFC000000000000
+
+/* The puzzle pieces must be specified by the path followed
+ * from one end to the other along 12 hexagonal directions.
+ *
+ * Piece 0 Piece 1 Piece 2 Piece 3 Piece 4
+ *
+ * O O O O O O O O O O O O O O O
+ * O O O O O O O
+ * O O O
+ *
+ * Piece 5 Piece 6 Piece 7 Piece 8 Piece 9
+ *
+ * O O O O O O O O O O O O O
+ * O O O O O O O O O
+ * O O O
+ *
+ * I had to make it 12 directions because I wanted all of the
+ * piece definitions to fit into the same size arrays. It is
+ * not possible to define piece 4 in terms of the 6 cardinal
+ * directions in 4 moves.
+ */
+
+const (
+ E = iota
+ ESE
+ SE
+ S
+ SW
+ WSW
+ W
+ WNW
+ NW
+ N
+ NE
+ ENE
+ PIVOT
+)
+
+var piece_def = [10][4]int8{
+ [4]int8{E, E, E, SE},
+ [4]int8{SE, E, NE, E},
+ [4]int8{E, E, SE, SW},
+ [4]int8{E, E, SW, SE},
+ [4]int8{SE, E, NE, S},
+ [4]int8{E, E, SW, E},
+ [4]int8{E, SE, SE, NE},
+ [4]int8{E, SE, SE, W},
+ [4]int8{E, SE, E, E},
+ [4]int8{E, E, E, SW},
+}
+
+/* To minimize the amount of work done in the recursive solve function below,
+ * I'm going to allocate enough space for all legal rotations of each piece
+ * at each position on the board. That's 10 pieces x 50 board positions x
+ * 12 rotations. However, not all 12 rotations will fit on every cell, so
+ * I'll have to keep count of the actual number that do.
+ * The pieces are going to be unsigned long long ints just like the board so
+ * they can be bitwise-anded with the board to determine if they fit.
+ * I'm also going to record the next possible open cell for each piece and
+ * location to reduce the burden on the solve function.
+ */
+var (
+ pieces [10][50][12]uint64
+ piece_counts [10][50]int
+ next_cell [10][50][12]int8
+)
+
+/* Returns the direction rotated 60 degrees clockwise */
+func rotate(dir int8) int8 { return (dir + 2) % PIVOT }
+
+/* Returns the direction flipped on the horizontal axis */
+func flip(dir int8) int8 { return (PIVOT - dir) % PIVOT }
+
+/* Returns the new cell index from the specified cell in the
+ * specified direction. The index is only valid if the
+ * starting cell and direction have been checked by the
+ * out_of_bounds function first.
+ */
+func shift(cell, dir int8) int8 {
+ switch dir {
+ case E:
+ return cell + 1
+ case ESE:
+ if ((cell / 5) % 2) != 0 {
+ return cell + 7
+ } else {
+ return cell + 6
+ }
+ case SE:
+ if ((cell / 5) % 2) != 0 {
+ return cell + 6
+ } else {
+ return cell + 5
+ }
+ case S:
+ return cell + 10
+ case SW:
+ if ((cell / 5) % 2) != 0 {
+ return cell + 5
+ } else {
+ return cell + 4
+ }
+ case WSW:
+ if ((cell / 5) % 2) != 0 {
+ return cell + 4
+ } else {
+ return cell + 3
+ }
+ case W:
+ return cell - 1
+ case WNW:
+ if ((cell / 5) % 2) != 0 {
+ return cell - 6
+ } else {
+ return cell - 7
+ }
+ case NW:
+ if ((cell / 5) % 2) != 0 {
+ return cell - 5
+ } else {
+ return cell - 6
+ }
+ case N:
+ return cell - 10
+ case NE:
+ if ((cell / 5) % 2) != 0 {
+ return cell - 4
+ } else {
+ return cell - 5
+ }
+ case ENE:
+ if ((cell / 5) % 2) != 0 {
+ return cell - 3
+ } else {
+ return cell - 4
+ }
+ }
+ return cell
+}
+
+/* Returns wether the specified cell and direction will land outside
+ * of the board. Used to determine if a piece is at a legal board
+ * location or not.
+ */
+func out_of_bounds(cell, dir int8) bool {
+ switch dir {
+ case E:
+ return cell%5 == 4
+ case ESE:
+ i := cell % 10
+ return i == 4 || i == 8 || i == 9 || cell >= 45
+ case SE:
+ return cell%10 == 9 || cell >= 45
+ case S:
+ return cell >= 40
+ case SW:
+ return cell%10 == 0 || cell >= 45
+ case WSW:
+ i := cell % 10
+ return i == 0 || i == 1 || i == 5 || cell >= 45
+ case W:
+ return cell%5 == 0
+ case WNW:
+ i := cell % 10
+ return i == 0 || i == 1 || i == 5 || cell < 5
+ case NW:
+ return cell%10 == 0 || cell < 5
+ case N:
+ return cell < 10
+ case NE:
+ return cell%10 == 9 || cell < 5
+ case ENE:
+ i := cell % 10
+ return i == 4 || i == 8 || i == 9 || cell < 5
+ }
+ return false
+}
+
+/* Rotate a piece 60 degrees clockwise */
+func rotate_piece(piece int) {
+ for i := 0; i < 4; i++ {
+ piece_def[piece][i] = rotate(piece_def[piece][i])
+ }
+}
+
+/* Flip a piece along the horizontal axis */
+func flip_piece(piece int) {
+ for i := 0; i < 4; i++ {
+ piece_def[piece][i] = flip(piece_def[piece][i])
+ }
+}
+
+/* Convenience function to quickly calculate all of the indices for a piece */
+func calc_cell_indices(cell []int8, piece int, index int8) {
+ cell[0] = index
+ for i := 1; i < 5; i++ {
+ cell[i] = shift(cell[i-1], piece_def[piece][i-1])
+ }
+}
+
+/* Convenience function to quickly calculate if a piece fits on the board */
+func cells_fit_on_board(cell []int8, piece int) bool {
+ return !out_of_bounds(cell[0], piece_def[piece][0]) &&
+ !out_of_bounds(cell[1], piece_def[piece][1]) &&
+ !out_of_bounds(cell[2], piece_def[piece][2]) &&
+ !out_of_bounds(cell[3], piece_def[piece][3])
+}
+
+/* Returns the lowest index of the cells of a piece.
+ * I use the lowest index that a piece occupies as the index for looking up
+ * the piece in the solve function.
+ */
+func minimum_of_cells(cell []int8) int8 {
+ minimum := cell[0]
+ for i := 1; i < 5; i++ {
+ if cell[i] < minimum {
+ minimum = cell[i]
+ }
+ }
+ return minimum
+}
+
+/* Calculate the lowest possible open cell if the piece is placed on the board.
+ * Used to later reduce the amount of time searching for open cells in the
+ * solve function.
+ */
+func first_empty_cell(cell []int8, minimum int8) int8 {
+ first_empty := minimum
+ for first_empty == cell[0] || first_empty == cell[1] ||
+ first_empty == cell[2] || first_empty == cell[3] ||
+ first_empty == cell[4] {
+ first_empty++
+ }
+ return first_empty
+}
+
+/* Generate the unsigned long long int that will later be anded with the
+ * board to determine if it fits.
+ */
+func bitmask_from_cells(cell []int8) uint64 {
+ var piece_mask uint64
+ for i := 0; i < 5; i++ {
+ piece_mask |= 1 << uint(cell[i])
+ }
+ return piece_mask
+}
+
+/* Record the piece and other important information in arrays that will
+ * later be used by the solve function.
+ */
+func record_piece(piece int, minimum int8, first_empty int8, piece_mask uint64) {
+ pieces[piece][minimum][piece_counts[piece][minimum]] = piece_mask
+ next_cell[piece][minimum][piece_counts[piece][minimum]] = first_empty
+ piece_counts[piece][minimum]++
+}
+
+/* Fill the entire board going cell by cell. If any cells are "trapped"
+ * they will be left alone.
+ */
+func fill_contiguous_space(board []int8, index int8) {
+ if board[index] == 1 {
+ return
+ }
+ board[index] = 1
+ if !out_of_bounds(index, E) {
+ fill_contiguous_space(board, shift(index, E))
+ }
+ if !out_of_bounds(index, SE) {
+ fill_contiguous_space(board, shift(index, SE))
+ }
+ if !out_of_bounds(index, SW) {
+ fill_contiguous_space(board, shift(index, SW))
+ }
+ if !out_of_bounds(index, W) {
+ fill_contiguous_space(board, shift(index, W))
+ }
+ if !out_of_bounds(index, NW) {
+ fill_contiguous_space(board, shift(index, NW))
+ }
+ if !out_of_bounds(index, NE) {
+ fill_contiguous_space(board, shift(index, NE))
+ }
+}
+
+/* To thin the number of pieces, I calculate if any of them trap any empty
+ * cells at the edges. There are only a handful of exceptions where the
+ * the board can be solved with the trapped cells. For example: piece 8 can
+ * trap 5 cells in the corner, but piece 3 can fit in those cells, or piece 0
+ * can split the board in half where both halves are viable.
+ */
+func has_island(cell []int8, piece int) bool {
+ temp_board := make([]int8, 50)
+ var i int
+ for i = 0; i < 5; i++ {
+ temp_board[cell[i]] = 1
+ }
+ i = 49
+ for temp_board[i] == 1 {
+ i--
+ }
+ fill_contiguous_space(temp_board, int8(i))
+ c := 0
+ for i = 0; i < 50; i++ {
+ if temp_board[i] == 0 {
+ c++
+ }
+ }
+ if c == 0 || (c == 5 && piece == 8) || (c == 40 && piece == 8) ||
+ (c%5 == 0 && piece == 0) {
+ return false
+ }
+ return true
+}
+
+/* Calculate all six rotations of the specified piece at the specified index.
+ * We calculate only half of piece 3's rotations. This is because any solution
+ * found has an identical solution rotated 180 degrees. Thus we can reduce the
+ * number of attempted pieces in the solve algorithm by not including the 180-
+ * degree-rotated pieces of ONE of the pieces. I chose piece 3 because it gave
+ * me the best time ;)
+ */
+func calc_six_rotations(piece, index int) {
+ cell := make([]int8, 5)
+ for rotation := 0; rotation < 6; rotation++ {
+ if piece != 3 || rotation < 3 {
+ calc_cell_indices(cell, piece, int8(index))
+ if cells_fit_on_board(cell, piece) && !has_island(cell, piece) {
+ minimum := minimum_of_cells(cell)
+ first_empty := first_empty_cell(cell, minimum)
+ piece_mask := bitmask_from_cells(cell)
+ record_piece(piece, minimum, first_empty, piece_mask)
+ }
+ }
+ rotate_piece(piece)
+ }
+}
+
+/* Calculate every legal rotation for each piece at each board location. */
+func calc_pieces() {
+ for piece := 0; piece < 10; piece++ {
+ for index := 0; index < 50; index++ {
+ calc_six_rotations(piece, index)
+ flip_piece(piece)
+ calc_six_rotations(piece, index)
+ }
+ }
+}
+
+/* Calculate all 32 possible states for a 5-bit row and all rows that will
+ * create islands that follow any of the 32 possible rows. These pre-
+ * calculated 5-bit rows will be used to find islands in a partially solved
+ * board in the solve function.
+ */
+const (
+ ROW_MASK = 0x1F
+ TRIPLE_MASK = 0x7FFF
+)
+
+var (
+ all_rows = [32]int8{0, 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, 28, 29, 30, 31,
+ }
+ bad_even_rows [32][32]int8
+ bad_odd_rows [32][32]int8
+ bad_even_triple [32768]int8
+ bad_odd_triple [32768]int8
+)
+
+func rows_bad(row1, row2 int8, even bool) int8 {
+ /* even is referring to row1 */
+ var row2_shift int8
+ /* Test for blockages at same index and shifted index */
+ if even {
+ row2_shift = ((row2 << 1) & ROW_MASK) | 0x01
+ } else {
+ row2_shift = (row2 >> 1) | 0x10
+ }
+ block := ((row1 ^ row2) & row2) & ((row1 ^ row2_shift) & row2_shift)
+ /* Test for groups of 0's */
+ in_zeroes := false
+ group_okay := false
+ for i := uint8(0); i < 5; i++ {
+ if row1&(1<<i) != 0 {
+ if in_zeroes {
+ if !group_okay {
+ return 1
+ }
+ in_zeroes = false
+ group_okay = false
+ }
+ } else {
+ if !in_zeroes {
+ in_zeroes = true
+ }
+ if (block & (1 << i)) == 0 {
+ group_okay = true
+ }
+ }
+ }
+ if in_zeroes {
+ return boolInt(!group_okay)
+ }
+ return 0
+}
+
+/* Check for cases where three rows checked sequentially cause a false
+ * positive. One scenario is when 5 cells may be surrounded where piece 5
+ * or 7 can fit. The other scenario is when piece 2 creates a hook shape.
+ */
+func triple_is_okay(row1, row2, row3 int, even bool) bool {
+ if even {
+ /* There are four cases:
+ * row1: 00011 00001 11001 10101
+ * row2: 01011 00101 10001 10001
+ * row3: 011?? 00110 ????? ?????
+ */
+ return ((row1 == 0x03) && (row2 == 0x0B) && ((row3 & 0x1C) == 0x0C)) ||
+ ((row1 == 0x01) && (row2 == 0x05) && (row3 == 0x06)) ||
+ ((row1 == 0x19) && (row2 == 0x11)) ||
+ ((row1 == 0x15) && (row2 == 0x11))
+ }
+ /* There are two cases:
+ * row1: 10011 10101
+ * row2: 10001 10001
+ * row3: ????? ?????
+ */
+ return ((row1 == 0x13) && (row2 == 0x11)) ||
+ ((row1 == 0x15) && (row2 == 0x11))
+}
+
+func calc_rows() {
+ for row1 := int8(0); row1 < 32; row1++ {
+ for row2 := int8(0); row2 < 32; row2++ {
+ bad_even_rows[row1][row2] = rows_bad(row1, row2, true)
+ bad_odd_rows[row1][row2] = rows_bad(row1, row2, false)
+ }
+ }
+ for row1 := 0; row1 < 32; row1++ {
+ for row2 := 0; row2 < 32; row2++ {
+ for row3 := 0; row3 < 32; row3++ {
+ result1 := bad_even_rows[row1][row2]
+ result2 := bad_odd_rows[row2][row3]
+ if result1 == 0 && result2 != 0 && triple_is_okay(row1, row2, row3, true) {
+ bad_even_triple[row1+(row2*32)+(row3*1024)] = 0
+ } else {
+ bad_even_triple[row1+(row2*32)+(row3*1024)] = boolInt(result1 != 0 || result2 != 0)
+ }
+
+ result1 = bad_odd_rows[row1][row2]
+ result2 = bad_even_rows[row2][row3]
+ if result1 == 0 && result2 != 0 && triple_is_okay(row1, row2, row3, false) {
+ bad_odd_triple[row1+(row2*32)+(row3*1024)] = 0
+ } else {
+ bad_odd_triple[row1+(row2*32)+(row3*1024)] = boolInt(result1 != 0 || result2 != 0)
+ }
+ }
+ }
+ }
+}
+
+/* Calculate islands while solving the board.
+ */
+func boardHasIslands(cell int8) int8 {
+ /* Too low on board, don't bother checking */
+ if cell >= 40 {
+ return 0
+ }
+ current_triple := (board >> uint((cell/5)*5)) & TRIPLE_MASK
+ if (cell/5)%2 != 0 {
+ return bad_odd_triple[current_triple]
+ }
+ return bad_even_triple[current_triple]
+}
+
+/* The recursive solve algorithm. Try to place each permutation in the upper-
+ * leftmost empty cell. Mark off available pieces as it goes along.
+ * Because the board is a bit mask, the piece number and bit mask must be saved
+ * at each successful piece placement. This data is used to create a 50 char
+ * array if a solution is found.
+ */
+var (
+ avail uint16 = 0x03FF
+ sol_nums [10]int8
+ sol_masks [10]uint64
+ solutions [2100][50]int8
+ solution_count = 0
+)
+
+func record_solution() {
+ for sol_no := 0; sol_no < 10; sol_no++ {
+ sol_mask := sol_masks[sol_no]
+ for index := 0; index < 50; index++ {
+ if sol_mask&1 == 1 {
+ solutions[solution_count][index] = sol_nums[sol_no]
+ /* Board rotated 180 degrees is a solution too! */
+ solutions[solution_count+1][49-index] = sol_nums[sol_no]
+ }
+ sol_mask = sol_mask >> 1
+ }
+ }
+ solution_count += 2
+}
+
+func solve(depth, cell int8) {
+ if solution_count >= *max_solutions {
+ return
+ }
+
+ for board&(1<<uint(cell)) != 0 {
+ cell++
+ }
+
+ for piece := int8(0); piece < 10; piece++ {
+ var piece_no_mask uint16 = 1 << uint(piece)
+ if avail&piece_no_mask == 0 {
+ continue
+ }
+ avail ^= piece_no_mask
+ max_rots := piece_counts[piece][cell]
+ piece_mask := pieces[piece][cell]
+ for rotation := 0; rotation < max_rots; rotation++ {
+ if board&piece_mask[rotation] == 0 {
+ sol_nums[depth] = piece
+ sol_masks[depth] = piece_mask[rotation]
+ if depth == 9 {
+ /* Solution found!!!!!11!!ONE! */
+ record_solution()
+ avail ^= piece_no_mask
+ return
+ }
+ board |= piece_mask[rotation]
+ if boardHasIslands(next_cell[piece][cell][rotation]) == 0 {
+ solve(depth+1, next_cell[piece][cell][rotation])
+ }
+ board ^= piece_mask[rotation]
+ }
+ }
+ avail ^= piece_no_mask
+ }
+}
+
+/* pretty print a board in the specified hexagonal format */
+func pretty(b *[50]int8) {
+ for i := 0; i < 50; i += 10 {
+ fmt.Printf("%c %c %c %c %c \n %c %c %c %c %c \n", b[i]+'0', b[i+1]+'0',
+ b[i+2]+'0', b[i+3]+'0', b[i+4]+'0', b[i+5]+'0', b[i+6]+'0',
+ b[i+7]+'0', b[i+8]+'0', b[i+9]+'0')
+ }
+ fmt.Printf("\n")
+}
+
+/* Find smallest and largest solutions */
+func smallest_largest() (smallest, largest *[50]int8) {
+ smallest = &solutions[0]
+ largest = &solutions[0]
+ for i := 1; i < solution_count; i++ {
+ candidate := &solutions[i]
+ for j, s := range *smallest {
+ c := candidate[j]
+ if c == s {
+ continue
+ }
+ if c < s {
+ smallest = candidate
+ }
+ break
+ }
+ for j, s := range *largest {
+ c := candidate[j]
+ if c == s {
+ continue
+ }
+ if c > s {
+ largest = candidate
+ }
+ break
+ }
+ }
+ return
+}
+
+func main() {
+ flag.Parse()
+ calc_pieces()
+ calc_rows()
+ solve(0, 0)
+ fmt.Printf("%d solutions found\n\n", solution_count)
+ smallest, largest := smallest_largest()
+ pretty(smallest)
+ pretty(largest)
+}
diff --git a/shootout/meteor-contest.txt b/shootout/meteor-contest.txt
new file mode 100644
index 0000000..38d9783
--- /dev/null
+++ b/shootout/meteor-contest.txt
@@ -0,0 +1,24 @@
+2098 solutions found
+
+0 0 0 0 1
+ 2 2 2 0 1
+2 6 6 1 1
+ 2 6 1 5 5
+8 6 5 5 5
+ 8 6 3 3 3
+4 8 8 9 3
+ 4 4 8 9 3
+4 7 4 7 9
+ 7 7 7 9 9
+
+9 9 9 9 8
+ 9 6 6 8 5
+6 6 8 8 5
+ 6 8 2 5 5
+7 7 7 2 5
+ 7 4 7 2 0
+1 4 2 2 0
+ 1 4 4 0 3
+1 4 0 0 3
+ 1 1 3 3 3
+
diff --git a/shootout/nbody.c b/shootout/nbody.c
new file mode 100644
index 0000000..ea601aa
--- /dev/null
+++ b/shootout/nbody.c
@@ -0,0 +1,172 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+ * The Great Computer Language Shootout
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by Christoph Bauer
+ *
+ */
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define pi 3.141592653589793
+#define solar_mass (4 * pi * pi)
+#define days_per_year 365.24
+
+struct planet {
+ double x, y, z;
+ double vx, vy, vz;
+ double mass;
+};
+
+void advance(int nbodies, struct planet * bodies, double dt)
+{
+ int i, j;
+
+ for (i = 0; i < nbodies; i++) {
+ struct planet * b = &(bodies[i]);
+ for (j = i + 1; j < nbodies; j++) {
+ struct planet * b2 = &(bodies[j]);
+ double dx = b->x - b2->x;
+ double dy = b->y - b2->y;
+ double dz = b->z - b2->z;
+ double distance = sqrt(dx * dx + dy * dy + dz * dz);
+ double mag = dt / (distance * distance * distance);
+ b->vx -= dx * b2->mass * mag;
+ b->vy -= dy * b2->mass * mag;
+ b->vz -= dz * b2->mass * mag;
+ b2->vx += dx * b->mass * mag;
+ b2->vy += dy * b->mass * mag;
+ b2->vz += dz * b->mass * mag;
+ }
+ }
+ for (i = 0; i < nbodies; i++) {
+ struct planet * b = &(bodies[i]);
+ b->x += dt * b->vx;
+ b->y += dt * b->vy;
+ b->z += dt * b->vz;
+ }
+}
+
+double energy(int nbodies, struct planet * bodies)
+{
+ double e;
+ int i, j;
+
+ e = 0.0;
+ for (i = 0; i < nbodies; i++) {
+ struct planet * b = &(bodies[i]);
+ e += 0.5 * b->mass * (b->vx * b->vx + b->vy * b->vy + b->vz * b->vz);
+ for (j = i + 1; j < nbodies; j++) {
+ struct planet * b2 = &(bodies[j]);
+ double dx = b->x - b2->x;
+ double dy = b->y - b2->y;
+ double dz = b->z - b2->z;
+ double distance = sqrt(dx * dx + dy * dy + dz * dz);
+ e -= (b->mass * b2->mass) / distance;
+ }
+ }
+ return e;
+}
+
+void offset_momentum(int nbodies, struct planet * bodies)
+{
+ double px = 0.0, py = 0.0, pz = 0.0;
+ int i;
+ for (i = 0; i < nbodies; i++) {
+ px += bodies[i].vx * bodies[i].mass;
+ py += bodies[i].vy * bodies[i].mass;
+ pz += bodies[i].vz * bodies[i].mass;
+ }
+ bodies[0].vx = - px / solar_mass;
+ bodies[0].vy = - py / solar_mass;
+ bodies[0].vz = - pz / solar_mass;
+}
+
+#define NBODIES 5
+struct planet bodies[NBODIES] = {
+ { /* sun */
+ 0, 0, 0, 0, 0, 0, solar_mass
+ },
+ { /* jupiter */
+ 4.84143144246472090e+00,
+ -1.16032004402742839e+00,
+ -1.03622044471123109e-01,
+ 1.66007664274403694e-03 * days_per_year,
+ 7.69901118419740425e-03 * days_per_year,
+ -6.90460016972063023e-05 * days_per_year,
+ 9.54791938424326609e-04 * solar_mass
+ },
+ { /* saturn */
+ 8.34336671824457987e+00,
+ 4.12479856412430479e+00,
+ -4.03523417114321381e-01,
+ -2.76742510726862411e-03 * days_per_year,
+ 4.99852801234917238e-03 * days_per_year,
+ 2.30417297573763929e-05 * days_per_year,
+ 2.85885980666130812e-04 * solar_mass
+ },
+ { /* uranus */
+ 1.28943695621391310e+01,
+ -1.51111514016986312e+01,
+ -2.23307578892655734e-01,
+ 2.96460137564761618e-03 * days_per_year,
+ 2.37847173959480950e-03 * days_per_year,
+ -2.96589568540237556e-05 * days_per_year,
+ 4.36624404335156298e-05 * solar_mass
+ },
+ { /* neptune */
+ 1.53796971148509165e+01,
+ -2.59193146099879641e+01,
+ 1.79258772950371181e-01,
+ 2.68067772490389322e-03 * days_per_year,
+ 1.62824170038242295e-03 * days_per_year,
+ -9.51592254519715870e-05 * days_per_year,
+ 5.15138902046611451e-05 * solar_mass
+ }
+};
+
+int main(int argc, char ** argv)
+{
+ int n = atoi(argv[1]);
+ int i;
+
+ offset_momentum(NBODIES, bodies);
+ printf ("%.9f\n", energy(NBODIES, bodies));
+ for (i = 1; i <= n; i++)
+ advance(NBODIES, bodies, 0.01);
+ printf ("%.9f\n", energy(NBODIES, bodies));
+ return 0;
+}
diff --git a/shootout/nbody.go b/shootout/nbody.go
new file mode 100644
index 0000000..4eeba44
--- /dev/null
+++ b/shootout/nbody.go
@@ -0,0 +1,180 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * based on C program by Christoph Bauer
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "math"
+)
+
+var n = flag.Int("n", 1000, "number of iterations")
+
+type Body struct {
+ x, y, z, vx, vy, vz, mass float64
+}
+
+const (
+ solarMass = 4 * math.Pi * math.Pi
+ daysPerYear = 365.24
+)
+
+func (b *Body) offsetMomentum(px, py, pz float64) {
+ b.vx = -px / solarMass
+ b.vy = -py / solarMass
+ b.vz = -pz / solarMass
+}
+
+type System []*Body
+
+func NewSystem(body []Body) System {
+ n := make(System, len(body))
+ for i := 0; i < len(body); i++ {
+ n[i] = new(Body) // copy to avoid overwriting the inputs
+ *n[i] = body[i]
+ }
+ var px, py, pz float64
+ for _, body := range n {
+ px += body.vx * body.mass
+ py += body.vy * body.mass
+ pz += body.vz * body.mass
+ }
+ n[0].offsetMomentum(px, py, pz)
+ return n
+}
+
+func (sys System) energy() float64 {
+ var e float64
+ for i, body := range sys {
+ e += 0.5 * body.mass *
+ (body.vx*body.vx + body.vy*body.vy + body.vz*body.vz)
+ for j := i + 1; j < len(sys); j++ {
+ body2 := sys[j]
+ dx := body.x - body2.x
+ dy := body.y - body2.y
+ dz := body.z - body2.z
+ distance := math.Sqrt(dx*dx + dy*dy + dz*dz)
+ e -= (body.mass * body2.mass) / distance
+ }
+ }
+ return e
+}
+
+func (sys System) advance(dt float64) {
+ for i, body := range sys {
+ for j := i + 1; j < len(sys); j++ {
+ body2 := sys[j]
+ dx := body.x - body2.x
+ dy := body.y - body2.y
+ dz := body.z - body2.z
+
+ dSquared := dx*dx + dy*dy + dz*dz
+ distance := math.Sqrt(dSquared)
+ mag := dt / (dSquared * distance)
+
+ body.vx -= dx * body2.mass * mag
+ body.vy -= dy * body2.mass * mag
+ body.vz -= dz * body2.mass * mag
+
+ body2.vx += dx * body.mass * mag
+ body2.vy += dy * body.mass * mag
+ body2.vz += dz * body.mass * mag
+ }
+ }
+
+ for _, body := range sys {
+ body.x += dt * body.vx
+ body.y += dt * body.vy
+ body.z += dt * body.vz
+ }
+}
+
+var (
+ jupiter = Body{
+ x: 4.84143144246472090e+00,
+ y: -1.16032004402742839e+00,
+ z: -1.03622044471123109e-01,
+ vx: 1.66007664274403694e-03 * daysPerYear,
+ vy: 7.69901118419740425e-03 * daysPerYear,
+ vz: -6.90460016972063023e-05 * daysPerYear,
+ mass: 9.54791938424326609e-04 * solarMass,
+ }
+ saturn = Body{
+ x: 8.34336671824457987e+00,
+ y: 4.12479856412430479e+00,
+ z: -4.03523417114321381e-01,
+ vx: -2.76742510726862411e-03 * daysPerYear,
+ vy: 4.99852801234917238e-03 * daysPerYear,
+ vz: 2.30417297573763929e-05 * daysPerYear,
+ mass: 2.85885980666130812e-04 * solarMass,
+ }
+ uranus = Body{
+ x: 1.28943695621391310e+01,
+ y: -1.51111514016986312e+01,
+ z: -2.23307578892655734e-01,
+ vx: 2.96460137564761618e-03 * daysPerYear,
+ vy: 2.37847173959480950e-03 * daysPerYear,
+ vz: -2.96589568540237556e-05 * daysPerYear,
+ mass: 4.36624404335156298e-05 * solarMass,
+ }
+ neptune = Body{
+ x: 1.53796971148509165e+01,
+ y: -2.59193146099879641e+01,
+ z: 1.79258772950371181e-01,
+ vx: 2.68067772490389322e-03 * daysPerYear,
+ vy: 1.62824170038242295e-03 * daysPerYear,
+ vz: -9.51592254519715870e-05 * daysPerYear,
+ mass: 5.15138902046611451e-05 * solarMass,
+ }
+ sun = Body{
+ mass: solarMass,
+ }
+)
+
+func main() {
+ flag.Parse()
+
+ system := NewSystem([]Body{sun, jupiter, saturn, uranus, neptune})
+ fmt.Printf("%.9f\n", system.energy())
+ for i := 0; i < *n; i++ {
+ system.advance(0.01)
+ }
+ fmt.Printf("%.9f\n", system.energy())
+}
diff --git a/shootout/nbody.txt b/shootout/nbody.txt
new file mode 100644
index 0000000..1731557
--- /dev/null
+++ b/shootout/nbody.txt
@@ -0,0 +1,2 @@
+-0.169075164
+-0.169087605
diff --git a/shootout/pidigits.c b/shootout/pidigits.c
new file mode 100644
index 0000000..9cbcae4
--- /dev/null
+++ b/shootout/pidigits.c
@@ -0,0 +1,125 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ http://shootout.alioth.debian.org/
+
+ contributed by Paolo Bonzini & Sean Bartlett
+ modified by Michael Mellor
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <gmp.h>
+
+static mpz_t numer, accum, denom, tmp1, tmp2;
+
+static int extract_digit()
+{
+ if (mpz_cmp(numer, accum) > 0)
+ return -1;
+
+ /* Compute (numer * 3 + accum) / denom */
+ mpz_mul_2exp(tmp1, numer, 1);
+ mpz_add(tmp1, tmp1, numer);
+ mpz_add(tmp1, tmp1, accum);
+ mpz_fdiv_qr(tmp1, tmp2, tmp1, denom);
+
+ /* Now, if (numer * 4 + accum) % denom... */
+ mpz_add(tmp2, tmp2, numer);
+
+ /* ... is normalized, then the two divisions have the same result. */
+ if (mpz_cmp(tmp2, denom) >= 0)
+ return -1;
+
+ return mpz_get_ui(tmp1);
+}
+
+static void next_term(unsigned int k)
+{
+ unsigned int y2 = k*2 + 1;
+
+ mpz_mul_2exp(tmp1, numer, 1);
+ mpz_add(accum, accum, tmp1);
+ mpz_mul_ui(accum, accum, y2);
+ mpz_mul_ui(numer, numer, k);
+ mpz_mul_ui(denom, denom, y2);
+}
+
+static void eliminate_digit(unsigned int d)
+{
+ mpz_submul_ui(accum, denom, d);
+ mpz_mul_ui(accum, accum, 10);
+ mpz_mul_ui(numer, numer, 10);
+}
+
+static void pidigits(unsigned int n)
+{
+ int d;
+ unsigned int i = 0, k = 0, m;
+ mpz_init(tmp1);
+ mpz_init(tmp2);
+ mpz_init_set_ui(numer, 1);
+ mpz_init_set_ui(accum, 0);
+ mpz_init_set_ui(denom, 1);
+
+ for(;;)
+ {
+ do {
+ k++;
+ next_term(k);
+ d = extract_digit();
+ } while(d == -1);
+
+ putchar(d + '0');
+
+ i++;
+ m = i%10;
+ if(m == 0)
+ printf("\t:%d\n", i);
+ if(i >= n)
+ break;
+ eliminate_digit(d);
+ }
+
+ if(m) {
+ m = 10 - m;
+ while(m--)
+ putchar(' ');
+ printf("\t:%d\n", n);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ pidigits(argc > 1 ? atoi(argv[1]) : 27);
+ return 0;
+}
diff --git a/shootout/pidigits.go b/shootout/pidigits.go
new file mode 100644
index 0000000..16f8f81
--- /dev/null
+++ b/shootout/pidigits.go
@@ -0,0 +1,138 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * based on pidigits.c (by Paolo Bonzini & Sean Bartlett,
+ * modified by Michael Mellor)
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "math/big"
+)
+
+var n = flag.Int("n", 27, "number of digits")
+var silent = flag.Bool("s", false, "don't print result")
+
+var (
+ tmp1 = big.NewInt(0)
+ tmp2 = big.NewInt(0)
+ tmp3 = big.NewInt(0)
+ y2 = big.NewInt(0)
+ bigk = big.NewInt(0)
+ numer = big.NewInt(1)
+ accum = big.NewInt(0)
+ denom = big.NewInt(1)
+ ten = big.NewInt(10)
+)
+
+func extract_digit() int64 {
+ if numer.Cmp(accum) > 0 {
+ return -1
+ }
+
+ // Compute (numer * 3 + accum) / denom
+ tmp1.Lsh(numer, 1)
+ tmp1.Add(tmp1, numer)
+ tmp1.Add(tmp1, accum)
+ tmp1.DivMod(tmp1, denom, tmp2)
+
+ // Now, if (numer * 4 + accum) % denom...
+ tmp2.Add(tmp2, numer)
+
+ // ... is normalized, then the two divisions have the same result.
+ if tmp2.Cmp(denom) >= 0 {
+ return -1
+ }
+
+ return tmp1.Int64()
+}
+
+func next_term(k int64) {
+ y2.SetInt64(k*2 + 1)
+ bigk.SetInt64(k)
+
+ tmp1.Lsh(numer, 1)
+ accum.Add(accum, tmp1)
+ accum.Mul(accum, y2)
+ numer.Mul(numer, bigk)
+ denom.Mul(denom, y2)
+}
+
+func eliminate_digit(d int64) {
+ tmp3.SetInt64(d)
+ accum.Sub(accum, tmp3.Mul(denom, tmp3))
+ accum.Mul(accum, ten)
+ numer.Mul(numer, ten)
+}
+
+func printf(s string, arg ...interface{}) {
+ if !*silent {
+ fmt.Printf(s, arg...)
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ var m int // 0 <= m < 10
+ for i, k := 0, int64(0); ; {
+ d := int64(-1)
+ for d < 0 {
+ k++
+ next_term(k)
+ d = extract_digit()
+ }
+
+ printf("%c", d+'0')
+
+ i++
+ m = i % 10
+ if m == 0 {
+ printf("\t:%d\n", i)
+ }
+ if i >= *n {
+ break
+ }
+ eliminate_digit(d)
+ }
+
+ if m > 0 {
+ printf("%s\t:%d\n", " "[m:10], *n)
+ }
+}
diff --git a/shootout/pidigits.txt b/shootout/pidigits.txt
new file mode 100644
index 0000000..ad946a9
--- /dev/null
+++ b/shootout/pidigits.txt
@@ -0,0 +1,3 @@
+3141592653 :10
+5897932384 :20
+6264338 :27
diff --git a/shootout/regex-dna-parallel.go b/shootout/regex-dna-parallel.go
new file mode 100644
index 0000000..49bb559
--- /dev/null
+++ b/shootout/regex-dna-parallel.go
@@ -0,0 +1,127 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "regexp"
+ "runtime"
+)
+
+var variants = []string{
+ "agggtaaa|tttaccct",
+ "[cgt]gggtaaa|tttaccc[acg]",
+ "a[act]ggtaaa|tttacc[agt]t",
+ "ag[act]gtaaa|tttac[agt]ct",
+ "agg[act]taaa|ttta[agt]cct",
+ "aggg[acg]aaa|ttt[cgt]ccct",
+ "agggt[cgt]aa|tt[acg]accct",
+ "agggta[cgt]a|t[acg]taccct",
+ "agggtaa[cgt]|[acg]ttaccct",
+}
+
+type Subst struct {
+ pat, repl string
+}
+
+var substs = []Subst{
+ Subst{"B", "(c|g|t)"},
+ Subst{"D", "(a|g|t)"},
+ Subst{"H", "(a|c|t)"},
+ Subst{"K", "(g|t)"},
+ Subst{"M", "(a|c)"},
+ Subst{"N", "(a|c|g|t)"},
+ Subst{"R", "(a|g)"},
+ Subst{"S", "(c|g)"},
+ Subst{"V", "(a|c|g)"},
+ Subst{"W", "(a|t)"},
+ Subst{"Y", "(c|t)"},
+}
+
+func countMatches(pat string, bytes []byte) int {
+ re := regexp.MustCompile(pat)
+ n := 0
+ for {
+ e := re.FindIndex(bytes)
+ if e == nil {
+ break
+ }
+ n++
+ bytes = bytes[e[1]:]
+ }
+ return n
+}
+
+func main() {
+ runtime.GOMAXPROCS(4)
+ bytes, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "can't read input: %s\n", err)
+ os.Exit(2)
+ }
+ ilen := len(bytes)
+ // Delete the comment lines and newlines
+ bytes = regexp.MustCompile("(>[^\n]+)?\n").ReplaceAll(bytes, []byte{})
+ clen := len(bytes)
+
+ mresults := make([]chan int, len(variants))
+ for i, s := range variants {
+ ch := make(chan int)
+ mresults[i] = ch
+ go func(ss string) {
+ ch <- countMatches(ss, bytes)
+ }(s)
+ }
+
+ lenresult := make(chan int)
+ bb := bytes
+ go func() {
+ for _, sub := range substs {
+ bb = regexp.MustCompile(sub.pat).ReplaceAll(bb, []byte(sub.repl))
+ }
+ lenresult <- len(bb)
+ }()
+
+ for i, s := range variants {
+ fmt.Printf("%s %d\n", s, <-mresults[i])
+ }
+ fmt.Printf("\n%d\n%d\n%d\n", ilen, clen, <-lenresult)
+}
diff --git a/shootout/regex-dna-parallel.txt b/shootout/regex-dna-parallel.txt
new file mode 100644
index 0000000..e23e71f
--- /dev/null
+++ b/shootout/regex-dna-parallel.txt
@@ -0,0 +1,13 @@
+agggtaaa|tttaccct 1
+[cgt]gggtaaa|tttaccc[acg] 0
+a[act]ggtaaa|tttacc[agt]t 0
+ag[act]gtaaa|tttac[agt]ct 0
+agg[act]taaa|ttta[agt]cct 1
+aggg[acg]aaa|ttt[cgt]ccct 0
+agggt[cgt]aa|tt[acg]accct 0
+agggta[cgt]a|t[acg]taccct 0
+agggtaa[cgt]|[acg]ttaccct 2
+
+10245
+10000
+13348
diff --git a/shootout/regex-dna.c b/shootout/regex-dna.c
new file mode 100644
index 0000000..0c95bc7
--- /dev/null
+++ b/shootout/regex-dna.c
@@ -0,0 +1,156 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+** The Computer Language Shootout
+** http://shootout.alioth.debian.org/
+** contributed by Mike Pall
+**
+** regex-dna benchmark using PCRE
+**
+** compile with:
+** gcc -O3 -fomit-frame-pointer -o regexdna regexdna.c -lpcre
+*/
+
+#define __USE_STRING_INLINES
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pcre.h>
+
+typedef struct fbuf {
+ char *buf;
+ size_t size, len;
+} fbuf_t;
+
+static void fb_init(fbuf_t *b)
+{
+ b->buf = NULL;
+ b->len = b->size = 0;
+}
+
+static char *fb_need(fbuf_t *b, size_t need)
+{
+ need += b->len;
+ if (need > b->size) {
+ if (b->size == 0) b->size = need;
+ else while (need > b->size) b->size += b->size;
+ if (!(b->buf = realloc(b->buf, b->size))) exit(1);
+ }
+ return b->buf+b->len;
+}
+
+#define FB_MINREAD (3<<16)
+
+/* Read all of a stdio stream into dst buffer. */
+static size_t fb_readall(fbuf_t *dst, FILE *fp)
+{
+ char *dp;
+ int n;
+ for (dp = fb_need(dst, FB_MINREAD);
+ (n = fread(dp, 1, dst->size-dst->len, fp)) > 0;
+ dp = fb_need(dst, FB_MINREAD)) dst->len += n;
+ if (ferror(fp)) exit(1);
+ return dst->len;
+}
+
+/* Substitute pattern p with replacement r, copying from src to dst buffer. */
+static size_t fb_subst(fbuf_t *dst, fbuf_t *src, const char *p, const char *r)
+{
+ pcre *re;
+ pcre_extra *re_ex;
+ const char *re_e;
+ char *dp;
+ int re_eo, m[3], pos, rlen, clen;
+ if (!(re = pcre_compile(p, PCRE_CASELESS, &re_e, &re_eo, NULL))) exit(1);
+ re_ex = pcre_study(re, 0, &re_e);
+ for (dst->len = 0, rlen = strlen(r), pos = 0;
+ pcre_exec(re, re_ex, src->buf, src->len, pos, 0, m, 3) >= 0;
+ pos = m[1]) {
+ clen = m[0]-pos;
+ dp = fb_need(dst, clen+rlen);
+ dst->len += clen+rlen;
+ memcpy(dp, src->buf+pos, clen);
+ memcpy(dp+clen, r, rlen);
+ }
+ clen = src->len-pos;
+ dp = fb_need(dst, clen);
+ dst->len += clen;
+ memcpy(dp, src->buf+pos, clen);
+ return dst->len;
+}
+
+/* Count all matches with pattern p in src buffer. */
+static int fb_countmatches(fbuf_t *src, const char *p)
+{
+ pcre *re;
+ pcre_extra *re_ex;
+ const char *re_e;
+ int re_eo, m[3], pos, count;
+ if (!(re = pcre_compile(p, PCRE_CASELESS, &re_e, &re_eo, NULL))) exit(1);
+ re_ex = pcre_study(re, 0, &re_e);
+ for (count = 0, pos = 0;
+ pcre_exec(re, re_ex, src->buf, src->len, pos, 0, m, 3) >= 0;
+ pos = m[1]) count++;
+ return count;
+}
+
+static const char *variants[] = {
+ "agggtaaa|tttaccct", "[cgt]gggtaaa|tttaccc[acg]",
+ "a[act]ggtaaa|tttacc[agt]t", "ag[act]gtaaa|tttac[agt]ct",
+ "agg[act]taaa|ttta[agt]cct", "aggg[acg]aaa|ttt[cgt]ccct",
+ "agggt[cgt]aa|tt[acg]accct", "agggta[cgt]a|t[acg]taccct",
+ "agggtaa[cgt]|[acg]ttaccct", NULL
+};
+
+static const char *subst[] = {
+ "B", "(c|g|t)", "D", "(a|g|t)", "H", "(a|c|t)", "K", "(g|t)",
+ "M", "(a|c)", "N", "(a|c|g|t)", "R", "(a|g)", "S", "(c|g)",
+ "V", "(a|c|g)", "W", "(a|t)", "Y", "(c|t)", NULL
+};
+
+int main(int argc, char **argv)
+{
+ fbuf_t seq[2];
+ const char **pp;
+ size_t ilen, clen, slen;
+ int flip;
+ fb_init(&seq[0]);
+ fb_init(&seq[1]);
+ ilen = fb_readall(&seq[0], stdin);
+ clen = fb_subst(&seq[1], &seq[0], ">.*|\n", "");
+ for (pp = variants; *pp; pp++)
+ printf("%s %d\n", *pp, fb_countmatches(&seq[1], *pp));
+ for (slen = 0, flip = 1, pp = subst; *pp; pp += 2, flip = 1-flip)
+ slen = fb_subst(&seq[1-flip], &seq[flip], *pp, pp[1]);
+ printf("\n%zu\n%zu\n%zu\n", ilen, clen, slen);
+ return 0;
+}
diff --git a/shootout/regex-dna.go b/shootout/regex-dna.go
new file mode 100644
index 0000000..2f44137
--- /dev/null
+++ b/shootout/regex-dna.go
@@ -0,0 +1,109 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "regexp"
+)
+
+var variants = []string{
+ "agggtaaa|tttaccct",
+ "[cgt]gggtaaa|tttaccc[acg]",
+ "a[act]ggtaaa|tttacc[agt]t",
+ "ag[act]gtaaa|tttac[agt]ct",
+ "agg[act]taaa|ttta[agt]cct",
+ "aggg[acg]aaa|ttt[cgt]ccct",
+ "agggt[cgt]aa|tt[acg]accct",
+ "agggta[cgt]a|t[acg]taccct",
+ "agggtaa[cgt]|[acg]ttaccct",
+}
+
+type Subst struct {
+ pat, repl string
+}
+
+var substs = []Subst{
+ Subst{"B", "(c|g|t)"},
+ Subst{"D", "(a|g|t)"},
+ Subst{"H", "(a|c|t)"},
+ Subst{"K", "(g|t)"},
+ Subst{"M", "(a|c)"},
+ Subst{"N", "(a|c|g|t)"},
+ Subst{"R", "(a|g)"},
+ Subst{"S", "(c|g)"},
+ Subst{"V", "(a|c|g)"},
+ Subst{"W", "(a|t)"},
+ Subst{"Y", "(c|t)"},
+}
+
+func countMatches(pat string, bytes []byte) int {
+ re := regexp.MustCompile(pat)
+ n := 0
+ for {
+ e := re.FindIndex(bytes)
+ if len(e) == 0 {
+ break
+ }
+ n++
+ bytes = bytes[e[1]:]
+ }
+ return n
+}
+
+func main() {
+ bytes, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "can't read input: %s\n", err)
+ os.Exit(2)
+ }
+ ilen := len(bytes)
+ // Delete the comment lines and newlines
+ bytes = regexp.MustCompile("(>[^\n]+)?\n").ReplaceAll(bytes, []byte{})
+ clen := len(bytes)
+ for _, s := range variants {
+ fmt.Printf("%s %d\n", s, countMatches(s, bytes))
+ }
+ for _, sub := range substs {
+ bytes = regexp.MustCompile(sub.pat).ReplaceAll(bytes, []byte(sub.repl))
+ }
+ fmt.Printf("\n%d\n%d\n%d\n", ilen, clen, len(bytes))
+}
diff --git a/shootout/regex-dna.txt b/shootout/regex-dna.txt
new file mode 100644
index 0000000..e23e71f
--- /dev/null
+++ b/shootout/regex-dna.txt
@@ -0,0 +1,13 @@
+agggtaaa|tttaccct 1
+[cgt]gggtaaa|tttaccc[acg] 0
+a[act]ggtaaa|tttacc[agt]t 0
+ag[act]gtaaa|tttac[agt]ct 0
+agg[act]taaa|ttta[agt]cct 1
+aggg[acg]aaa|ttt[cgt]ccct 0
+agggt[cgt]aa|tt[acg]accct 0
+agggta[cgt]a|t[acg]taccct 0
+agggtaa[cgt]|[acg]ttaccct 2
+
+10245
+10000
+13348
diff --git a/shootout/reverse-complement.c b/shootout/reverse-complement.c
new file mode 100644
index 0000000..ed2c982
--- /dev/null
+++ b/shootout/reverse-complement.c
@@ -0,0 +1,102 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+ * The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org
+ *
+ * contributed by Bob W
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define JBFSIZE 82 // line input buffer size
+#define QBFSIZE 5200 // output buffer initial size
+#define Z16 "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+#define V32 "\0TVGH\0\0CD\0\0M\0KN\0\0\0YSA\0BW\0R\0\0\0\0\0\0"
+#define VALL Z16 Z16 Z16 Z16 V32 V32 Z16 Z16 Z16 Z16 Z16 Z16 Z16 Z16
+
+int errex(char *s, int n) { // error message+value, return 1
+ fprintf(stderr,"\n*** Error: %s [%d]!\n", s, n);
+ return 1;
+}
+
+int main () { // ***** main *****
+ char *pj, *pq, *pr; // buffer pointers: inp,out,/out
+ char *jjj = malloc(JBFSIZE); // allocate input line buffer
+ char *qqq = malloc(QBFSIZE); // output buffer (dyn. size)
+ char *pqstop = qqq+QBFSIZE; // end-of-buffer pointer
+ char xtab[256] = VALL; // char conversion table
+
+ if (!jjj || !qqq)
+ return errex("Buffer allocation", !jjj + !qqq);
+ pj = fgets(jjj,JBFSIZE,stdin); // fetch 1st line
+ if (!pj)
+ return errex("No input data",0);
+ if (*jjj != '>')
+ return errex("1st char not '>'", 0);
+
+ while (pj) { // MAIN LOOP: process data
+ fputs(jjj, stdout); // output ID line
+
+ for (pq=qqq+1, pr=pqstop; ; pq++) { // LOOP: fill output buffer
+ pj = fgets(jjj, JBFSIZE, stdin); // get line from stdin
+ if (!pj || (*jjj=='>')) break; // EOF or new ID line
+ if (pr <= (pq+61)) { // need to resize buffer
+ char *newstop = pqstop + 12777888;
+ char *newptr = realloc(qqq, newstop-qqq);
+ if (!newptr)
+ return errex("Out of memory", 0);
+ if (newptr != qqq) { // new base: adj. pointers
+ size_t x = newptr-qqq; // offset for pointer update
+ pq+=x; pr+=x; qqq+=x;
+ newstop+=x; pqstop+=x;
+ }
+ pr = __builtin_memmove(newstop-(pqstop-pr), pr, pqstop-pr);
+ pqstop = newstop; // buffer resize complete
+ }
+ while (*pj) { // LOOP: conv. & revert line
+ char c = xtab[(unsigned char)(*pj++)];
+ if (c) // conversion valid
+ *(--pr) = c;
+ }
+ }
+
+ for (pq = qqq; pr<pqstop; ) { // LOOP: format output
+ size_t x = (pqstop-pr)<60 ? pqstop-pr : 60;
+ __builtin_memmove(pq,pr,x); // move line to free space
+ pr+=x; pq+=x; *(pq++) = 0xA; // adjust pointers, add LF
+ }
+ fwrite(qqq, 1, pq-qqq, stdout); // output converted data
+ }
+ return 0;
+}
diff --git a/shootout/reverse-complement.go b/shootout/reverse-complement.go
new file mode 100644
index 0000000..11b59d2
--- /dev/null
+++ b/shootout/reverse-complement.go
@@ -0,0 +1,108 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "bufio"
+ "os"
+)
+
+const lineSize = 60
+
+var complement = [256]uint8{
+ 'A': 'T', 'a': 'T',
+ 'C': 'G', 'c': 'G',
+ 'G': 'C', 'g': 'C',
+ 'T': 'A', 't': 'A',
+ 'U': 'A', 'u': 'A',
+ 'M': 'K', 'm': 'K',
+ 'R': 'Y', 'r': 'Y',
+ 'W': 'W', 'w': 'W',
+ 'S': 'S', 's': 'S',
+ 'Y': 'R', 'y': 'R',
+ 'K': 'M', 'k': 'M',
+ 'V': 'B', 'v': 'B',
+ 'H': 'D', 'h': 'D',
+ 'D': 'H', 'd': 'H',
+ 'B': 'V', 'b': 'V',
+ 'N': 'N', 'n': 'N',
+}
+
+func main() {
+ in := bufio.NewReader(os.Stdin)
+ buf := make([]byte, 1024*1024)
+ line, err := in.ReadSlice('\n')
+ for err == nil {
+ os.Stdout.Write(line)
+
+ // Accumulate reversed complement in buf[w:]
+ nchar := 0
+ w := len(buf)
+ for {
+ line, err = in.ReadSlice('\n')
+ if err != nil || line[0] == '>' {
+ break
+ }
+ line = line[0 : len(line)-1]
+ nchar += len(line)
+ if len(line)+nchar/60+128 >= w {
+ nbuf := make([]byte, len(buf)*5)
+ copy(nbuf[len(nbuf)-len(buf):], buf)
+ w += len(nbuf) - len(buf)
+ buf = nbuf
+ }
+
+ // This loop is the bottleneck.
+ for _, c := range line {
+ w--
+ buf[w] = complement[c]
+ }
+ }
+
+ // Copy down to beginning of buffer, inserting newlines.
+ // The loop left room for the newlines and 128 bytes of padding.
+ i := 0
+ for j := w; j < len(buf); j += 60 {
+ n := copy(buf[i:i+60], buf[j:])
+ buf[i+n] = '\n'
+ i += n + 1
+ }
+ os.Stdout.Write(buf[0:i])
+ }
+}
diff --git a/shootout/reverse-complement.txt b/shootout/reverse-complement.txt
new file mode 100644
index 0000000..14d792a
--- /dev/null
+++ b/shootout/reverse-complement.txt
@@ -0,0 +1,171 @@
+>ONE Homo sapiens alu
+CGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAAC
+CTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACA
+GGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCAT
+GTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAA
+AGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTC
+TGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGG
+GTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACC
+ACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTG
+GTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTA
+CAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCT
+GGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTC
+TCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCCCGGCTAAT
+TTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTCGAACTCCT
+GACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCA
+CCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGC
+GCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCC
+TCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTA
+GTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGAT
+CCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCT
+TTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTC
+ACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTG
+GGATTACAGGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGT
+TTCACCATGTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGG
+CCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAG
+TCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCG
+CCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGC
+GCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGG
+CCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGC
+TGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCG
+CCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCA
+AGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCC
+CGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTC
+GAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGC
+GTGAGCCACCGCGCCCGGCC
+>TWO IUB ambiguity codes
+TAGGDHACHATCRGTRGVTGAGWTATGYTGCTGTCABACDWVTRTAAGAVVAGATTTNDA
+GASMTCTGCATBYTTCAAKTTACMTATTACTTCATARGGYACMRTGTTTTYTATACVAAT
+TTCTAKGDACKADACTATATNTANTCGTTCACGBCGYSCBHTANGGTGATCGTAAAGTAA
+CTATBAAAAGATSTGWATBCSGAKHTTABBAACGTSYCATGCAAVATKTSKTASCGGAAT
+WVATTTNTCCTTCTTCTTDDAGTGGTTGGATACVGTTAYMTMTBTACTTTHAGCTAGBAA
+AAGAGKAAGTTRATWATCAGATTMDDTTTAAAVAAATATTKTCYTAAATTVCNKTTRACG
+ADTATATTTATGATSADSCAATAWAGCGRTAGTGTAAGTGACVGRADYGTGCTACHVSDT
+CTVCARCSYTTAATATARAAAATTTAATTTACDAATTGBACAGTAYAABATBTGCAGBVG
+TGATGGDCAAAATBNMSTTABKATTGGSTCCTAGBTTACTTGTTTAGTTTATHCGATSTA
+AAGTCGAKAAASTGTTTTAWAKCAGATATACTTTTMTTTTGBATAGAGGAGCMATGATRA
+AAGGNCAYDCCDDGAAAGTHGBTAATCKYTBTACBGTBCTTTTTGDTAASSWTAAWAARA
+TTGGCTAAGWGRADTYACATAGCTCBTAGATAWAGCAATNGTATMATGTTKMMAGTAWTC
+CCNTSGAAWATWCAAAAMACTGAADNTYGATNAATCCGAYWNCTAACGTTAGAGDTTTTC
+ATCTGGKRTAVGAABVCTGWGBTCTDVGKATTBTCTAAGGVADAAAVWTCTAGGGGAGGG
+TTAGAACAATTAAHTAATNAAATGCATKATCTAAYRTDTCAGSAYTTYHGATRTTWAVTA
+BGNTCDACAGBCCRCAGWCRTCABTGMMAWGMCTCAACCGATRTGBCAVAATCGTDWDAA
+CAYAWAATWCTGGTAHCCCTAAGATAACSCTTAGTGSAACAWTBGTCDTTDGACWDBAAC
+HTTTNGSKTYYAAYGGATNTGATTTAARTTAMBAATCTAAGTBTCATYTAACTTADTGTT
+TCGATACGAAHGGCYATATACCWDTKYATDCSHTDTCAAAATGTGBACTGSCCVGATGTA
+TCMMAGCCTTDAAABAATGAAGAGTAACTHATMGVTTAATAACCCGGTTVSANTGCAATT
+GTGAGATTTAMGTTTAMAAYGCTGACAYAAAAAGGCACAMYTAAGVGGCTGGAABVTACG
+GATTSTYGTBVAKTATWACCGTGTKAGTDTGTATGTTTAAAGGAAAAAGTAACATARAAA
+GGTYCAMNYAAABTATAGNTSATANAGTCATCCTATWADKAACTRGTMSACDGTATSAYT
+AAHSHGTAABYGACTYTATADTGSTATAGAGAAATCGNTAAAGGAAATCAGTTGTNCYMV
+TNACDRTATBNATATASTAGAAMSCGGGANRCKKMCAAACATTNAGTCTRMAATBMTACC
+CGTACTTCTBGDSYAATWGAAAATGACADDCHAKAAAYATATTKTTTTCACANACWAGAA
+AKATCCTTATTAYKHKCTAAACARTATTTTDATBTVWCYGCAATACTAGGKAAASTTDGA
+MGGCHTTHAATVCAHDRYAGGRCTATACGTCMAGAGAGCTBTHGNACARTCCBDCTAAGA
+GCGGCTTTARTAAAGAATCCNAGTAWBTGACTTGAATTACWTVACAGAAABCAATNAAAC
+CGTNTRANTTGAYCMAWBADTANABRGGTKTHTWTAGTTVCTMBKTAGMTVKCCAGCANT
+TVAGSWTTAGCCGCRHTTTCCTTHNTATTAAGAAGAATAGGMTRAARTCTABGTACDTTT
+TATAAVDHAHTATAGATCCTAGTAAGYTWATDWCATGAGGGATAGTAAMDMNGBASTWAM
+TSTATRBAYDABATGTATATYCGCACTGTTTTAACMCWBTATAWAGTATBTSTATVTTAR
+CCTMTTAAKADATCAACTAATYTSVTAKGDATTATGCKTCAYCAKAATACTTKAANGAGT
+ATTSDAGATCGGAAATACTTAAYAAVGTATMCGCTTGTGTDCTAATYTATTTTATTTWAA
+CAGWRCTATGTAGMTGTTTGTTYKTNGTTKTCAGAACNTRACCTACKTGSRATGTGGGGG
+CTGTCATTAAGTAAATNGSTTABCCCCTCGCAGCTCWHTCGCGAAGCAVATGCKACGHCA
+ACAKTTAATAACASAAADATTWNYTGTAATTGTTCGTMHACHTWATGTGCWTTTTGAAHY
+ACTTTGTAYAMSAAACTTAADAAATATAGTABMATATYAATGSGGTAGTTTGTGTBYGGT
+TWSGSVGWMATTDMTCCWWCABTCSVACAGBAATGTTKATBGTCAATAATCTTCTTAAAC
+ARVAATHAGYBWCTRWCABGTWWAATCTAAGTCASTAAAKTAAGVKBAATTBGABACGTA
+AGGTTAAATAAAAACTRMDTWBCTTTTTAATAAAAGATMGCCTACKAKNTBAGYRASTGT
+ASSTCGTHCGAAKTTATTATATTYTTTGTAGAACATGTCAAAACTWTWTHGKTCCYAATA
+AAGTGGAYTMCYTAARCSTAAATWAKTGAATTTRAGTCTSSATACGACWAKAASATDAAA
+TGYYACTSAACAAHAKTSHYARGASTATTATTHAGGYGGASTTTBGAKGATSANAACACD
+TRGSTTRAAAAAAAACAAGARTCVTAGTAAGATAWATGVHAAKATWGAAAAGTYAHVTAC
+TCTGRTGTCAWGATRVAAKTCGCAAVCGASWGGTTRTCSAMCCTAACASGWKKAWDAATG
+ACRCBACTATGTGTCTTCAAAHGSCTATATTTCGTVWAGAAGTAYCKGARAKSGKAGTAN
+TTTCYACATWATGTCTAAAADMDTWCAATSTKDACAMAADADBSAAATAGGCTHAHAGTA
+CGACVGAATTATAAAGAHCCVAYHGHTTTACATSTTTATGNCCMTAGCATATGATAVAAG
+>THREE Homo sapiens frequency
+ATATTTATCTTTTCACTTCCTACATTGGTCAGACCATTATTCGACACGTGGCGTCATTTT
+GTCATACCGGGTAATGTTGGAAACAAAACGTACTGATAAAATACTGAGTTGTAAACTCTA
+ATCAGATAACGCGCTTGGATATTAAGATTCACACAGGGGTTTCGGCTGTAAAAAAACTTG
+TGGAGCTGTTCTGGGACAGATAAGTTGTACCTCGTACTTAGCTAATTAATGAACCAACTG
+ATTACGATAGAACAATTCTGAGGCCGCCAGGACAGCCAAATTTTAATCTTATAAAGCTGG
+AAACAGCCGGTATTAGCTTCTCGCATACTTTGCCTGCATTGGTACCTTACAGATATCAGC
+GTAGTCATATACACCTCGGTCTCAGCTAAGCTTGTATCTCTTAGAGTAGTTCAAAGATAG
+TGGACAATACCTGTGGAATCGATTGCAGATATGGATTTATTTAACTACTGAGTCTCATTC
+ACAAGCTAAGCAAGGAGCACGTTTTGGTGCCGGCATACCGATTTGCTATCATGTCAGCAA
+ATTTGCGTTGTATTCCTAGTTGCACCCATTAAGGCCACACTCCGAACCTAATTATTACAT
+CGCAAAGACATGTACGAAGGACCCGATGTCGAATAGAAGGGAGGACTGTTCATTGGAAGC
+TAGACCAGAGGAATCGCAAAGATGCAACTCTTACAATAAAAATCTAATTTCAGTCAACAC
+GCAATTTCTATAAGGTTTCCGATAATAATGAACCGTCTTCCACAGGGGAATTTGCCATGC
+TCGTAAAAGTAGTTAATCCAAGTAGAAGAAATTTTGATAATGTTTTAAGTTGGCACGAAG
+GAATTCAGAGAGATCTTACCTAACAAAGGCATTAGTAGATGTTCCTTGGTTCACACTCGG
+TCAATCAGAGCACATACTACGGGCGATACCGGGAATGACACAACATCAATGAGATTGTTA
+AGTGAGGTAATTGACTTTAGAGGACTCGATCAGTATACTGTCACTATGAACATCGTATTA
+ATTGTTATCCGATATATACACCACCGATTTGCTTGTGCAAGGTTACAGACCCATTCGATA
+AATACAAACACGGAGCGATATTATTTAAGGAGTGCTGTCTTCAAAAGAATTATTCCCACA
+CCGACATAAGAACTTCGCTCCGTCATTCCAGATTTAAATAACATAACGTAACGCTTTGCT
+GATAACATAACATAACCGAGAATTTGCTTAGGAAATTTGGAGCAATATTGCATTGTTTCT
+CAGTCATCACAAGGCCCGCCAAAGAACTCTGAGAATCAGGATTCAACATGATTGGTAAGA
+CTCTATATATATAACTTAATTCTTGTGTCCGGAGATAGAAAGAGGACGAGAGATACTACG
+AAAGAAAGTGTACTTCGATGTATCAATTCAGACGCCTTCTCTATCATCAACATTATAGGT
+CTCGTATATGCTCGGCGCGATCTGCTTCTCTCCGCCAATAGCCCCATAGTGTATTTCAAG
+CGCAGTAACAGTGAAATCGTTACGAAGGTAGGGATGTTGCTTATAATTGTCGTAACTTAT
+CGCTTATGTATCTTTCAAGAATGAACGGCAGCATATACATACGTTCTACCTTTAGCTACA
+AAGCATCCATATACTCCCTCTCATGATTGAAACTCTTCCCTATTTTGTAGCCAATAGTGA
+AAGCGTATTAGTATAAATTCGTCGGTTTTTCACTCGCAACTGTTATACTCTGCAAACAAA
+CGAAAGCCTCATAGTACAAACCTAAAGCTACATACTTCATCATTGGCAGACCAGTGGCGG
+TATTTCTACGGAAGCATCACTATAGATATAAAGTTTCCCTTCATGTACGTCTGTTAACCA
+TATCACAAGAAACTGCTATCTCTGTCACGTAACAATTCACGCGCCTTATCGCCAAATGTT
+CATATATGCGCGGTATACGTATGAACGAATACTAATTAGTATAACGGAGGATTCACGGGA
+GGGATACTTGGGGCATTTATAAATCGTCTAAAAATTTTCTATCAGCACTTGCGGGTTATA
+GTGGATTACTAGGCAACATAATATTCTGTATTGGTCCAAATGACGCTATAGATAAATTAG
+CAAAATACATTGTTTCCATTTATGTAAGTCGAAACTCCAGGACTCCCGGGAACCAGTTAA
+ACCGTCTGGAAAAGACACATTGTGAGCGGGACTTCAATGATAGCTTTCAATGAGCTTCTC
+ATGCTTGGGGTCTGTACATATATGTTGGCGAAATTATCGTCTGTATTCTGTTATGCTTTG
+ATCATGGGTTATTAGTATAGTGTCCGGTTAAGTACCAATACCGCTAGAGACCCGACCTAA
+GTCGATAACTAACGATCATCGACGTAAGGATCGTCTCGATCAGTACTTCAGTCTAGATCT
+GGGAATAGTAACTCGTTAGTGAACTATGTCGTGTCATAACTCTAAAATGCAATCAAATCT
+TATTATTGAGTATTGATTATATAAAGCATCCGCTTAGCTTTACCCTCAAATGTTATATGC
+AATTTAAAGCGCTTGATATCGTCTACTCAAGTTCAGGTTTCACATGGCCGCAACGTGACG
+TTATTAGAGGTGGGTCATCATCTCTGAGGCTAGTGATGTTGAATACTCATTGAATGGGAA
+GTGGAATACCATGCTCGTAGGTAACAGCATGACCTATAAAATATACTATGGGTGTGTGGT
+AGATCAATATTGTTCAAGCATATCGTAACAATAACGGCTGAAATGTTACTGACATGAAAG
+AGGGAGTCCAAACCATTCTAACAGCTGATCAAGTCGTCTAAAAACGCCTGGTTCAGCCTT
+AAGAGTTATAAGCCAGACAAATTGTATCAATAGAGAATCCGTAAATTCCTCGGCCAACCT
+CTTGCAAAGACATCACTATCAATATACTACCGTGATCTTAATTAGTGAACTTATATAAAT
+ATCTACAACCAGATTCAACGGAAAAGCTTTAGTGGATTAGAAATTGCCAAGAATCACATT
+CATGTGGGTTCGAATGCTTTAGTAATACCATTTCGCCGAGTAGTCACTTCGCTGAACTGT
+CGTAAATTGCTATGACATAATCGAAAAGGATTGTCAAGAGTCGATTACTGCGGACTAATA
+ATCCCCACGGGGGTGGTCTCATGTCTCCCCAGGCGAGTGGGGACGGTTGATAAACACGCT
+GCATCGCGGACTGATGTTCCCAGTATTACATAGTCACATTGGATTGCGAGTAGTCTACCT
+ATTTATGAGCGAGAGATGCCTCTAACTACTTCGACTTTTAAAACCTTTCCACGCCAGTAT
+TCGGCGAAAGGGAAGTATTAAGGGTTGTCATAATTAAGCTGATACCACTTCAGACTTTGC
+TCTACTTCTGTCTTTCATTGGTTTAGTAAAGTCTGTCCATTCGTCGAGACCGTCTTTTGC
+AGCCTCATTCTACCAACTGCTCCGACTCTTAGTCTGCTTCTCCCAGCGTTATAACAAGAG
+GCATTTTGTCATCCTTAAAACAATAATAAAGAACTCGGAGCACTGATATAATGACTGAAT
+TAGAACCGCTTAAAAATACAACGAATAGATAAGACTATCGGATAAGATCTAATATGTAGT
+GATTAAGCCCTTTATTAATTAATAATAGTTACCCTTTCTGATGTAACGCGACATATTACG
+ATTTAGTGGCACGTCTGAATTGCAAAGCAGATCTCTACCCGATTTTTATTATAAATCCCG
+TATACATCTTGACTTGAGTAATTGTTCATCTTTTTATATCTCTTCGTACTACAAATAATT
+AATATCTCAACCCGTATTGTGTGATTCTAATTACCAACAGAATACGAGGAGGTTTTTGCT
+TAGGGCCATATATAATGAATCTATCTCGTTTATTCGCGGAACCCGAGATAACATTACGAT
+GTAACTATTTTAGAGAACTTAATACAAGAAACATTGCTGATTACTCATAACTAAATGCTT
+GGTAATATATCCTCAGTGCCCCTACCATCTTTTACGCAGGGATGTAATTACTTAGGATTC
+ATTGTGTAAGAATTACAATGAACGATGGATATGAAGGCATGTTGCGAGGTGTTCCTTGGT
+ATGTGAAGTTCGCAGGGCAACAAAAATTTCGCAGAATAGGCCTCAAAGTATTGGTAAAGA
+AGACAACTAATCATCACGAGCTTCTGATATCAATACGAACGAGTCCTGTGATGGATGAAA
+GAAAGTCGTATCGAAAATGTCAAGAGTCTGCCCAATGTAACTTACTTCAAAAAATAACGC
+TTCCGCCAAGTACGTTCGAATAAACGTAATTTTAAAAATACATAAGGGGTGTTAGAAAGT
+AAGCGACGGGATATAAGTTAGACTCAAGATTCCGCCGTAAAACGAGACTGATTCCGAAGA
+TTGTTCGTGGATCTGGTCATGACTTTCACTGAGTAAGGAGTTTCGACATATGTCAATAAA
+CACAAAAATAGAAGCTATTCGATCTGAAAAATATTAGGACAAGAAACTATCTCACGCTAG
+CCCAGAATATTCACTCACCCACGGGCGATACTAAAGCACTATATAGTCGCGTGATTACTA
+TACATATGGTACACATAAGAATCACGATCAGGTTCTCAATTTTCAACAATATATGTTTAT
+TTGCATAGGTAATATTAGGCCTTTAAGAGAAGGATGGGTGAGATACTCCGGGGATGGCGG
+CAATAAAGAAAAACACGATATGAGTAATAGGATCCTAATATCTTGGCGAGAGACTTAAGG
+TACGAATTTTGCGCAATCTATTTTTTACTTGGCCAGAATTCATGTATGGTATAAGTACGA
+ACTTTTTTGATCACTTTCATGGCTACCTGATTAGGATAGTTTGAGGAATTTCCCAAATAT
+ACCGATTTAATATACACTAGGGCTTGTCACTTTGAGTCAGAAAAAGAATATAATTACTTA
+GGGTAATGCTGCATACATATTCTTATATTGCAAAGGTTCTCTGGGTAATCTTGAGCCTTC
+ACGATACCTGGTGAAGTGTT
diff --git a/shootout/spectral-norm-parallel.go b/shootout/spectral-norm-parallel.go
new file mode 100644
index 0000000..5ad4e20
--- /dev/null
+++ b/shootout/spectral-norm-parallel.go
@@ -0,0 +1,114 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * Based on spectral-norm.c by Sebastien Loisel
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "math"
+ "runtime"
+)
+
+var n = flag.Int("n", 2000, "count")
+var nCPU = flag.Int("ncpu", 4, "number of cpus")
+
+func evalA(i, j int) float64 { return 1 / float64(((i+j)*(i+j+1)/2 + i + 1)) }
+
+type Vec []float64
+
+func (v Vec) Times(i, n int, u Vec, c chan int) {
+ for ; i < n; i++ {
+ v[i] = 0
+ for j := 0; j < len(u); j++ {
+ v[i] += evalA(i, j) * u[j]
+ }
+ }
+ c <- 1
+}
+
+func (v Vec) TimesTransp(i, n int, u Vec, c chan int) {
+ for ; i < n; i++ {
+ v[i] = 0
+ for j := 0; j < len(u); j++ {
+ v[i] += evalA(j, i) * u[j]
+ }
+ }
+ c <- 1
+}
+
+func wait(c chan int) {
+ for i := 0; i < *nCPU; i++ {
+ <-c
+ }
+}
+
+func (v Vec) ATimesTransp(u Vec) {
+ x := make(Vec, len(u))
+ c := make(chan int, *nCPU)
+ for i := 0; i < *nCPU; i++ {
+ go x.Times(i*len(v) / *nCPU, (i+1)*len(v) / *nCPU, u, c)
+ }
+ wait(c)
+ for i := 0; i < *nCPU; i++ {
+ go v.TimesTransp(i*len(v) / *nCPU, (i+1)*len(v) / *nCPU, x, c)
+ }
+ wait(c)
+}
+
+func main() {
+ flag.Parse()
+ runtime.GOMAXPROCS(*nCPU)
+ N := *n
+ u := make(Vec, N)
+ for i := 0; i < N; i++ {
+ u[i] = 1
+ }
+ v := make(Vec, N)
+ for i := 0; i < 10; i++ {
+ v.ATimesTransp(u)
+ u.ATimesTransp(v)
+ }
+ var vBv, vv float64
+ for i := 0; i < N; i++ {
+ vBv += u[i] * v[i]
+ vv += v[i] * v[i]
+ }
+ fmt.Printf("%0.9f\n", math.Sqrt(vBv/vv))
+}
diff --git a/shootout/spectral-norm.c b/shootout/spectral-norm.c
new file mode 100644
index 0000000..9247543
--- /dev/null
+++ b/shootout/spectral-norm.c
@@ -0,0 +1,84 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* -*- mode: c -*-
+ *
+ * The Great Computer Language Shootout
+ * http://shootout.alioth.debian.org/
+ *
+ * Contributed by Sebastien Loisel
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+double eval_A(int i, int j) { return 1.0/((i+j)*(i+j+1)/2+i+1); }
+
+void eval_A_times_u(int N, const double u[], double Au[])
+{
+ int i,j;
+ for(i=0;i<N;i++)
+ {
+ Au[i]=0;
+ for(j=0;j<N;j++) Au[i]+=eval_A(i,j)*u[j];
+ }
+}
+
+void eval_At_times_u(int N, const double u[], double Au[])
+{
+ int i,j;
+ for(i=0;i<N;i++)
+ {
+ Au[i]=0;
+ for(j=0;j<N;j++) Au[i]+=eval_A(j,i)*u[j];
+ }
+}
+
+void eval_AtA_times_u(int N, const double u[], double AtAu[])
+{ double v[N]; eval_A_times_u(N,u,v); eval_At_times_u(N,v,AtAu); }
+
+int main(int argc, char *argv[])
+{
+ int i;
+ int N = ((argc == 2) ? atoi(argv[1]) : 2000);
+ double u[N],v[N],vBv,vv;
+ for(i=0;i<N;i++) u[i]=1;
+ for(i=0;i<10;i++)
+ {
+ eval_AtA_times_u(N,u,v);
+ eval_AtA_times_u(N,v,u);
+ }
+ vBv=vv=0;
+ for(i=0;i<N;i++) { vBv+=u[i]*v[i]; vv+=v[i]*v[i]; }
+ printf("%0.9f\n",sqrt(vBv/vv));
+ return 0;
+}
diff --git a/shootout/spectral-norm.go b/shootout/spectral-norm.go
new file mode 100644
index 0000000..fb41051
--- /dev/null
+++ b/shootout/spectral-norm.go
@@ -0,0 +1,96 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ * Based on spectral-norm.c by Sebastien Loisel
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "math"
+)
+
+var n = flag.Int("n", 2000, "count")
+
+func evalA(i, j int) float64 { return 1 / float64(((i+j)*(i+j+1)/2 + i + 1)) }
+
+type Vec []float64
+
+func (v Vec) Times(u Vec) {
+ for i := 0; i < len(v); i++ {
+ v[i] = 0
+ for j := 0; j < len(u); j++ {
+ v[i] += evalA(i, j) * u[j]
+ }
+ }
+}
+
+func (v Vec) TimesTransp(u Vec) {
+ for i := 0; i < len(v); i++ {
+ v[i] = 0
+ for j := 0; j < len(u); j++ {
+ v[i] += evalA(j, i) * u[j]
+ }
+ }
+}
+
+func (v Vec) ATimesTransp(u Vec) {
+ x := make(Vec, len(u))
+ x.Times(u)
+ v.TimesTransp(x)
+}
+
+func main() {
+ flag.Parse()
+ N := *n
+ u := make(Vec, N)
+ for i := 0; i < N; i++ {
+ u[i] = 1
+ }
+ v := make(Vec, N)
+ for i := 0; i < 10; i++ {
+ v.ATimesTransp(u)
+ u.ATimesTransp(v)
+ }
+ var vBv, vv float64
+ for i := 0; i < N; i++ {
+ vBv += u[i] * v[i]
+ vv += v[i] * v[i]
+ }
+ fmt.Printf("%0.9f\n", math.Sqrt(vBv/vv))
+}
diff --git a/shootout/spectral-norm.txt b/shootout/spectral-norm.txt
new file mode 100644
index 0000000..b988598
--- /dev/null
+++ b/shootout/spectral-norm.txt
@@ -0,0 +1 @@
+1.274224152
diff --git a/shootout/threadring.c b/shootout/threadring.c
new file mode 100644
index 0000000..59bb59d
--- /dev/null
+++ b/shootout/threadring.c
@@ -0,0 +1,115 @@
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/*
+* The Computer Language Benchmarks Game
+* http://shootout.alioth.debian.org/
+
+* contributed by Premysl Hruby
+*/
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <string.h>
+#include <limits.h>
+
+// PTHREAD_STACK_MIN undeclared on mingw
+#ifndef PTHREAD_STACK_MIN
+#define PTHREAD_STACK_MIN 65535
+#endif
+
+#define THREADS (503)
+
+struct stack {
+ char x[PTHREAD_STACK_MIN];
+};
+
+
+/* staticaly initialize mutex[0] mutex */
+static pthread_mutex_t mutex[THREADS];
+static int data[THREADS];
+static struct stack stacks[THREADS];
+/* stacks must be defined staticaly, or my i386 box run of virtual memory for this
+ * process while creating thread +- #400 */
+
+static void* thread(void *num)
+{
+ int l = (int)(uintptr_t)num;
+ int r = (l+1) % THREADS;
+ int token;
+
+ while(1) {
+ pthread_mutex_lock(mutex + l);
+ token = data[l];
+ if (token) {
+ data[r] = token - 1;
+ pthread_mutex_unlock(mutex + r);
+ }
+ else {
+ printf("%i\n", l+1);
+ exit(0);
+ }
+ }
+}
+
+
+
+int main(int argc, char **argv)
+{
+ int i;
+ pthread_t cthread;
+ pthread_attr_t stack_attr;
+
+ if (argc != 2)
+ exit(255);
+ data[0] = atoi(argv[1]);
+
+ pthread_attr_init(&stack_attr);
+
+ for (i = 0; i < THREADS; i++) {
+ pthread_mutex_init(mutex + i, NULL);
+ pthread_mutex_lock(mutex + i);
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+ pthread_attr_setstackaddr(&stack_attr, &stacks[i]);
+ pthread_attr_setstacksize(&stack_attr, sizeof(struct stack));
+#else
+ pthread_attr_setstack(&stack_attr, &stacks[i], sizeof(struct stack));
+#endif
+
+ pthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);
+ }
+
+ pthread_mutex_unlock(mutex + 0);
+ pthread_join(cthread, NULL);
+}
diff --git a/shootout/threadring.go b/shootout/threadring.go
new file mode 100644
index 0000000..035867f
--- /dev/null
+++ b/shootout/threadring.go
@@ -0,0 +1,74 @@
+//go:build ignore
+// +build ignore
+
+/*
+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.
+
+ * Neither the name of "The Computer Language Benchmarks Game" nor the
+ name of "The Computer Language Shootout Benchmarks" 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 OWNER 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.
+*/
+
+/* The Computer Language Benchmarks Game
+ * http://shootout.alioth.debian.org/
+ *
+ * contributed by The Go Authors.
+ */
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+)
+
+var n = flag.Int("n", 1000, "how many passes")
+
+const Nthread = 503
+
+func f(i int, in <-chan int, out chan<- int) {
+ for {
+ n := <-in
+ if n == 0 {
+ fmt.Printf("%d\n", i)
+ os.Exit(0)
+ }
+ out <- n - 1
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ one := make(chan int) // will be input to thread 1
+ var in, out chan int = nil, one
+ for i := 1; i <= Nthread-1; i++ {
+ in, out = out, make(chan int)
+ go f(i, in, out)
+ }
+ go f(Nthread, out, one)
+ one <- *n
+ <-make(chan int) // hang until ring completes
+}
diff --git a/shootout/threadring.txt b/shootout/threadring.txt
new file mode 100644
index 0000000..f9aaa4d
--- /dev/null
+++ b/shootout/threadring.txt
@@ -0,0 +1 @@
+498
diff --git a/shootout/timing.log b/shootout/timing.log
new file mode 100644
index 0000000..4e7d17a
--- /dev/null
+++ b/shootout/timing.log
@@ -0,0 +1,1254 @@
+All tests on r45 or r70
+
+Aug 3 2009
+
+First version of fasta. Translation of fasta.c, fetched from
+ http://shootout.alioth.debian.org/u32q/benchmark.php?test=fasta&lang=gpp&id=4
+
+fasta -n 25000000
+ gcc -O2 fasta.c 5.98u 0.00s 6.01r
+ gccgo -O2 fasta.go 8.82u 0.02s 8.85r
+ 6g fasta.go 13.50u 0.02s 13.53r
+ 6g -B fata.go 12.99u 0.02s 13.02r
+
+Aug 4 2009
+[added timing.sh]
+
+# myrandom:
+# hand-written optimization of integer division
+# use int32->float conversion
+fasta -n 25000000
+ # probably I/O library inefficiencies
+ gcc -O2 fasta.c 5.99u 0.00s 6.00r
+ gccgo -O2 fasta.go 8.82u 0.02s 8.85r
+ gc fasta 10.70u 0.00s 10.77r
+ gc_B fasta 10.09u 0.03s 10.12r
+
+reverse-complement < output-of-fasta-25000000
+ # we don't know - memory cache behavior?
+ gcc -O2 reverse-complement.c 2.04u 0.94s 10.54r
+ gccgo -O2 reverse-complement.go 6.54u 0.63s 7.17r
+ gc reverse-complement 6.55u 0.70s 7.26r
+ gc_B reverse-complement 6.32u 0.70s 7.10r
+
+nbody 50000000
+ # math.Sqrt needs to be in assembly; inlining is probably the other 50%
+ gcc -O2 nbody.c 21.61u 0.01s 24.80r
+ gccgo -O2 nbody.go 118.55u 0.02s 120.32r
+ gc nbody 100.84u 0.00s 100.85r
+ gc_B nbody 103.33u 0.00s 103.39r
+[
+hacked Sqrt in assembler
+ gc nbody 31.97u 0.00s 32.01r
+]
+
+binary-tree 15 # too slow to use 20
+ # memory allocation and garbage collection
+ gcc -O2 binary-tree.c -lm 0.86u 0.00s 0.87r
+ gccgo -O2 binary-tree.go 1.69u 0.46s 2.15r
+ gccgo -O2 binary-tree-freelist.go 8.48u 0.00s 8.48r
+ gc binary-tree 9.60u 0.01s 9.62r
+ gc binary-tree-freelist 0.48u 0.01s 0.50r
+
+August 5, 2009
+
+fannkuch 12
+ # bounds checking is half the difference
+ # rest might be registerization
+ gcc -O2 fannkuch.c 60.09u 0.01s 60.32r
+ gccgo -O2 fannkuch.go 64.89u 0.00s 64.92r
+ gc fannkuch 124.59u 0.00s 124.67r
+ gc_B fannkuch 91.14u 0.00s 91.16r
+
+regex-dna 100000
+ # regexp code is slow on trivial regexp
+ gcc -O2 regex-dna.c -lpcre 0.92u 0.00s 0.99r
+ gc regexp-dna 26.94u 0.18s 28.75r
+ gc_B regexp-dna 26.51u 0.09s 26.75r
+
+spectral-norm 5500
+ gcc -O2 spectral-norm.c -lm 11.54u 0.00s 11.55r
+ gccgo -O2 spectral-norm.go 12.20u 0.00s 12.23r
+ gc spectral-norm 50.23u 0.00s 50.36r
+ gc_B spectral-norm 49.69u 0.01s 49.83r
+ gc spectral-norm-parallel 24.47u 0.03s 11.05r # has shift >>1 not div /2
+ [using >>1 instead of /2 : gc gives 24.33u 0.00s 24.33r]
+
+August 6, 2009
+
+k-nucleotide 5000000
+ # string maps are slower than glib string maps
+ gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0 k-nucleotide.c: 10.72u 0.01s 10.74r
+ gccgo -O2 k-nucleotide.go 21.64u 0.83s 22.78r
+ gc k-nucleotide 16.08u 0.06s 16.50r
+ gc_B k-nucleotide 17.32u 0.02s 17.37r
+
+mandelbrot 5500
+ # floating point code generator should use more registers
+ gcc -O2 mandelbrot.c 56.13u 0.02s 56.17r
+ gccgo -O2 mandelbrot.go 57.49u 0.01s 57.51r
+ gc mandelbrot 74.32u 0.00s 74.35r
+ gc_B mandelbrot 74.28u 0.01s 74.31r
+
+meteor 2100
+ # we don't know
+ gcc -O2 meteor-contest.c 0.10u 0.00s 0.10r
+ gccgo -O2 meteor-contest.go 0.12u 0.00s 0.14r
+ gc meteor-contest 0.24u 0.00s 0.26r
+ gc_B meteor-contest 0.23u 0.00s 0.24r
+
+pidigits 10000
+ # bignum is slower than gmp
+ gcc -O2 pidigits.c -lgmp 2.60u 0.00s 2.62r
+ gc pidigits 77.69u 0.14s 78.18r
+ gc_B pidigits 74.26u 0.18s 75.41r
+ gc_B pidigits 68.48u 0.20s 69.31r # special case: no bounds checking in bignum
+
+August 7 2009
+
+# New gc does better division by powers of 2. Significant improvements:
+
+spectral-norm 5500
+ # floating point code generator should use more registers; possibly inline evalA
+ gcc -O2 spectral-norm.c -lm 11.50u 0.00s 11.50r
+ gccgo -O2 spectral-norm.go 12.02u 0.00s 12.02r
+ gc spectral-norm 23.98u 0.00s 24.00r # new time is 0.48 times old time, 52% faster
+ gc_B spectral-norm 23.71u 0.01s 23.72r # ditto
+ gc spectral-norm-parallel 24.04u 0.00s 6.26r # /2 put back. note: 4x faster (on r70, idle)
+
+k-nucleotide 1000000
+ # string maps are slower than glib string maps
+ gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0 10.82u 0.04s 10.87r
+ gccgo -O2 k-nucleotide.go 22.73u 0.89s 23.63r
+ gc k-nucleotide 15.97u 0.03s 16.04r
+ gc_B k-nucleotide 15.86u 0.06s 15.93r # 8.5% faster, but probably due to weird cache effeccts in previous version
+
+pidigits 10000
+ # bignum is slower than gmp
+ gcc -O2 pidigits.c -lgmp 2.58u 0.00s 2.58r
+ gc pidigits 71.24u 0.04s 71.28r # 8.5% faster
+ gc_B pidigits 71.25u 0.03s 71.29r # 4% faster
+
+threadring 50000000
+ gcc -O2 threadring.c -lpthread 35.51u 160.21s 199.50r
+ gccgo -O2 threadring.go 90.33u 459.95s 448.03r
+ gc threadring 33.11u 0.00s 33.14r
+ GOMAXPROCS=4 gc threadring 114.48u 226.65s 371.59r
+ # change wait code to do <-make(chan int) instead of time.Sleep
+ gc threadring 28.41u 0.01s 29.35r
+ GOMAXPROCS=4 gc threadring 112.59u 232.83s 384.72r
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 18.14u 276.52s 76.93r
+ gc chameneosredux 20.19u 0.01s 20.23r
+
+Aug 10 2009
+
+# new 6g with better fp registers, fast div and mod of integers
+# complete set of timings listed. significant changes marked ***
+
+fasta -n 25000000
+ # probably I/O library inefficiencies
+ gcc -O2 fasta.c 5.96u 0.00s 5.97r
+ gc fasta 10.59u 0.01s 10.61r
+ gc_B fasta 9.92u 0.02s 9.95r
+
+reverse-complement < output-of-fasta-25000000
+ # we don't know - memory cache behavior?
+ gcc -O2 reverse-complement.c 1.96u 1.56s 16.23r
+ gccgo -O2 reverse-complement.go 6.41u 0.62s 7.05r
+ gc reverse-complement 6.46u 0.70s 7.17r
+ gc_B reverse-complement 6.22u 0.72s 6.95r
+
+nbody 50000000
+ # math.Sqrt needs to be in assembly; inlining is probably the other 50%
+ gcc -O2 nbody.c 21.26u 0.01s 21.28r
+ gccgo -O2 nbody.go 116.68u 0.07s 116.80r
+ gc nbody 86.64u 0.01s 86.68r # -14%
+ gc_B nbody 85.72u 0.02s 85.77r # *** -17%
+
+binary-tree 15 # too slow to use 20
+ # memory allocation and garbage collection
+ gcc -O2 binary-tree.c -lm 0.87u 0.00s 0.87r
+ gccgo -O2 binary-tree.go 1.61u 0.47s 2.09r
+ gccgo -O2 binary-tree-freelist.go 0.00u 0.00s 0.01r
+ gc binary-tree 9.11u 0.01s 9.13r # *** -5%
+ gc binary-tree-freelist 0.47u 0.01s 0.48r
+
+fannkuch 12
+ # bounds checking is half the difference
+ # rest might be registerization
+ gcc -O2 fannkuch.c 59.92u 0.00s 59.94r
+ gccgo -O2 fannkuch.go 65.54u 0.00s 65.58r
+ gc fannkuch 123.98u 0.01s 124.04r
+ gc_B fannkuch 90.75u 0.00s 90.78r
+
+regex-dna 100000
+ # regexp code is slow on trivial regexp
+ gcc -O2 regex-dna.c -lpcre 0.91u 0.00s 0.92r
+ gc regex-dna 27.25u 0.02s 27.28r
+ gc_B regex-dna 29.51u 0.03s 29.55r
+
+spectral-norm 5500
+ # possibly inline evalA
+ gcc -O2 spectral-norm.c -lm 11.57u 0.00s 11.57r
+ gccgo -O2 spectral-norm.go 12.07u 0.01s 12.08r
+ gc spectral-norm 23.99u 0.00s 24.00r
+ gc_B spectral-norm 23.73u 0.00s 23.75r
+
+k-nucleotide 1000000
+ # string maps are slower than glib string maps
+ gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0 10.63u 0.02s 10.69r
+ gccgo -O2 k-nucleotide.go 23.19u 0.91s 24.12r
+ gc k-nucleotide 16.73u 0.04s 16.78r # *** +5% (but this one seems to vary by more than that)
+ gc_B k-nucleotide 16.46u 0.04s 16.51r # *** +5%
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 56.16u 0.00s 56.16r
+ gccgo -O2 mandelbrot.go 57.41u 0.01s 57.42r
+ gc mandelbrot 64.05u 0.02s 64.08r # *** -14%
+ gc_B mandelbrot 64.10u 0.02s 64.14r # *** -14%
+
+meteor 2100
+ # we don't know
+ gcc -O2 meteor-contest.c 0.10u 0.00s 0.10r
+ gccgo -O2 meteor-contest.go 0.12u 0.00s 0.12r
+ gc meteor-contest 0.18u 0.00s 0.20r # *** -25%
+ gc_B meteor-contest 0.17u 0.00s 0.18r # *** -24%
+
+pidigits 10000
+ # bignum is slower than gmp
+ gcc -O2 pidigits.c -lgmp 2.57u 0.00s 2.57r
+ gc pidigits 71.82u 0.04s 71.89r
+ gc_B pidigits 71.84u 0.08s 71.98r
+
+threadring 50000000
+ gcc -O2 threadring.c -lpthread 30.91u 164.33s 204.57r
+ gccgo -O2 threadring.go 87.12u 460.04s 447.61r
+ gc threadring 38.55u 0.00s 38.56r # *** +16%
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 17.93u 323.65s 88.47r
+ gc chameneosredux 21.72u 0.00s 21.73r
+
+August 10 2009
+
+# In-place versions for some bignum operations.
+pidigits 10000
+ gcc -O2 pidigits.c -lgmp 2.56u 0.00s 2.57r
+ gc pidigits 55.22u 0.04s 55.29r # *** -23%
+ gc_B pidigits 55.49u 0.02s 55.60r # *** -23%
+
+September 3 2009
+
+# New 6g inlines slices, has a few other tweaks.
+# Complete rerun. Significant changes marked.
+
+fasta -n 25000000
+ # probably I/O library inefficiencies
+ gcc -O2 fasta.c 5.96u 0.00s 5.96r
+ gc fasta 10.63u 0.02s 10.66r
+ gc_B fasta 9.92u 0.01s 9.94r
+
+reverse-complement < output-of-fasta-25000000
+ # we don't know - memory cache behavior?
+ gcc -O2 reverse-complement.c 1.92u 0.33s 2.93r
+ gccgo -O2 reverse-complement.go 6.76u 0.72s 7.58r # +5%
+ gc reverse-complement 6.59u 0.70s 7.29r # +2%
+ gc_B reverse-complement 5.57u 0.80s 6.37r # -10%
+
+nbody 50000000
+ # math.Sqrt needs to be in assembly; inlining is probably the other 50%
+ # also loop alignment appears to be critical
+ gcc -O2 nbody.c 21.28u 0.00s 21.28r
+ gccgo -O2 nbody.go 119.21u 0.00s 119.22r # +2%
+ gc nbody 109.72u 0.00s 109.78r # + 28% *****
+ gc_B nbody 85.90u 0.00s 85.91r
+
+binary-tree 15 # too slow to use 20
+ # memory allocation and garbage collection
+ gcc -O2 binary-tree.c -lm 0.86u 0.00s 0.87r
+ gccgo -O2 binary-tree.go 1.88u 0.54s 2.42r # +17%
+ gccgo -O2 binary-tree-freelist.go 0.01u 0.01s 0.02r
+ gc binary-tree 8.94u 0.01s 8.96r # -2%
+ gc binary-tree-freelist 0.47u 0.01s 0.48r
+
+fannkuch 12
+ # bounds checking is half the difference
+ # rest might be registerization
+ gcc -O2 fannkuch.c 60.12u 0.00s 60.12r
+ gccgo -O2 fannkuch.go 92.62u 0.00s 92.66r # +41% ***
+ gc fannkuch 123.90u 0.00s 123.92r
+ gc_B fannkuch 89.71u 0.00s 89.74r # -1%
+
+regex-dna 100000
+ # regexp code is slow on trivial regexp
+ gcc -O2 regex-dna.c -lpcre 0.88u 0.00s 0.88r
+ gc regex-dna 25.77u 0.01s 25.79r # -5%
+ gc_B regex-dna 26.05u 0.02s 26.09r # -12% ***
+
+spectral-norm 5500
+ # possibly inline evalA
+ gcc -O2 spectral-norm.c -lm 11.51u 0.00s 11.51r
+ gccgo -O2 spectral-norm.go 11.95u 0.00s 11.96r
+ gc spectral-norm 24.23u 0.00s 24.23r
+ gc_B spectral-norm 23.83u 0.00s 23.84r
+
+k-nucleotide 1000000
+ # string maps are slower than glib string maps
+ gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0 10.68u 0.04s 10.72r
+ gccgo -O2 k-nucleotide.go 23.03u 0.88s 23.92r
+ gc k-nucleotide 15.79u 0.05s 15.85r # -5% (but this one seems to vary by more than that)
+ gc_B k-nucleotide 17.88u 0.05s 17.95r # +8% (ditto)
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 56.17u 0.02s 56.20r
+ gccgo -O2 mandelbrot.go 56.74u 0.02s 56.79r # -1%
+ gc mandelbrot 63.31u 0.01s 63.35r # -1%
+ gc_B mandelbrot 63.29u 0.00s 63.31r # -1%
+
+meteor 2100
+ # we don't know
+ gcc -O2 meteor-contest.c 0.10u 0.00s 0.10r
+ gccgo -O2 meteor-contest.go 0.11u 0.00s 0.12r
+ gc meteor-contest 0.18u 0.00s 0.19r
+ gc_B meteor-contest 0.17u 0.00s 0.18r
+
+pidigits 10000
+ # bignum is slower than gmp
+ gcc -O2 pidigits.c -lgmp 2.56u 0.00s 2.57r
+ gc pidigits 55.87u 0.03s 55.91r
+ gc_B pidigits 55.93u 0.03s 55.99r
+
+# these tests are compared using real time, since they run multiple processors
+# accuracy probably low
+threadring 50000000
+ gcc -O2 threadring.c -lpthread 26.31u 164.69s 199.92r # -2%
+ gccgo -O2 threadring.go 87.90u 487.26s 472.81r # +6%
+ gc threadring 28.89u 0.00s 28.90r # -25% ***
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 16.41u 296.91s 81.17r # -8%
+ gc chameneosredux 19.97u 0.00s 19.97r # -8%
+
+Sep 22, 2009
+
+# 6g inlines sliceslice in most cases.
+
+fasta -n 25000000
+ # probably I/O library inefficiencies
+ gc fasta 10.24u 0.00s 10.25r # -4%
+ gc_B fasta 9.68u 0.01s 9.69r # -3%
+
+reverse-complement < output-of-fasta-25000000
+ # we don't know - memory cache behavior?
+ gc reverse-complement 6.67u 0.69s 7.37r # +1%
+ gc_B reverse-complement 6.00u 0.64s 6.65r # +7%
+
+nbody -n 50000000
+ # math.Sqrt needs to be in assembly; inlining is probably the other 50%
+ # also loop alignment appears to be critical
+ gc nbody 86.27u 0.00s 86.29r # -21%
+ gc_B nbody 104.52u 0.00s 104.54r # +22%
+
+fannkuch 12
+ # bounds checking is half the difference
+ # rest might be registerization
+ gc fannkuch 128.36u 0.00s 128.37r # +4%
+ gc_B fannkuch 89.32u 0.00s 89.34r
+
+regex-dna 100000
+ # regexp code is slow on trivial regexp
+ gc regex-dna 24.82u 0.01s 24.86r # -4%
+ gc_B regex-dna 24.55u 0.01s 24.57r # -6%
+
+spectral-norm 5500
+ # possibly inline evalA
+ gc spectral-norm 24.05u 0.00s 24.07r # -1%
+ gc_B spectral-norm 23.60u 0.00s 23.65r # -1%
+
+k-nucleotide 1000000
+ # string maps are slower than glib string maps
+ gc k-nucleotide 17.84u 0.04s 17.89r # +13% but mysterious variation continues
+ gc_B k-nucleotide 15.56u 0.08s 15.65r # -13% (ditto)
+
+mandelbrot 16000
+ gc mandelbrot 64.08u 0.01s 64.11r # +1%
+ gc_B mandelbrot 64.04u 0.00s 64.05r # +1%
+
+pidigits 10000
+ # bignum is slower than gmp
+ gc pidigits 58.68u 0.02s 58.72r # +5%
+ gc_B pidigits 58.86u 0.05s 58.99r # +5%
+
+# these tests are compared using real time, since they run multiple processors
+# accuracy probably low
+threadring 50000000
+ gc threadring 32.70u 0.02s 32.77r # +13%
+
+chameneos 6000000
+ gc chameneosredux 26.62u 0.00s 26.63r # +13%
+
+Sep 24, 2009
+
+# Sqrt now in assembler for 6g.
+nbody -n 50000000
+ # remember, at least for 6g, alignment of loops may be important
+ gcc -O2 nbody.c 21.24u 0.00s 21.25r
+ gccgo -O2 nbody.go 121.03u 0.00s 121.04r
+ gc nbody 30.26u 0.00s 30.27r # -65% ***
+ gc_B nbody 30.20u 0.02s 30.22r # -72% ***
+
+Nov 13 2009
+
+# fix bug in regexp; take performance hit. good regexps will come in time.
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.92u 0.00s 0.94r
+ gc regex-dna 29.78u 0.03s 29.83r
+ gc_B regex-dna 32.63u 0.03s 32.74r
+
+Nov 24 2009
+
+# Roger Peppe's rewrite of the benchmark
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 18.00u 303.29s 83.64r
+ gc chameneosredux 12.10u 0.00s 12.10r # 2.22X faster
+
+Jan 6, 2010
+
+# Long-overdue update. All numbers included in this complete run.
+# Some programs (e.g. reverse-complement) rewritten for speed.
+# Regular expressions much faster in common cases (although still far behind PCRE)
+# Bignum stuff improved
+# Better (but sometimes slower) locking in channels.
+
+fasta -n 25000000
+ gcc -O2 fasta.c 5.99u 0.01s 6.00r
+ gc fasta 9.11u 0.00s 9.12r # -11%
+ gc_B fasta 8.60u 0.00s 8.62r # +12% ??
+
+reverse-complement < output-of-fasta-25000000
+ gcc -O2 reverse-complement.c 2.00u 0.80s 9.54r
+# gccgo -O2 reverse-complement.go 4.57u 0.35s 4.94r # 33% faster
+ gc reverse-complement 2.01u 0.38s 2.40r # 3.3X faster
+ gc_B reverse-complement 1.88u 0.36s 2.24r # 3.2X faster
+GOGC=off
+ gc reverse-complement 2.01u 0.35s 2.37r
+ gc_B reverse-complement 1.86u 0.32s 2.19r
+
+nbody -n 50000000
+ gcc -O2 nbody.c 21.28u 0.00s 21.31r
+ gccgo -O2 nbody.go 80.02u 0.00s 80.05r # 33% faster
+ gc nbody 30.13u 0.00s 30.13r
+ gc_B nbody 29.89u 0.01s 29.91r
+
+binary-tree 15 # too slow to use 20
+ gcc -O2 binary-tree.c -lm 0.86u 0.00s 0.87r
+ gccgo -O2 binary-tree.go 4.82u 0.41s 5.24r # 2.5X slower
+ gc binary-tree 7.23u 0.01s 7.25r # # -19%
+ gc binary-tree-freelist 0.43u 0.00s 0.44r # -9%
+
+fannkuch 12
+ gcc -O2 fannkuch.c 60.17u 0.00s 60.17r
+ gccgo -O2 fannkuch.go 78.47u 0.01s 78.49r
+ gc fannkuch 128.86u 0.00s 128.96r
+ gc_B fannkuch 90.17u 0.00s 90.21r
+
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.90u 0.00s 0.92r
+ gc regex-dna 9.48u 0.01s 9.50r # 3.1X faster
+ gc_B regex-dna 9.08u 0.00s 9.10r # 3.6X faster
+
+spectral-norm 5500
+ gcc -O2 spectral-norm.c -lm 11.48u 0.00s 11.48r
+ gccgo -O2 spectral-norm.go 11.68u 0.00s 11.70r
+ gc spectral-norm 23.98u 0.00s 23.99r
+ gc_B spectral-norm 23.68u 0.00s 23.69r
+
+k-nucleotide 1000000
+ gcc -O2 k-nucleotide.c 10.85u 0.04s 10.90r
+ gccgo -O2 k-nucleotide.go 25.26u 0.87s 26.14r
+ gc k-nucleotide 15.28u 0.06s 15.37r # restored; mysterious variation continues
+ gc_B k-nucleotide 15.97u 0.03s 16.00r
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 56.12u 0.01s 56.15r
+ gccgo -O2 mandelbrot.go 56.86u 0.01s 56.89r
+ gc mandelbrot 66.05u 0.00s 66.07r # -3%
+ gc_B mandelbrot 66.06u 0.00s 66.07r # -3%
+
+meteor 2100
+ gcc -O2 meteor-contest.c 0.10u 0.00s 0.10r
+ gccgo -O2 meteor-contest.go 0.12u 0.00s 0.12r
+ gc meteor-contest 0.17u 0.00s 0.17r
+ gc_B meteor-contest 0.15u 0.00s 0.16r
+
+pidigits 10000
+ gcc -O2 pidigits.c -lgmp 2.57u 0.00s 2.59r
+ gc pidigits 38.27u 0.02s 38.30r # 1.5X faster
+ gc_B pidigits 38.27u 0.02s 38.31r # 1.5X faster
+
+threadring 50000000
+ gcc -O2 threadring.c 37.11u 170.59s 212.75r
+ gccgo -O2 threadring.go 89.67u 447.56s 442.55r # -6.5%
+ gc threadring 36.08u 0.04s 36.15r # +10%
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 19.02u 331.08s 90.79r
+ gc chameneosredux 12.54u 0.00s 12.55r
+
+Oct 19, 2010
+
+# Another long-overdue update. Some of the code is new; parallel versions
+# of some are added. A few significant improvements.
+
+fasta -n 25000000
+ gcc -O2 fasta.c 4.92u 0.00s 4.93r
+ gccgo -O2 fasta.go 3.31u 0.00s 3.34r # new code
+ gc fasta 3.68u 0.00s 3.69r # 2.5X faster with no code
+ gc_B fasta 3.68u 0.00s 3.69r # 2.3X faster with no code
+
+reverse-complement < output-of-fasta-25000000
+ gcc -O2 reverse-complement.c 1.93u 0.81s 11.24r
+ gccgo -O2 reverse-complement.go 1.58u 0.43s 2.04r # first run with new code?
+ gc reverse-complement 1.84u 0.34s 2.20r # 10% faster
+ gc_B reverse-complement 1.85u 0.32s 2.18r
+
+nbody -n 50000000
+ gcc -O2 nbody.c 21.35u 0.00s 21.36r
+ gccgo -O2 nbody.go 21.62u 0.00s 21.66r # 3.7X faster - why??
+ gc nbody 29.78u 0.00s 29.79r
+ gc_B nbody 29.72u 0.00s 29.72r
+
+binary-tree 15 # too slow to use 20
+ gcc -O2 binary-tree.c -lm 0.86u 0.00s 0.88r
+ gccgo -O2 binary-tree.go 4.05u 0.02s 4.08r # 28% faster
+ gccgo -O2 binary-tree-freelist 0.34u 0.08s 0.34r
+ gc binary-tree 5.94u 0.00s 5.95r # 20% faster
+ gc binary-tree-freelist 0.50u 0.01s 0.54r
+
+fannkuch 12
+ gcc -O2 fannkuch.c 60.45u 0.00s 60.45r
+ gccgo -O2 fannkuch.go 64.64u 0.00s 64.64r
+ gccgo -O2 fannkuch-parallel.go 115.63u 0.00s 31.58r
+ gc fannkuch 126.52u 0.04s 126.68r
+ gc fannkuch-parallel 238.82u 0.10s 65.93r # GOMAXPROCS=4
+ gc_B fannkuch 88.99u 0.00s 89.02r
+
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.89u 0.00s 0.89r
+ gc regex-dna 8.99u 0.02s 9.03r
+ gc regex-dna-parallel 8.94u 0.02s 3.68r # GOMAXPROCS=4
+ gc_B regex-dna 9.12u 0.00s 9.14r
+
+spectral-norm 5500
+ gcc -O2 spectral-norm.c -lm 11.55u 0.00s 11.57r
+ gccgo -O2 spectral-norm.go 11.73u 0.00s 11.75r
+ gc spectral-norm 23.74u 0.00s 23.79r
+ gc_B spectral-norm 24.49u 0.02s 24.54r
+
+k-nucleotide 1000000
+ gcc -O2 k-nucleotide.c 11.44u 0.06s 11.50r
+ gccgo -O2 k-nucleotide.go 8.65u 0.04s 8.71r
+ gccgo -O2 k-nucleotide-parallel.go 8.75u 0.03s 2.97r # set GOMAXPROCS=4
+ gc k-nucleotide 14.92u 0.05s 15.01r
+ gc k-nucleotide-parallel 16.96u 0.06s 6.53r # set GOMAXPROCS=4
+ gc_B k-nucleotide 15.97u 0.03s 16.08r
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 56.32u 0.00s 56.35r
+ gccgo -O2 mandelbrot.go 55.62u 0.02s 55.77r
+ gc mandelbrot 64.85u 0.01s 64.94r
+ gc_B mandelbrot 65.02u 0.01s 65.14r
+
+meteor 2100
+ gcc -O2 meteor-contest.c 0.10u 0.00s 0.10r
+ gccgo -O2 meteor-contest.go 0.10u 0.00s 0.11r
+ gc meteor-contest 0.17u 0.00s 0.18r
+ gc_B meteor-contest 0.16u 0.00s 0.16r
+
+pidigits 10000
+ gcc -O2 pidigits.c -lgmp 2.58u 0.00s 2.59r
+ gccgo -O2 pidigits.go 14.06u 0.01s 14.09r # first run?
+ gc pidigits 8.47u 0.05s 8.55r # 4.5X faster due to package big
+ gc_B pidigits 8.33u 0.01s 8.36r # 4.5X faster due to package big
+
+threadring 50000000
+ gcc -O2 threadring.c 28.18u 153.19s 186.47r
+ gccgo -O2 threadring.go 110.10u 516.48s 515.25r
+ gc threadring 40.39u 0.00s 40.40r
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 18.20u 301.55s 83.10r
+ gccgo -O2 chameneosredux.go 52.22u 324.54s 201.21r
+ gc chameneosredux 13.52u 0.00s 13.54r
+
+Dec 14, 2010
+
+# Improved regex code (same algorithm) gets ~30%.
+
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.77u 0.01s 0.78r
+ gc regex-dna 6.80u 0.00s 6.81r
+ gc regex-dna-parallel 6.82u 0.01s 2.75r
+ gc_B regex-dna 6.69u 0.02s 6.70r
+
+Feb 15, 2011
+
+# Improved GC, still single-threaded but more efficient
+
+fasta -n 25000000
+ gcc -O2 fasta.c 3.40u 0.00s 3.40r
+ gccgo -O2 fasta.go 3.51u 0.00s 3.50r
+ gc fasta 3.66u 0.01s 3.66r
+ gc_B fasta 3.66u 0.00s 3.66r
+
+reverse-complement < output-of-fasta-25000000
+ gcc -O2 reverse-complement.c 1.86u 1.29s 4.93r
+ gccgo -O2 reverse-complement.go 2.18u 0.41s 2.60r
+ gc reverse-complement 1.67u 0.48s 2.15r
+ gc_B reverse-complement 1.71u 0.45s 2.15r
+
+nbody -n 50000000
+ gcc -O2 -lm nbody.c 21.64u 0.00s 21.64r
+ gccgo -O2 nbody.go 21.46u 0.00s 21.45r
+ gc nbody 29.07u 0.00s 29.06r
+ gc_B nbody 31.61u 0.00s 31.61r
+
+binary-tree 15 # too slow to use 20
+ gcc -O2 binary-tree.c -lm 0.88u 0.00s 0.87r
+ gccgo -O2 binary-tree.go 2.74u 0.07s 2.81r
+ gccgo -O2 binary-tree-freelist.go 0.01u 0.00s 0.00r
+ gc binary-tree 4.22u 0.02s 4.24r
+ gc binary-tree-freelist 0.54u 0.02s 0.55r
+
+fannkuch 12
+ gcc -O2 fannkuch.c 57.64u 0.00s 57.64r
+ gccgo -O2 fannkuch.go 65.79u 0.00s 65.82r
+ gccgo -O2 fannkuch-parallel.go 160.91u 0.02s 43.90r
+ gc fannkuch 126.36u 0.03s 126.53r
+ gc fannkuch-parallel 175.23u 0.04s 45.49r
+ gc_B fannkuch 89.23u 0.00s 89.24r
+
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.77u 0.01s 0.80r
+ gccgo -O2 regex-dna.go 12.38u 0.10s 12.52r
+ gccgo -O2 regex-dna-parallel.go 43.96u 4.64s 15.11r
+ gc regex-dna 7.03u 0.01s 7.05r
+ gc regex-dna-parallel 6.85u 0.05s 2.70r
+ gc_B regex-dna 6.87u 0.02s 6.89r
+
+spectral-norm 5500
+ gcc -O2 spectral-norm.c -lm 12.29u 0.00s 12.28r
+ gccgo -O2 spectral-norm.go 11.79u 0.00s 11.79r
+ gc spectral-norm 24.00u 0.02s 24.05r
+ gc_B spectral-norm 24.59u 0.01s 24.59r
+
+k-nucleotide 1000000
+ gcc -O2 k-nucleotide.c 9.75u 0.07s 9.82r
+ gccgo -O2 k-nucleotide.go 8.92u 0.06s 8.98r
+ gccgo -O2 k-nucleotide-parallel.go 8.40u 0.04s 2.76r
+ gc k-nucleotide 17.01u 0.03s 17.04r
+ gc k-nucleotide-parallel 16.51u 0.08s 6.21r
+ gc_B k-nucleotide 16.94u 0.08s 17.02r
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 54.60u 0.00s 54.66r
+ gccgo -O2 mandelbrot.go 59.38u 0.00s 59.41r
+ gc mandelbrot 64.93u 0.04s 65.08r
+ gc_B mandelbrot 64.85u 0.03s 64.92r
+
+meteor 2098
+ gcc -O2 meteor-contest.c 0.10u 0.01s 0.10r
+ gccgo -O2 meteor-contest.go 0.11u 0.00s 0.11r
+ gc meteor-contest 0.18u 0.00s 0.17r
+ gc_B meteor-contest 0.17u 0.00s 0.16r
+
+pidigits 10000
+ gcc -O2 pidigits.c -lgmp 2.24u 0.00s 2.23r
+ gccgo -O2 pidigits.go 14.05u 0.00s 14.06r
+ gc pidigits 6.34u 0.05s 6.38r
+ gc_B pidigits 6.37u 0.02s 6.38r
+
+threadring 50000000
+ gcc -O2 threadring.c 30.50u 258.05s 325.72r
+ gccgo -O2 threadring.go 92.87u 748.39s 728.46r
+ gc threadring 38.03u 0.01s 38.04r
+
+# Apr 15, 2011
+# Move to new machine, Intel Xeon E5520@2.27GHz.
+# (Was Opteron(tm) Processor 8214 HE)
+
+fasta -n 25000000
+OLD:
+ gcc -O2 fasta.c 3.39u 0.04s 3.42r
+ gccgo -O2 fasta.go 3.52u 0.00s 3.52r
+ gc fasta 3.63u 0.04s 3.67r
+ gc_B fasta 3.66u 0.00s 3.66r
+NEW:
+ gcc -O2 fasta.c 1.45u 0.02s 1.47r
+ gccgo -O2 fasta.go 1.51u 0.01s 1.51r
+ gc fasta 2.04u 0.00s 2.04r
+ gc_B fasta 2.05u 0.00s 2.04r
+
+reverse-complement < output-of-fasta-25000000
+OLD:
+ gcc -O2 reverse-complement.c 1.87u 1.51s 7.02r
+ gccgo -O2 reverse-complement.go 1.56u 0.54s 3.37r
+ gc reverse-complement 1.73u 0.36s 2.08r
+ gc_B reverse-complement 1.75u 0.37s 2.12r
+NEW:
+ gcc -O2 reverse-complement.c 1.20u 0.47s 12.96r
+ gccgo -O2 reverse-complement.go 0.88u 0.14s 1.01r
+ gc reverse-complement 1.13u 0.17s 1.30r
+ gc_B reverse-complement 1.11u 0.09s 1.20r
+
+nbody -n 50000000
+OLD:
+ gcc -O2 -lm nbody.c 21.90u 0.00s 21.92r
+ gccgo -O2 nbody.go 23.12u 0.03s 23.19r
+ gc nbody 29.07u 0.00s 29.07r
+ gc_B nbody 31.84u 0.00s 31.85r
+NEW:
+ gcc -O2 -lm nbody.c 13.01u 0.00s 13.03r
+ gccgo -O2 nbody.go 13.35u 0.00s 13.37r
+ gc nbody 21.78u 0.00s 21.82r
+ gc_B nbody 21.72u 0.00s 21.76r
+
+binary-tree 15 # too slow to use 20
+OLD:
+ gcc -O2 binary-tree.c -lm 0.83u 0.02s 0.84r
+ gccgo -O2 binary-tree.go 2.61u 0.02s 2.62r
+ gccgo -O2 binary-tree-freelist.go 0.32u 0.01s 0.32r
+ gc binary-tree 3.93u 0.04s 3.97r
+ gc binary-tree-freelist 0.47u 0.03s 0.50r
+NEW:
+ gcc -O2 binary-tree.c -lm 0.60u 0.00s 0.59r
+ gccgo -O2 binary-tree.go 1.53u 0.00s 1.52r
+ gccgo -O2 binary-tree-freelist.go 0.01u 0.00s 0.00r
+ gc binary-tree 1.93u 0.02s 1.95r
+ gc binary-tree-freelist 0.32u 0.01s 0.32r
+
+fannkuch 12
+OLD:
+ gcc -O2 fannkuch.c 57.64u 0.00s 57.64r
+ gccgo -O2 fannkuch.go 65.56u 0.01s 65.65r
+ gccgo -O2 fannkuch-parallel.go 179.12u 0.00s 49.82r
+ gc fannkuch 126.39u 0.00s 126.39r
+ gc fannkuch-parallel 172.49u 0.02s 45.44r
+ gc_B fannkuch 89.30u 0.00s 89.28r
+NEW:
+ gcc -O2 fannkuch.c 45.17u 0.00s 45.26r
+ gccgo -O2 fannkuch.go 53.63u 0.00s 53.73r
+ gccgo -O2 fannkuch-parallel.go 216.72u 0.00s 58.42r
+ gc fannkuch 108.21u 0.00s 108.44r
+ gc fannkuch-parallel 227.20u 0.00s 57.27r
+ gc_B fannkuch 56.14u 0.00s 56.26r
+
+regex-dna 100000
+OLD:
+ gcc -O2 regex-dna.c -lpcre 0.77u 0.01s 0.78r
+ gccgo -O2 regex-dna.go 10.15u 0.02s 10.23r
+ gccgo -O2 regex-dna-parallel.go 33.81u 3.22s 11.62r
+ gc regex-dna 6.52u 0.04s 6.56r
+ gc regex-dna-parallel 6.84u 0.03s 2.70r
+ gc_B regex-dna 6.83u 0.01s 6.84r
+NEW:
+ gcc -O2 regex-dna.c -lpcre 0.47u 0.00s 0.47r
+ gccgo -O2 regex-dna.go 6.00u 0.00s 6.00r
+ gccgo -O2 regex-dna-parallel.go 44.54u 1.57s 6.51r
+ gc regex-dna 5.41u 0.01s 5.42r
+ gc regex-dna-parallel 5.62u 0.01s 2.20r
+ gc_B regex-dna 5.50u 0.00s 5.50r
+
+spectral-norm 5500
+OLD:
+ gcc -O2 spectral-norm.c -lm 12.29u 0.00s 12.28r
+ gccgo -O2 spectral-norm.go 11.56u 0.00s 11.55r
+ gc spectral-norm 23.98u 0.00s 24.00r
+ gc_B spectral-norm 24.62u 0.00s 24.65r
+NEW:
+ gcc -O2 spectral-norm.c -lm 15.79u 0.00s 15.82r
+ gccgo -O2 spectral-norm.go 15.32u 0.00s 15.35r
+ gc spectral-norm 19.62u 0.01s 19.67r
+ gc_B spectral-norm 19.62u 0.00s 19.66r
+
+k-nucleotide 1000000
+OLD:
+ gcc -O2 k-nucleotide.c 9.82u 0.06s 9.87r
+ gccgo -O2 k-nucleotide.go 8.30u 0.02s 8.32r
+ gccgo -O2 k-nucleotide-parallel.go 8.84u 0.05s 3.02r
+ gc k-nucleotide 15.38u 0.07s 15.44r
+ gc k-nucleotide-parallel 16.40u 0.03s 5.93r
+ gc_B k-nucleotide 15.19u 0.05s 15.23r
+NEW:
+ gcc -O2 -k-nucleotide.c 4.88u 0.03s 4.92r
+ gccgo -O2 k-nucleotide.go 5.94u 0.01s 5.96r
+ gccgo -O2 k-nucleotide-parallel.go 6.44u 0.03s 1.47r
+ gc k-nucleotide 9.61u 0.01s 9.63r
+ gc k-nucleotide-parallel 9.70u 0.00s 3.39r
+ gc_B k-nucleotide 9.19u 0.03s 9.23r
+
+mandelbrot 16000
+OLD:
+ gcc -O2 mandelbrot.c 54.54u 0.00s 54.56r
+ gccgo -O2 mandelbrot.go 59.63u 0.03s 59.67r
+ gc mandelbrot 64.82u 0.00s 64.83r
+ gc_B mandelbrot 64.84u 0.00s 64.91r
+NEW:
+ gcc -O2 mandelbrot.c 36.07u 0.01s 36.15r
+ gccgo -O2 mandelbrot.go 43.57u 0.00s 43.66r
+ gc mandelbrot 60.66u 0.00s 60.79r
+ gc_B mandelbrot 60.90u 0.00s 61.03r
+
+meteor 2098
+OLD:
+ gcc -O2 meteor-contest.c 0.11u 0.00s 0.10r
+ gccgo -O2 meteor-contest.go 0.10u 0.01s 0.10r
+ gc meteor-contest 0.18u 0.00s 0.17r
+ gc_B meteor-contest 0.17u 0.00s 0.16r
+NEW:
+ gcc -O2 meteor-contest.c 0.10u 0.00s 0.09r
+ gccgo -O2 meteor-contest.go 0.10u 0.00s 0.09r
+ gc meteor-contest 0.14u 0.00s 0.14r
+ gc_B meteor-contest 0.13u 0.00s 0.13r
+
+pidigits 10000
+OLD:
+ gcc -O2 pidigits.c -lgmp 2.22u 0.00s 2.21r
+ gccgo -O2 pidigits.go 13.39u 0.00s 13.40r
+ gc pidigits 6.42u 0.04s 6.45r
+ gc_B pidigits 6.45u 0.02s 6.47r
+NEW:
+ gcc -O2 pidigits.c -lgmp 2.27u 0.00s 2.29r
+ gccgo -O2 pidigits.go 9.21u 0.00s 9.22r
+ gc pidigits 3.60u 0.00s 3.60r
+ gc_B pidigits 3.56u 0.02s 3.58r
+
+threadring 50000000
+OLD:
+ gcc -O2 threadring.c -lpthread 34.51u 267.95s 336.12r
+ gccgo -O2 threadring.go 103.51u 588.57s 627.16r
+ gc threadring 54.68u 0.00s 54.73r
+NEW:
+ gcc -O2 threadring.c 32.00u 259.39s 369.74r
+ gccgo -O2 threadring.go 133.06u 546.02s 595.33r
+ gc threadring 16.75u 0.02s 16.80r
+
+chameneos 6000000
+OLD:
+ gcc -O2 chameneosredux.c -lpthread 12.65u 31.02s 13.33r
+ gccgo -O2 chameneosredux.go 47.04u 302.84s 252.29r
+ gc chameneosredux 14.14u 0.00s 14.14r
+NEW:
+ gcc -O2 chameneosredux.c -lpthread 8.05u 63.43s 11.16r
+ gccgo -O2 chameneosredux.go 82.95u 304.37s 207.64r
+ gc chameneosredux 9.42u 0.00s 9.43r
+
+# May 13, 2011
+# after gc update to inline append when possible - 35% faster
+
+regex-dna 100000
+ gc regex-dna 3.94u 0.00s 3.95r
+ gc regex-dna-parallel 4.15u 0.01s 1.63r
+ gc_B regex-dna 4.01u 0.01s 4.02r
+
+# Aug 4, 2011
+# After various updates to locking code and some runtime changes.
+# Slowdowns believed due to slower (but more correct) memmove.
+
+fannkuch 12
+ gccgo -O2 fannkuch.go 51.59u 0.00s 51.69r # -4%
+ gccgo -O2 fannkuch-parallel.go 253.17u 0.00s 64.67r # -11%
+ gc fannkuch 103.14u 0.00s 103.36r # -5%
+ gc fannkuch-parallel 189.63u 0.00s 49.37r # +9%
+ gc_B fannkuch 49.19u 0.00s 49.29r # -14%
+
+regex-dna 100000
+ gc regex-dna 3.78u 0.00s 3.78r # -43%
+ gc regex-dna-parallel 3.84u 0.02s 1.48r # -49%
+ gc_B regex-dna 3.62u 0.00s 3.63r # -52%
+
+k-nucleotide 1000000
+ gc k-nucleotide 12.23u 0.02s 12.27r # +27%
+ gc k-nucleotide-parallel 12.76u 0.02s 4.37r # +29%
+ gc_B k-nucleotide 12.18u 0.01s 12.21r # +33%
+
+threadring 50000000
+ gc threadring 17.49u 0.00s 17.53r # +4%
+
+chameneos 6000000
+ gc chameneosredux 7.61u 0.00s 7.63r # -24%
+
+Aug 9, 2011
+# After custom algorithms for 1- 2- 4- 8-byte scalars.
+
+fannkuch 12
+ gc fannkuch-parallel 157.17u 0.00s 41.08r # -17%
+
+k-nucleotide 1000000
+ gc k-nucleotide 8.72u 0.03s 8.76r # -39%
+ gc k-nucleotide-parallel 8.79u 0.01s 3.14r # -39%
+ gc_B k-nucleotide 8.65u 0.03s 8.69r # -39%
+
+pidigits 10000
+ gc pidigits 3.71u 0.02s 3.73r # +4%
+ gc_B pidigits 3.73u 0.00s 3.73r # +4%
+
+threadring 50000000
+ gc threadring 14.51u 0.00s 14.54r # -17%
+
+chameneos 6000000
+ gc chameneosredux 7.41u 0.00s 7.42r # -3%
+
+# A complete run at the Go 1 release.
+# Significant changes:
+# - gccgo is now enabled for all tests (goroutines are cheap enough)
+# - threadring and chameneos are 14% faster, probably due to runtime changes
+# - regex-dna 36% faster
+# - fannkuch-parallel (only) slowed down 40%
+# - gccgo on binary-tree-freelist is still optimized to nothing
+# Other changes are modest.
+
+fasta -n 25000000
+ gcc -O2 fasta.c 1.45u 0.02s 1.48r
+ gccgo -O2 fasta.go 1.46u 0.00s 1.47r
+ gc fasta 1.99u 0.01s 2.00r
+ gc_B fasta 1.99u 0.01s 2.01r
+
+reverse-complement < output-of-fasta-25000000
+ gcc -O2 reverse-complement.c 0.95u 0.48s 4.99r
+ gccgo -O2 reverse-complement.go 0.93u 0.16s 1.09r
+ gc reverse-complement 1.20u 0.19s 1.39r
+ gc_B reverse-complement 1.04u 0.16s 1.20r
+
+nbody -n 50000000
+ gcc -O2 -lm nbody.c 13.02u 0.00s 13.05r
+ gccgo -O2 nbody.go 14.46u 0.00s 14.49r
+ gc nbody 21.79u 0.00s 21.84r
+ gc_B nbody 21.74u 0.00s 21.79r
+
+binary-tree 15 # too slow to use 20
+ gcc -O2 binary-tree.c -lm 0.60u 0.01s 0.61r
+ gccgo -O2 binary-tree.go 1.30u 0.01s 1.32r
+ gccgo -O2 binary-tree-freelist.go 0.00u 0.00s 0.00r
+ gc binary-tree 1.84u 0.01s 1.86r
+ gc binary-tree-freelist 0.33u 0.00s 0.33r
+
+fannkuch 12
+ gcc -O2 fannkuch.c 45.24u 0.00s 45.34r
+ gccgo -O2 fannkuch.go 59.76u 0.01s 59.90r
+ gccgo -O2 fannkuch-parallel.go 218.20u 0.01s 61.60r
+ gc fannkuch 103.92u 0.00s 104.16r
+ gc fannkuch-parallel 221.61u 0.00s 60.49r
+ gc_B fannkuch 53.17u 0.00s 53.30r
+
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.47u 0.00s 0.48r
+ gccgo -O2 regex-dna.go 6.52u 0.00s 6.54r
+ gccgo -O2 regex-dna-parallel.go 14.40u 0.73s 4.35r
+ gc regex-dna 2.63u 0.02s 2.66r # -36%
+ gc regex-dna-parallel 2.87u 0.01s 1.11r
+ gc_B regex-dna 2.65u 0.00s 2.66r
+
+spectral-norm 5500
+ gcc -O2 spectral-norm.c -lm 15.78u 0.00s 15.82r
+ gccgo -O2 spectral-norm.go 15.79u 0.00s 15.83r
+ gc spectral-norm 19.76u 0.00s 19.80r
+ gc_B spectral-norm 19.73u 0.01s 19.78r
+
+k-nucleotide 1000000
+ gcc -O2 k-nucleotide.c 5.59u 0.03s 5.63r
+ gccgo -O2 k-nucleotide.go 4.09u 0.03s 4.13r
+ gccgo -O2 k-nucleotide-parallel.go 4.50u 0.06s 1.63r
+ gc k-nucleotide 9.23u 0.02s 9.27r
+ gc k-nucleotide-parallel 9.87u 0.03s 3.55r
+ gc_B k-nucleotide 9.20u 0.00s 9.22r
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 36.09u 0.00s 36.18r
+ gccgo -O2 mandelbrot.go 41.69u 0.01s 41.80r
+ gc mandelbrot 60.91u 0.02s 61.07r
+ gc_B mandelbrot 60.90u 0.00s 61.04r
+
+meteor 2098
+ gcc -O2 meteor-contest.c 0.09u 0.00s 0.09r
+ gccgo -O2 meteor-contest.go 0.09u 0.00s 0.09r
+ gc meteor-contest 0.14u 0.00s 0.15r
+ gc_B meteor-contest 0.14u 0.00s 0.14r
+
+pidigits 10000
+ gcc -O2 pidigits.c -lgmp 2.27u 0.00s 2.27r
+ gccgo -O2 pidigits.go 8.65u 0.00s 8.67r
+ gc pidigits 3.70u 0.04s 3.75r
+ gc_B pidigits 3.72u 0.02s 3.75r
+
+threadring 50000000
+ gcc -O2 threadring.c 40.91u 369.85s 323.31r
+ gccgo -O2 threadring.go 26.97u 30.82s 57.93r
+ gc threadring 12.81u 0.01s 12.85r # -13%
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 9.44u 72.90s 12.65r
+ gccgo -O2 chameneosredux.go 7.73u 7.53s 15.30r
+ gc chameneosredux 6.51u 0.00s 6.53r # - 14%
+
+# After http://codereview.appspot.com/6248049, moving panicindex
+# calls out of line (putting the likely code into a single path and shortening
+# loops). Significant changes since the last run (note: some are slower for
+# unrelated and as yet undiagnosed reasons):
+
+nbody -n 50000000
+ gc nbody 19.10u 0.01s 19.19r # -12%
+ gc_B nbody 19.19u 0.00s 19.23r # -12%
+
+binary-tree 15 # too slow to use 20
+ gc binary-tree 1.49u 0.01s 1.51r # -19%
+
+fannkuch 12
+ gc fannkuch 60.79u 0.00s 60.92r # -41%
+ gc fannkuch-parallel 183.51u 0.01s 51.75r # -14%
+ gc_B fannkuch 51.68u 0.00s 51.79r # -3%
+
+k-nucleotide 1000000
+ gc k-nucleotide 9.74u 0.04s 9.80r # +6%
+ gc k-nucleotide-parallel 9.89u 0.05s 3.59r # +1%
+ gc_B k-nucleotide 9.39u 0.02s 9.43r # +2%
+
+mandelbrot (much slower, due to unrelated http://codereview.appspot.com/6209077)
+ gc mandelbrot 100.98u 0.00s 101.20r # +65%
+ gc_B mandelbrot 100.90u 0.01s 101.17r # +65%
+
+meteor 2098
+ gc meteor-contest 0.13u 0.00s 0.13r # -13%
+ gc_B meteor-contest 0.13u 0.00s 0.13r # -7%
+
+# May 30, 2012.
+# After http://codereview.appspot.com/6261051, restoring old code generated
+# for floating-point constants. Mandelbrot is back to its previous numbers.
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 36.07u 0.00s 36.16r
+ gccgo -O2 mandelbrot.go 41.72u 0.01s 41.90r
+ gc mandelbrot 60.62u 0.00s 60.76r
+ gc_B mandelbrot 60.68u 0.00s 60.82r
+
+# May 30, 2012.
+# After http://codereview.appspot.com/6248068, better FP code
+# by avoiding MOVSD between registers.
+# Plus some other timing changes that have crept in from other speedups,
+# from garbage collection to Printf.
+
+fasta -n 25000000
+ gc fasta 1.76u 0.00s 1.76r # -12%
+ gc_B fasta 1.71u 0.00s 1.72r # -12%
+
+nbody -n 50000000
+ gc nbody 17.56u 0.00s 17.60r # -8%
+ gc_B nbody 17.30u 0.00s 17.34r # -10%
+
+fannkuch 12
+ gc fannkuch-parallel 155.92u 0.01s 44.05r # -15%
+
+k-nucleotide 1000000
+ gc k-nucleotide 9.22u 0.01s 9.26r # -5%
+ gc k-nucleotide-parallel 9.23u 0.03s 3.26r # -9%
+ gc_B k-nucleotide 9.22u 0.03s 9.28r # -2%
+
+mandelbrot 16000
+ gc mandelbrot 44.80u 0.00s 44.90r # -27%
+ gc_B mandelbrot 44.81u 0.00s 44.92r # -26%
+
+pidigits 10000
+ gc pidigits 3.51u 0.00s 3.52r # -6%
+ gc_B pidigits 3.51u 0.00s 3.52r # -6%
+
+# Aug 28, 2012
+# After some assembler work in package big.
+pidigits 10000
+ gc pidigits 2.85u 0.02s 2.88r # -22%
+ gc_B pidigits 2.88u 0.01s 2.90r # -21%
+
+# Sep 26, 2012
+# 64-bit ints, plus significantly better floating-point code.
+# Interesting details:
+# Generally something in the 0-10% slower range, some (binary tree) more
+# Floating-point noticeably faster:
+# nbody -25%
+# mandelbrot -37% relative to Go 1.
+# Other:
+# regex-dna +47%
+fasta -n 25000000
+ gcc -O2 fasta.c 1.43u 0.03s 1.46r
+ gccgo -O2 fasta.go 1.47u 0.00s 1.47r
+ gc fasta 1.78u 0.01s 1.80r
+ gc_B fasta 1.76u 0.00s 1.76r
+
+reverse-complement < output-of-fasta-25000000
+ gcc -O2 reverse-complement.c 1.14u 0.39s 11.19r
+ gccgo -O2 reverse-complement.go 0.91u 0.17s 1.09r
+ gc reverse-complement 1.12u 0.18s 1.31r
+ gc_B reverse-complement 1.12u 0.15s 1.28r
+
+nbody -n 50000000
+ gcc -O2 nbody.c -lm 13.02u 0.00s 13.05r
+ gccgo -O2 nbody.go 13.90u 0.00s 13.93r
+ gc nbody 17.05u 0.00s 17.09r
+ gc_B nbody 16.30u 0.00s 16.34r
+
+binary-tree 15 # too slow to use 20
+ gcc -O2 binary-tree.c -lm 0.61u 0.00s 0.61r
+ gccgo -O2 binary-tree.go 1.24u 0.04s 1.29r
+ gccgo -O2 binary-tree-freelist.go 0.21u 0.01s 0.22r
+ gc binary-tree 1.93u 0.02s 1.96r
+ gc binary-tree-freelist 0.32u 0.00s 0.33r
+
+fannkuch 12
+ gcc -O2 fannkuch.c 45.19u 0.00s 45.29r
+ gccgo -O2 fannkuch.go 60.32u 0.00s 60.45r
+ gccgo -O2 fannkuch-parallel.go 185.59u 0.00s 59.49r
+ gc fannkuch 72.14u 0.00s 72.30r
+ gc fannkuch-parallel 172.54u 0.00s 43.59r
+ gc_B fannkuch 53.55u 0.00s 53.67r
+
+regex-dna 100000
+ gcc -O2 regex-dna.c -lpcre 0.47u 0.00s 0.47r
+ gccgo -O2 regex-dna.go 6.49u 0.05s 6.56r
+ gccgo -O2 regex-dna-parallel.go 14.60u 0.67s 4.42r
+ gc regex-dna 3.91u 0.00s 3.92r
+ gc regex-dna-parallel 4.01u 0.03s 1.56r
+ gc_B regex-dna 3.91u 0.00s 3.92r
+
+spectral-norm 5500
+ gcc -O2 spectral-norm.c -lm 15.85u 0.00s 15.89r
+ gccgo -O2 spectral-norm.go 15.86u 0.00s 15.89r
+ gc spectral-norm 19.72u 0.00s 19.76r
+ gc_B spectral-norm 19.68u 0.01s 19.74r
+
+k-nucleotide 1000000
+ gcc -O2 k-nucleotide.c -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -lglib-2.0 4.90u 0.01s 4.93r
+ gccgo -O2 k-nucleotide.go 4.78u 0.01s 4.80r
+ gccgo -O2 k-nucleotide-parallel.go 6.49u 0.02s 2.18r
+ gc k-nucleotide 9.05u 0.02s 9.09r
+ gc k-nucleotide-parallel 9.27u 0.01s 3.29r
+ gc_B k-nucleotide 8.95u 0.03s 9.00r
+
+mandelbrot 16000
+ gcc -O2 mandelbrot.c 36.11u 0.00s 36.19r
+ gccgo -O2 mandelbrot.go 43.67u 0.00s 43.77r
+ gc mandelbrot 38.57u 0.00s 38.66r
+ gc_B mandelbrot 38.59u 0.00s 38.68r
+
+meteor 2098
+ gcc -O2 meteor-contest.c 0.09u 0.00s 0.09r
+ gccgo -O2 meteor-contest.go 0.09u 0.00s 0.09r
+ gc meteor-contest 0.13u 0.00s 0.14r
+ gc_B meteor-contest 0.12u 0.00s 0.13r
+
+pidigits 10000
+ gcc -O2 pidigits.c -lgmp 2.26u 0.00s 2.27r
+ gccgo -O2 pidigits.go 9.05u 0.00s 9.07r
+ gc pidigits 2.88u 0.02s 2.90r
+ gc_B pidigits 2.89u 0.00s 2.90r
+
+threadring 50000000
+ gcc -O2 threadring.c -lpthread 37.30u 327.81s 289.28r
+ gccgo -O2 threadring.go 42.83u 26.15s 69.14r
+ gc threadring 13.00u 0.00s 13.03r
+
+chameneos 6000000
+ gcc -O2 chameneosredux.c -lpthread 8.80u 71.67s 12.19r
+ gccgo -O2 chameneosredux.go 11.28u 6.68s 18.00r
+ gc chameneosredux 6.94u 0.00s 6.96r
+
+# May 23, 2013
+# Go 1.1, which includes precise GC, new scheduler, faster maps.
+# 20%-ish speedups across many benchmarks.
+# gccgo showing significant improvement (even though it's not yet up to Go 1.1)
+#
+# Standouts:
+# fannkuch, regex-dna, k-nucleotide, threadring, chameneos
+
+fasta -n 25000000
+ gcc -m64 -O2 fasta.c 1.54u 0.01s 1.55r
+ gccgo -O2 fasta.go 1.42u 0.00s 1.43r
+ gc fasta 1.50u 0.01s 1.52r # -16%
+ gc_B fasta 1.46u 0.00s 1.46r # -17%
+
+reverse-complement < output-of-fasta-25000000
+ gcc -m64 -O2 reverse-complement.c 0.87u 0.37s 4.36r
+ gccgo -O2 reverse-complement.go 0.77u 0.15s 0.93r # -15%
+ gc reverse-complement 0.99u 0.12s 1.12r # -15%
+ gc_B reverse-complement 0.85u 0.17s 1.02r # -21%
+
+nbody -n 50000000
+ gcc -m64 -O2 nbody.c -lm 13.50u 0.00s 13.53r
+ gccgo -O2 nbody.go 13.98u 0.01s 14.02r
+ gc nbody 16.63u 0.01s 16.67r
+ gc_B nbody 15.74u 0.00s 15.76r
+
+binary-tree 15 # too slow to use 20
+ gcc -m64 -O2 binary-tree.c -lm 0.61u 0.00s 0.61r
+ gccgo -O2 binary-tree.go 1.11u 0.01s 1.12r # -13%
+ gccgo -O2 binary-tree-freelist.go 0.22u 0.01s 0.23r
+ gc binary-tree 1.83u 0.02s 1.83r # -7%
+ gc binary-tree-freelist 0.32u 0.00s 0.32r
+
+fannkuch 12
+ gcc -m64 -O2 fannkuch.c 45.56u 0.00s 45.67r
+ gccgo -O2 fannkuch.go 57.71u 0.00s 57.85r # -4%
+ gccgo -O2 fannkuch-parallel.go 146.31u 0.00s 37.50r #-37%
+ gc fannkuch 70.06u 0.03s 70.17r # -3%
+ gc fannkuch-parallel 131.88u 0.06s 33.59r # -23%
+ gc_B fannkuch 45.55u 0.02s 45.63r # -15%
+
+regex-dna 100000
+ gcc -m64 -O2 regex-dna.c -lpcre 0.44u 0.01s 0.45r
+ gccgo -O2 regex-dna.go 5.59u 0.00s 5.61r # -14%
+ gccgo -O2 regex-dna-parallel.go 10.85u 0.30s 3.34r # -24%
+ gc regex-dna 2.23u 0.01s 2.25r # -43%
+ gc regex-dna-parallel 2.35u 0.00s 0.93r # -40%
+ gc_B regex-dna 2.24u 0.01s 2.25r # -43%
+
+spectral-norm 5500
+ gcc -m64 -O2 spectral-norm.c -lm 14.84u 0.00s 14.88r
+ gccgo -O2 spectral-norm.go 15.33u 0.00s 15.37r
+ gc spectral-norm 16.75u 0.02s 16.79r # -15%
+ gc_B spectral-norm 16.77u 0.01s 16.79r # -15%
+
+k-nucleotide 1000000
+ gcc -O2 k-nucleotide.c -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lglib-2.0 4.50u 0.00s 4.52r
+ gccgo -O2 k-nucleotide.go 3.72u 0.04s 3.77r # -21%
+ gccgo -O2 k-nucleotide-parallel.go 3.88u 0.03s 1.42r # -35%
+ gc k-nucleotide 6.32u 0.01s 6.33r # -31%
+ gc k-nucleotide-parallel 6.47u 0.05s 2.13r # -33%
+ gc_B k-nucleotide 6.45u 0.01s 6.47r # - 28%
+
+mandelbrot 16000
+ gcc -m64 -O2 mandelbrot.c 36.03u 0.00s 36.11r
+ gccgo -O2 mandelbrot.go 37.61u 0.00s 37.74r # -14%
+ gc mandelbrot 38.19u 0.05s 38.29r
+ gc_B mandelbrot 38.19u 0.03s 38.26r
+
+meteor 2098
+ gcc -m64 -O2 meteor-contest.c 0.08u 0.00s 0.08r
+ gccgo -O2 meteor-contest.go 0.09u 0.01s 0.10r
+ gc meteor-contest 0.12u 0.00s 0.12r # -15% although perhaps just noise
+ gc_B meteor-contest 0.11u 0.00s 0.12r # -8% although perhaps just noise
+
+pidigits 10000
+ gcc -m64 -O2 pidigits.c -lgmp 2.27u 0.00s 2.28r
+ gccgo -O2 pidigits.go 8.95u 0.02s 8.99r
+ gc pidigits 2.88u 0.14s 2.91r
+ gc_B pidigits 2.92u 0.10s 2.91r
+
+threadring 50000000
+ gcc -m64 -O2 threadring.c -lpthread 14.75u 167.88s 212.23r
+ gccgo -O2 threadring.go 36.72u 12.08s 48.91r # -29%
+ gc threadring 10.93u 0.01s 10.95r # -16%
+
+chameneos 6000000
+ gcc -m64 -O2 chameneosredux.c -lpthread 8.89u 56.62s 9.75r
+ gccgo -O2 chameneosredux.go 9.48u 2.48s 11.99r # -33%
+ gc chameneosredux 5.80u 0.00s 5.81r # -16%
+
diff --git a/shootout/timing.sh b/shootout/timing.sh
new file mode 100755
index 0000000..9abcf78
--- /dev/null
+++ b/shootout/timing.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env bash
+# Copyright 2009 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+set -e
+
+eval $(go tool dist env)
+GC="go tool compile"
+LD="go tool link"
+
+gccm=""
+case "$O" in
+8)
+ gccm=-m32;;
+6)
+ gccm=-m64;;
+esac
+
+EXE="out"
+havepcre=true
+haveglib=true
+havegmp=true
+case "$(uname)" in
+*MINGW* | *WIN32* | *CYGWIN*)
+ havepcre=false
+ haveglib=false
+ havegmp=false
+ if which pkg-config >/dev/null 2>&1; then
+ if pkg-config --cflags libpcre >/dev/null 2>&1
+ then
+ echo "havepcre"
+ havepcre=true
+ fi
+ if pkg-config --cflags glib-2.0 >/dev/null 2>&1
+ then
+ haveglib=true
+ fi
+ if pkg-config --cflags gmp >/dev/null 2>&1
+ then
+ havegmp=true
+ fi
+ fi
+ EXE=exe;;
+esac
+
+PATH=.:$PATH
+
+havegccgo=false
+if which gccgo >/dev/null 2>&1
+then
+ havegccgo=true
+fi
+
+mode=run
+case X"$1" in
+X-test)
+ mode=test
+ shift
+esac
+
+gc() {
+ $GC $1.go; $LD -o a.$EXE $1.o
+}
+
+gc_B() {
+ $GC -B $1.go; $LD -o a.$EXE $1.o
+}
+
+runonly() {
+ if [ $mode = run ]
+ then
+ "$@"
+ fi
+}
+
+run() {
+ if [ $mode = test ]
+ then
+ if echo $1 | grep -q '^gc '
+ then
+ $1 # compile the program
+ program=$(echo $1 | sed 's/gc //')
+ shift
+ echo $program
+ $1 <fasta-1000.txt > /tmp/$$
+ case $program in
+ chameneosredux)
+ # exact numbers may vary but non-numbers should match
+ grep -v '[0-9]' /tmp/$$ > /tmp/$$x
+ grep -v '[0-9]' chameneosredux.txt > /tmp/$$y
+ cmp /tmp/$$x /tmp/$$y
+ rm -f /tmp/$$ /tmp/$$x /tmp/$$y
+ ;;
+ *)
+ cmp /tmp/$$ $program.txt
+ rm -f /tmp/$$
+ esac
+ fi
+ return
+ fi
+ if ! $havegccgo && echo $1 | grep -q '^gccgo '
+ then
+ return
+ fi
+ echo -n ' '$1' '
+ $1
+ shift
+
+ echo $((time -p $* >/dev/null) 2>&1) | awk '{print $4 "u " $6 "s " $2 "r"}'
+}
+
+fasta() {
+ runonly echo 'fasta -n 25000000'
+ run "gcc $gccm -O2 fasta.c" a.$EXE 25000000
+ run 'gccgo -O2 fasta.go' a.$EXE -n 25000000 #commented out until WriteString is in bufio
+ run 'gc fasta' a.$EXE -n 25000000
+ run 'gc_B fasta' a.$EXE -n 25000000
+}
+
+revcomp() {
+ runonly gcc -O2 fasta.c
+ runonly a.$EXE 25000000 > x
+ runonly echo 'reverse-complement < output-of-fasta-25000000'
+ run "gcc $gccm -O2 reverse-complement.c" a.$EXE < x
+ run 'gccgo -O2 reverse-complement.go' a.$EXE < x
+ run 'gc reverse-complement' a.$EXE < x
+ run 'gc_B reverse-complement' a.$EXE < x
+ rm x
+}
+
+nbody() {
+ runonly echo 'nbody -n 50000000'
+ run "gcc $gccm -O2 nbody.c -lm" a.$EXE 50000000
+ run 'gccgo -O2 nbody.go' a.$EXE -n 50000000
+ run 'gc nbody' a.$EXE -n 50000000
+ run 'gc_B nbody' a.$EXE -n 50000000
+}
+
+binarytree() {
+ runonly echo 'binary-tree 15 # too slow to use 20'
+ run "gcc $gccm -O2 binary-tree.c -lm" a.$EXE 15
+ run 'gccgo -O2 binary-tree.go' a.$EXE -n 15
+ run 'gccgo -O2 binary-tree-freelist.go' a.$EXE -n 15
+ run 'gc binary-tree' a.$EXE -n 15
+ run 'gc binary-tree-freelist' a.$EXE -n 15
+}
+
+fannkuch() {
+ runonly echo 'fannkuch 12'
+ run "gcc $gccm -O2 fannkuch.c" a.$EXE 12
+ run 'gccgo -O2 fannkuch.go' a.$EXE -n 12
+ run 'gccgo -O2 fannkuch-parallel.go' a.$EXE -n 12
+ run 'gc fannkuch' a.$EXE -n 12
+ run 'gc fannkuch-parallel' a.$EXE -n 12
+ run 'gc_B fannkuch' a.$EXE -n 12
+}
+
+regexdna() {
+ runonly gcc -O2 fasta.c
+ runonly a.$EXE 100000 > x
+ runonly echo 'regex-dna 100000'
+ if $havepcre; then
+ run "gcc $gccm -O2 regex-dna.c $(pkg-config libpcre --cflags --libs)" a.$EXE <x
+ fi
+ run 'gccgo -O2 regex-dna.go' a.$EXE <x
+ run 'gccgo -O2 regex-dna-parallel.go' a.$EXE <x
+ run 'gc regex-dna' a.$EXE <x
+ run 'gc regex-dna-parallel' a.$EXE <x
+ run 'gc_B regex-dna' a.$EXE <x
+ rm x
+}
+
+spectralnorm() {
+ runonly echo 'spectral-norm 5500'
+ run "gcc $gccm -O2 spectral-norm.c -lm" a.$EXE 5500
+ run 'gccgo -O2 spectral-norm.go' a.$EXE -n 5500
+ run 'gc spectral-norm' a.$EXE -n 5500
+ run 'gc_B spectral-norm' a.$EXE -n 5500
+}
+
+knucleotide() {
+ runonly gcc -O2 fasta.c
+ runonly a.$EXE 1000000 > x # should be using 25000000
+ runonly echo 'k-nucleotide 1000000'
+ if [ $mode = run ] && $haveglib; then
+ run "gcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)" a.$EXE <x
+ fi
+ run 'gccgo -O2 k-nucleotide.go' a.$EXE <x
+ run 'gccgo -O2 k-nucleotide-parallel.go' a.$EXE <x
+ run 'gc k-nucleotide' a.$EXE <x
+ run 'gc k-nucleotide-parallel' a.$EXE <x
+ run 'gc_B k-nucleotide' a.$EXE <x
+ rm x
+}
+
+mandelbrot() {
+ runonly echo 'mandelbrot 16000'
+ run "gcc $gccm -O2 mandelbrot.c" a.$EXE 16000
+ run 'gccgo -O2 mandelbrot.go' a.$EXE -n 16000
+ run 'gc mandelbrot' a.$EXE -n 16000
+ run 'gc_B mandelbrot' a.$EXE -n 16000
+}
+
+meteor() {
+ runonly echo 'meteor 2098'
+ run "gcc $gccm -O2 meteor-contest.c" a.$EXE 2098
+ run 'gccgo -O2 meteor-contest.go' a.$EXE -n 2098
+ run 'gc meteor-contest' a.$EXE -n 2098
+ run 'gc_B meteor-contest' a.$EXE -n 2098
+}
+
+pidigits() {
+ runonly echo 'pidigits 10000'
+ if $havegmp; then
+ run "gcc $gccm -O2 pidigits.c -lgmp" a.$EXE 10000
+ fi
+ run 'gccgo -O2 pidigits.go' a.$EXE -n 10000
+ run 'gc pidigits' a.$EXE -n 10000
+ run 'gc_B pidigits' a.$EXE -n 10000
+}
+
+threadring() {
+ runonly echo 'threadring 50000000'
+ run "gcc $gccm -O2 threadring.c -lpthread" a.$EXE 50000000
+ run 'gccgo -O2 threadring.go' a.$EXE -n 50000000
+ run 'gc threadring' a.$EXE -n 50000000
+}
+
+chameneos() {
+ runonly echo 'chameneos 6000000'
+ run "gcc $gccm -O2 chameneosredux.c -lpthread" a.$EXE 6000000
+ run 'gccgo -O2 chameneosredux.go' a.$EXE 6000000
+ run 'gc chameneosredux' a.$EXE 6000000
+}
+
+case $# in
+0)
+ run="fasta revcomp nbody binarytree fannkuch regexdna spectralnorm knucleotide mandelbrot meteor pidigits threadring chameneos"
+ ;;
+*)
+ run=$*
+esac
+
+for i in $run
+do
+ $i
+ runonly echo
+done
+
+rm *.o *.$EXE # Clean up
+
diff --git a/slices/cmp.go b/slices/cmp.go
new file mode 100644
index 0000000..fbf1934
--- /dev/null
+++ b/slices/cmp.go
@@ -0,0 +1,44 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import "golang.org/x/exp/constraints"
+
+// min is a version of the predeclared function from the Go 1.21 release.
+func min[T constraints.Ordered](a, b T) T {
+ if a < b || isNaN(a) {
+ return a
+ }
+ return b
+}
+
+// max is a version of the predeclared function from the Go 1.21 release.
+func max[T constraints.Ordered](a, b T) T {
+ if a > b || isNaN(a) {
+ return a
+ }
+ return b
+}
+
+// cmpLess is a copy of cmp.Less from the Go 1.21 release.
+func cmpLess[T constraints.Ordered](x, y T) bool {
+ return (isNaN(x) && !isNaN(y)) || x < y
+}
+
+// cmpCompare is a copy of cmp.Compare from the Go 1.21 release.
+func cmpCompare[T constraints.Ordered](x, y T) int {
+ xNaN := isNaN(x)
+ yNaN := isNaN(y)
+ if xNaN && yNaN {
+ return 0
+ }
+ if xNaN || x < y {
+ return -1
+ }
+ if yNaN || x > y {
+ return +1
+ }
+ return 0
+}
diff --git a/slices/slices.go b/slices/slices.go
new file mode 100644
index 0000000..5e8158b
--- /dev/null
+++ b/slices/slices.go
@@ -0,0 +1,499 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package slices defines various functions useful with slices of any type.
+package slices
+
+import (
+ "unsafe"
+
+ "golang.org/x/exp/constraints"
+)
+
+// Equal reports whether two slices are equal: the same length and all
+// elements equal. If the lengths are different, Equal returns false.
+// Otherwise, the elements are compared in increasing index order, and the
+// comparison stops at the first unequal pair.
+// Floating point NaNs are not considered equal.
+func Equal[S ~[]E, E comparable](s1, s2 S) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+ for i := range s1 {
+ if s1[i] != s2[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// EqualFunc reports whether two slices are equal using an equality
+// function on each pair of elements. If the lengths are different,
+// EqualFunc returns false. Otherwise, the elements are compared in
+// increasing index order, and the comparison stops at the first index
+// for which eq returns false.
+func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+ for i, v1 := range s1 {
+ v2 := s2[i]
+ if !eq(v1, v2) {
+ return false
+ }
+ }
+ return true
+}
+
+// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair
+// of elements. The elements are compared sequentially, starting at index 0,
+// until one element is not equal to the other.
+// The result of comparing the first non-matching elements is returned.
+// If both slices are equal until one of them ends, the shorter slice is
+// considered less than the longer one.
+// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
+func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int {
+ for i, v1 := range s1 {
+ if i >= len(s2) {
+ return +1
+ }
+ v2 := s2[i]
+ if c := cmpCompare(v1, v2); c != 0 {
+ return c
+ }
+ }
+ if len(s1) < len(s2) {
+ return -1
+ }
+ return 0
+}
+
+// CompareFunc is like [Compare] but uses a custom comparison function on each
+// pair of elements.
+// The result is the first non-zero result of cmp; if cmp always
+// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
+// and +1 if len(s1) > len(s2).
+func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
+ for i, v1 := range s1 {
+ if i >= len(s2) {
+ return +1
+ }
+ v2 := s2[i]
+ if c := cmp(v1, v2); c != 0 {
+ return c
+ }
+ }
+ if len(s1) < len(s2) {
+ return -1
+ }
+ return 0
+}
+
+// Index returns the index of the first occurrence of v in s,
+// or -1 if not present.
+func Index[S ~[]E, E comparable](s S, v E) int {
+ for i := range s {
+ if v == s[i] {
+ return i
+ }
+ }
+ return -1
+}
+
+// IndexFunc returns the first index i satisfying f(s[i]),
+// or -1 if none do.
+func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
+ for i := range s {
+ if f(s[i]) {
+ return i
+ }
+ }
+ return -1
+}
+
+// Contains reports whether v is present in s.
+func Contains[S ~[]E, E comparable](s S, v E) bool {
+ return Index(s, v) >= 0
+}
+
+// ContainsFunc reports whether at least one
+// element e of s satisfies f(e).
+func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
+ return IndexFunc(s, f) >= 0
+}
+
+// Insert inserts the values v... into s at index i,
+// returning the modified slice.
+// The elements at s[i:] are shifted up to make room.
+// In the returned slice r, r[i] == v[0],
+// and r[i+len(v)] == value originally at r[i].
+// Insert panics if i is out of range.
+// This function is O(len(s) + len(v)).
+func Insert[S ~[]E, E any](s S, i int, v ...E) S {
+ m := len(v)
+ if m == 0 {
+ return s
+ }
+ n := len(s)
+ if i == n {
+ return append(s, v...)
+ }
+ if n+m > cap(s) {
+ // Use append rather than make so that we bump the size of
+ // the slice up to the next storage class.
+ // This is what Grow does but we don't call Grow because
+ // that might copy the values twice.
+ s2 := append(s[:i], make(S, n+m-i)...)
+ copy(s2[i:], v)
+ copy(s2[i+m:], s[i:])
+ return s2
+ }
+ s = s[:n+m]
+
+ // before:
+ // s: aaaaaaaabbbbccccccccdddd
+ // ^ ^ ^ ^
+ // i i+m n n+m
+ // after:
+ // s: aaaaaaaavvvvbbbbcccccccc
+ // ^ ^ ^ ^
+ // i i+m n n+m
+ //
+ // a are the values that don't move in s.
+ // v are the values copied in from v.
+ // b and c are the values from s that are shifted up in index.
+ // d are the values that get overwritten, never to be seen again.
+
+ if !overlaps(v, s[i+m:]) {
+ // Easy case - v does not overlap either the c or d regions.
+ // (It might be in some of a or b, or elsewhere entirely.)
+ // The data we copy up doesn't write to v at all, so just do it.
+
+ copy(s[i+m:], s[i:])
+
+ // Now we have
+ // s: aaaaaaaabbbbbbbbcccccccc
+ // ^ ^ ^ ^
+ // i i+m n n+m
+ // Note the b values are duplicated.
+
+ copy(s[i:], v)
+
+ // Now we have
+ // s: aaaaaaaavvvvbbbbcccccccc
+ // ^ ^ ^ ^
+ // i i+m n n+m
+ // That's the result we want.
+ return s
+ }
+
+ // The hard case - v overlaps c or d. We can't just shift up
+ // the data because we'd move or clobber the values we're trying
+ // to insert.
+ // So instead, write v on top of d, then rotate.
+ copy(s[n:], v)
+
+ // Now we have
+ // s: aaaaaaaabbbbccccccccvvvv
+ // ^ ^ ^ ^
+ // i i+m n n+m
+
+ rotateRight(s[i:], m)
+
+ // Now we have
+ // s: aaaaaaaavvvvbbbbcccccccc
+ // ^ ^ ^ ^
+ // i i+m n n+m
+ // That's the result we want.
+ return s
+}
+
+// Delete removes the elements s[i:j] from s, returning the modified slice.
+// Delete panics if s[i:j] is not a valid slice of s.
+// Delete is O(len(s)-j), so if many items must be deleted, it is better to
+// make a single call deleting them all together than to delete one at a time.
+// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those
+// elements contain pointers you might consider zeroing those elements so that
+// objects they reference can be garbage collected.
+func Delete[S ~[]E, E any](s S, i, j int) S {
+ _ = s[i:j] // bounds check
+
+ return append(s[:i], s[j:]...)
+}
+
+// DeleteFunc removes any elements from s for which del returns true,
+// returning the modified slice.
+// When DeleteFunc removes m elements, it might not modify the elements
+// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
+// zeroing those elements so that objects they reference can be garbage
+// collected.
+func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
+ i := IndexFunc(s, del)
+ if i == -1 {
+ return s
+ }
+ // Don't start copying elements until we find one to delete.
+ for j := i + 1; j < len(s); j++ {
+ if v := s[j]; !del(v) {
+ s[i] = v
+ i++
+ }
+ }
+ return s[:i]
+}
+
+// Replace replaces the elements s[i:j] by the given v, and returns the
+// modified slice. Replace panics if s[i:j] is not a valid slice of s.
+func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
+ _ = s[i:j] // verify that i:j is a valid subslice
+
+ if i == j {
+ return Insert(s, i, v...)
+ }
+ if j == len(s) {
+ return append(s[:i], v...)
+ }
+
+ tot := len(s[:i]) + len(v) + len(s[j:])
+ if tot > cap(s) {
+ // Too big to fit, allocate and copy over.
+ s2 := append(s[:i], make(S, tot-i)...) // See Insert
+ copy(s2[i:], v)
+ copy(s2[i+len(v):], s[j:])
+ return s2
+ }
+
+ r := s[:tot]
+
+ if i+len(v) <= j {
+ // Easy, as v fits in the deleted portion.
+ copy(r[i:], v)
+ if i+len(v) != j {
+ copy(r[i+len(v):], s[j:])
+ }
+ return r
+ }
+
+ // We are expanding (v is bigger than j-i).
+ // The situation is something like this:
+ // (example has i=4,j=8,len(s)=16,len(v)=6)
+ // s: aaaaxxxxbbbbbbbbyy
+ // ^ ^ ^ ^
+ // i j len(s) tot
+ // a: prefix of s
+ // x: deleted range
+ // b: more of s
+ // y: area to expand into
+
+ if !overlaps(r[i+len(v):], v) {
+ // Easy, as v is not clobbered by the first copy.
+ copy(r[i+len(v):], s[j:])
+ copy(r[i:], v)
+ return r
+ }
+
+ // This is a situation where we don't have a single place to which
+ // we can copy v. Parts of it need to go to two different places.
+ // We want to copy the prefix of v into y and the suffix into x, then
+ // rotate |y| spots to the right.
+ //
+ // v[2:] v[:2]
+ // | |
+ // s: aaaavvvvbbbbbbbbvv
+ // ^ ^ ^ ^
+ // i j len(s) tot
+ //
+ // If either of those two destinations don't alias v, then we're good.
+ y := len(v) - (j - i) // length of y portion
+
+ if !overlaps(r[i:j], v) {
+ copy(r[i:j], v[y:])
+ copy(r[len(s):], v[:y])
+ rotateRight(r[i:], y)
+ return r
+ }
+ if !overlaps(r[len(s):], v) {
+ copy(r[len(s):], v[:y])
+ copy(r[i:j], v[y:])
+ rotateRight(r[i:], y)
+ return r
+ }
+
+ // Now we know that v overlaps both x and y.
+ // That means that the entirety of b is *inside* v.
+ // So we don't need to preserve b at all; instead we
+ // can copy v first, then copy the b part of v out of
+ // v to the right destination.
+ k := startIdx(v, s[j:])
+ copy(r[i:], v)
+ copy(r[i+len(v):], r[i+k:])
+ return r
+}
+
+// Clone returns a copy of the slice.
+// The elements are copied using assignment, so this is a shallow clone.
+func Clone[S ~[]E, E any](s S) S {
+ // Preserve nil in case it matters.
+ if s == nil {
+ return nil
+ }
+ return append(S([]E{}), s...)
+}
+
+// Compact replaces consecutive runs of equal elements with a single copy.
+// This is like the uniq command found on Unix.
+// Compact modifies the contents of the slice s and returns the modified slice,
+// which may have a smaller length.
+// When Compact discards m elements in total, it might not modify the elements
+// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
+// zeroing those elements so that objects they reference can be garbage collected.
+func Compact[S ~[]E, E comparable](s S) S {
+ if len(s) < 2 {
+ return s
+ }
+ i := 1
+ for k := 1; k < len(s); k++ {
+ if s[k] != s[k-1] {
+ if i != k {
+ s[i] = s[k]
+ }
+ i++
+ }
+ }
+ return s[:i]
+}
+
+// CompactFunc is like [Compact] but uses an equality function to compare elements.
+// For runs of elements that compare equal, CompactFunc keeps the first one.
+func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
+ if len(s) < 2 {
+ return s
+ }
+ i := 1
+ for k := 1; k < len(s); k++ {
+ if !eq(s[k], s[k-1]) {
+ if i != k {
+ s[i] = s[k]
+ }
+ i++
+ }
+ }
+ return s[:i]
+}
+
+// Grow increases the slice's capacity, if necessary, to guarantee space for
+// another n elements. After Grow(n), at least n elements can be appended
+// to the slice without another allocation. If n is negative or too large to
+// allocate the memory, Grow panics.
+func Grow[S ~[]E, E any](s S, n int) S {
+ if n < 0 {
+ panic("cannot be negative")
+ }
+ if n -= cap(s) - len(s); n > 0 {
+ // TODO(https://go.dev/issue/53888): Make using []E instead of S
+ // to workaround a compiler bug where the runtime.growslice optimization
+ // does not take effect. Revert when the compiler is fixed.
+ s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)]
+ }
+ return s
+}
+
+// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
+func Clip[S ~[]E, E any](s S) S {
+ return s[:len(s):len(s)]
+}
+
+// Rotation algorithm explanation:
+//
+// rotate left by 2
+// start with
+// 0123456789
+// split up like this
+// 01 234567 89
+// swap first 2 and last 2
+// 89 234567 01
+// join first parts
+// 89234567 01
+// recursively rotate first left part by 2
+// 23456789 01
+// join at the end
+// 2345678901
+//
+// rotate left by 8
+// start with
+// 0123456789
+// split up like this
+// 01 234567 89
+// swap first 2 and last 2
+// 89 234567 01
+// join last parts
+// 89 23456701
+// recursively rotate second part left by 6
+// 89 01234567
+// join at the end
+// 8901234567
+
+// TODO: There are other rotate algorithms.
+// This algorithm has the desirable property that it moves each element exactly twice.
+// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes.
+// The follow-cycles algorithm can be 1-write but it is not very cache friendly.
+
+// rotateLeft rotates b left by n spaces.
+// s_final[i] = s_orig[i+r], wrapping around.
+func rotateLeft[E any](s []E, r int) {
+ for r != 0 && r != len(s) {
+ if r*2 <= len(s) {
+ swap(s[:r], s[len(s)-r:])
+ s = s[:len(s)-r]
+ } else {
+ swap(s[:len(s)-r], s[r:])
+ s, r = s[len(s)-r:], r*2-len(s)
+ }
+ }
+}
+func rotateRight[E any](s []E, r int) {
+ rotateLeft(s, len(s)-r)
+}
+
+// swap swaps the contents of x and y. x and y must be equal length and disjoint.
+func swap[E any](x, y []E) {
+ for i := 0; i < len(x); i++ {
+ x[i], y[i] = y[i], x[i]
+ }
+}
+
+// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap.
+func overlaps[E any](a, b []E) bool {
+ if len(a) == 0 || len(b) == 0 {
+ return false
+ }
+ elemSize := unsafe.Sizeof(a[0])
+ if elemSize == 0 {
+ return false
+ }
+ // TODO: use a runtime/unsafe facility once one becomes available. See issue 12445.
+ // Also see crypto/internal/alias/alias.go:AnyOverlap
+ return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) &&
+ uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1)
+}
+
+// startIdx returns the index in haystack where the needle starts.
+// prerequisite: the needle must be aliased entirely inside the haystack.
+func startIdx[E any](haystack, needle []E) int {
+ p := &needle[0]
+ for i := range haystack {
+ if p == &haystack[i] {
+ return i
+ }
+ }
+ // TODO: what if the overlap is by a non-integral number of Es?
+ panic("needle not found")
+}
+
+// Reverse reverses the elements of the slice in place.
+func Reverse[S ~[]E, E any](s S) {
+ for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
+ s[i], s[j] = s[j], s[i]
+ }
+}
diff --git a/slices/slices_race_test.go b/slices/slices_race_test.go
new file mode 100644
index 0000000..5a2a15d
--- /dev/null
+++ b/slices/slices_race_test.go
@@ -0,0 +1,9 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build race
+
+package slices
+
+func init() { raceEnabled = true }
diff --git a/slices/slices_test.go b/slices/slices_test.go
new file mode 100644
index 0000000..7371bdb
--- /dev/null
+++ b/slices/slices_test.go
@@ -0,0 +1,1054 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import (
+ "math"
+ "strings"
+ "testing"
+)
+
+var raceEnabled bool
+
+var equalIntTests = []struct {
+ s1, s2 []int
+ want bool
+}{
+ {
+ []int{1},
+ nil,
+ false,
+ },
+ {
+ []int{},
+ nil,
+ true,
+ },
+ {
+ []int{1, 2, 3},
+ []int{1, 2, 3},
+ true,
+ },
+ {
+ []int{1, 2, 3},
+ []int{1, 2, 3, 4},
+ false,
+ },
+}
+
+var equalFloatTests = []struct {
+ s1, s2 []float64
+ wantEqual bool
+ wantEqualNaN bool
+}{
+ {
+ []float64{1, 2},
+ []float64{1, 2},
+ true,
+ true,
+ },
+ {
+ []float64{1, 2, math.NaN()},
+ []float64{1, 2, math.NaN()},
+ false,
+ true,
+ },
+}
+
+func TestEqual(t *testing.T) {
+ for _, test := range equalIntTests {
+ if got := Equal(test.s1, test.s2); got != test.want {
+ t.Errorf("Equal(%v, %v) = %t, want %t", test.s1, test.s2, got, test.want)
+ }
+ }
+ for _, test := range equalFloatTests {
+ if got := Equal(test.s1, test.s2); got != test.wantEqual {
+ t.Errorf("Equal(%v, %v) = %t, want %t", test.s1, test.s2, got, test.wantEqual)
+ }
+ }
+}
+
+// equal is simply ==.
+func equal[T comparable](v1, v2 T) bool {
+ return v1 == v2
+}
+
+// equalNaN is like == except that all NaNs are equal.
+func equalNaN[T comparable](v1, v2 T) bool {
+ isNaN := func(f T) bool { return f != f }
+ return v1 == v2 || (isNaN(v1) && isNaN(v2))
+}
+
+// offByOne returns true if integers v1 and v2 differ by 1.
+func offByOne(v1, v2 int) bool {
+ return v1 == v2+1 || v1 == v2-1
+}
+
+func TestEqualFunc(t *testing.T) {
+ for _, test := range equalIntTests {
+ if got := EqualFunc(test.s1, test.s2, equal[int]); got != test.want {
+ t.Errorf("EqualFunc(%v, %v, equal[int]) = %t, want %t", test.s1, test.s2, got, test.want)
+ }
+ }
+ for _, test := range equalFloatTests {
+ if got := EqualFunc(test.s1, test.s2, equal[float64]); got != test.wantEqual {
+ t.Errorf("Equal(%v, %v, equal[float64]) = %t, want %t", test.s1, test.s2, got, test.wantEqual)
+ }
+ if got := EqualFunc(test.s1, test.s2, equalNaN[float64]); got != test.wantEqualNaN {
+ t.Errorf("Equal(%v, %v, equalNaN[float64]) = %t, want %t", test.s1, test.s2, got, test.wantEqualNaN)
+ }
+ }
+
+ s1 := []int{1, 2, 3}
+ s2 := []int{2, 3, 4}
+ if EqualFunc(s1, s1, offByOne) {
+ t.Errorf("EqualFunc(%v, %v, offByOne) = true, want false", s1, s1)
+ }
+ if !EqualFunc(s1, s2, offByOne) {
+ t.Errorf("EqualFunc(%v, %v, offByOne) = false, want true", s1, s2)
+ }
+
+ s3 := []string{"a", "b", "c"}
+ s4 := []string{"A", "B", "C"}
+ if !EqualFunc(s3, s4, strings.EqualFold) {
+ t.Errorf("EqualFunc(%v, %v, strings.EqualFold) = false, want true", s3, s4)
+ }
+
+ cmpIntString := func(v1 int, v2 string) bool {
+ return string(rune(v1)-1+'a') == v2
+ }
+ if !EqualFunc(s1, s3, cmpIntString) {
+ t.Errorf("EqualFunc(%v, %v, cmpIntString) = false, want true", s1, s3)
+ }
+}
+
+func BenchmarkEqualFunc_Large(b *testing.B) {
+ type Large [4 * 1024]byte
+
+ xs := make([]Large, 1024)
+ ys := make([]Large, 1024)
+ for i := 0; i < b.N; i++ {
+ _ = EqualFunc(xs, ys, func(x, y Large) bool { return x == y })
+ }
+}
+
+var compareIntTests = []struct {
+ s1, s2 []int
+ want int
+}{
+ {
+ []int{1},
+ []int{1},
+ 0,
+ },
+ {
+ []int{1},
+ []int{},
+ 1,
+ },
+ {
+ []int{},
+ []int{1},
+ -1,
+ },
+ {
+ []int{},
+ []int{},
+ 0,
+ },
+ {
+ []int{1, 2, 3},
+ []int{1, 2, 3},
+ 0,
+ },
+ {
+ []int{1, 2, 3},
+ []int{1, 2, 3, 4},
+ -1,
+ },
+ {
+ []int{1, 2, 3, 4},
+ []int{1, 2, 3},
+ +1,
+ },
+ {
+ []int{1, 2, 3},
+ []int{1, 4, 3},
+ -1,
+ },
+ {
+ []int{1, 4, 3},
+ []int{1, 2, 3},
+ +1,
+ },
+ {
+ []int{1, 4, 3},
+ []int{1, 2, 3, 8, 9},
+ +1,
+ },
+}
+
+var compareFloatTests = []struct {
+ s1, s2 []float64
+ want int
+}{
+ {
+ []float64{},
+ []float64{},
+ 0,
+ },
+ {
+ []float64{1},
+ []float64{1},
+ 0,
+ },
+ {
+ []float64{math.NaN()},
+ []float64{math.NaN()},
+ 0,
+ },
+ {
+ []float64{1, 2, math.NaN()},
+ []float64{1, 2, math.NaN()},
+ 0,
+ },
+ {
+ []float64{1, math.NaN(), 3},
+ []float64{1, math.NaN(), 4},
+ -1,
+ },
+ {
+ []float64{1, math.NaN(), 3},
+ []float64{1, 2, 4},
+ -1,
+ },
+ {
+ []float64{1, math.NaN(), 3},
+ []float64{1, 2, math.NaN()},
+ -1,
+ },
+ {
+ []float64{1, 2, 3},
+ []float64{1, 2, math.NaN()},
+ +1,
+ },
+ {
+ []float64{1, 2, 3},
+ []float64{1, math.NaN(), 3},
+ +1,
+ },
+ {
+ []float64{1, math.NaN(), 3, 4},
+ []float64{1, 2, math.NaN()},
+ -1,
+ },
+}
+
+func TestCompare(t *testing.T) {
+ intWant := func(want bool) string {
+ if want {
+ return "0"
+ }
+ return "!= 0"
+ }
+ for _, test := range equalIntTests {
+ if got := Compare(test.s1, test.s2); (got == 0) != test.want {
+ t.Errorf("Compare(%v, %v) = %d, want %s", test.s1, test.s2, got, intWant(test.want))
+ }
+ }
+ for _, test := range equalFloatTests {
+ if got := Compare(test.s1, test.s2); (got == 0) != test.wantEqualNaN {
+ t.Errorf("Compare(%v, %v) = %d, want %s", test.s1, test.s2, got, intWant(test.wantEqualNaN))
+ }
+ }
+
+ for _, test := range compareIntTests {
+ if got := Compare(test.s1, test.s2); got != test.want {
+ t.Errorf("Compare(%v, %v) = %d, want %d", test.s1, test.s2, got, test.want)
+ }
+ }
+ for _, test := range compareFloatTests {
+ if got := Compare(test.s1, test.s2); got != test.want {
+ t.Errorf("Compare(%v, %v) = %d, want %d", test.s1, test.s2, got, test.want)
+ }
+ }
+}
+
+func equalToCmp[T comparable](eq func(T, T) bool) func(T, T) int {
+ return func(v1, v2 T) int {
+ if eq(v1, v2) {
+ return 0
+ }
+ return 1
+ }
+}
+
+func TestCompareFunc(t *testing.T) {
+ intWant := func(want bool) string {
+ if want {
+ return "0"
+ }
+ return "!= 0"
+ }
+ for _, test := range equalIntTests {
+ if got := CompareFunc(test.s1, test.s2, equalToCmp(equal[int])); (got == 0) != test.want {
+ t.Errorf("CompareFunc(%v, %v, equalToCmp(equal[int])) = %d, want %s", test.s1, test.s2, got, intWant(test.want))
+ }
+ }
+ for _, test := range equalFloatTests {
+ if got := CompareFunc(test.s1, test.s2, equalToCmp(equal[float64])); (got == 0) != test.wantEqual {
+ t.Errorf("CompareFunc(%v, %v, equalToCmp(equal[float64])) = %d, want %s", test.s1, test.s2, got, intWant(test.wantEqual))
+ }
+ }
+
+ for _, test := range compareIntTests {
+ if got := CompareFunc(test.s1, test.s2, cmpCompare[int]); got != test.want {
+ t.Errorf("CompareFunc(%v, %v, cmp[int]) = %d, want %d", test.s1, test.s2, got, test.want)
+ }
+ }
+ for _, test := range compareFloatTests {
+ if got := CompareFunc(test.s1, test.s2, cmpCompare[float64]); got != test.want {
+ t.Errorf("CompareFunc(%v, %v, cmp[float64]) = %d, want %d", test.s1, test.s2, got, test.want)
+ }
+ }
+
+ s1 := []int{1, 2, 3}
+ s2 := []int{2, 3, 4}
+ if got := CompareFunc(s1, s2, equalToCmp(offByOne)); got != 0 {
+ t.Errorf("CompareFunc(%v, %v, offByOne) = %d, want 0", s1, s2, got)
+ }
+
+ s3 := []string{"a", "b", "c"}
+ s4 := []string{"A", "B", "C"}
+ if got := CompareFunc(s3, s4, strings.Compare); got != 1 {
+ t.Errorf("CompareFunc(%v, %v, strings.Compare) = %d, want 1", s3, s4, got)
+ }
+
+ compareLower := func(v1, v2 string) int {
+ return strings.Compare(strings.ToLower(v1), strings.ToLower(v2))
+ }
+ if got := CompareFunc(s3, s4, compareLower); got != 0 {
+ t.Errorf("CompareFunc(%v, %v, compareLower) = %d, want 0", s3, s4, got)
+ }
+
+ cmpIntString := func(v1 int, v2 string) int {
+ return strings.Compare(string(rune(v1)-1+'a'), v2)
+ }
+ if got := CompareFunc(s1, s3, cmpIntString); got != 0 {
+ t.Errorf("CompareFunc(%v, %v, cmpIntString) = %d, want 0", s1, s3, got)
+ }
+}
+
+var indexTests = []struct {
+ s []int
+ v int
+ want int
+}{
+ {
+ nil,
+ 0,
+ -1,
+ },
+ {
+ []int{},
+ 0,
+ -1,
+ },
+ {
+ []int{1, 2, 3},
+ 2,
+ 1,
+ },
+ {
+ []int{1, 2, 2, 3},
+ 2,
+ 1,
+ },
+ {
+ []int{1, 2, 3, 2},
+ 2,
+ 1,
+ },
+}
+
+func TestIndex(t *testing.T) {
+ for _, test := range indexTests {
+ if got := Index(test.s, test.v); got != test.want {
+ t.Errorf("Index(%v, %v) = %d, want %d", test.s, test.v, got, test.want)
+ }
+ }
+}
+
+func equalToIndex[T any](f func(T, T) bool, v1 T) func(T) bool {
+ return func(v2 T) bool {
+ return f(v1, v2)
+ }
+}
+
+func BenchmarkIndex_Large(b *testing.B) {
+ type Large [4 * 1024]byte
+
+ ss := make([]Large, 1024)
+ for i := 0; i < b.N; i++ {
+ _ = Index(ss, Large{1})
+ }
+}
+
+func TestIndexFunc(t *testing.T) {
+ for _, test := range indexTests {
+ if got := IndexFunc(test.s, equalToIndex(equal[int], test.v)); got != test.want {
+ t.Errorf("IndexFunc(%v, equalToIndex(equal[int], %v)) = %d, want %d", test.s, test.v, got, test.want)
+ }
+ }
+
+ s1 := []string{"hi", "HI"}
+ if got := IndexFunc(s1, equalToIndex(equal[string], "HI")); got != 1 {
+ t.Errorf("IndexFunc(%v, equalToIndex(equal[string], %q)) = %d, want %d", s1, "HI", got, 1)
+ }
+ if got := IndexFunc(s1, equalToIndex(strings.EqualFold, "HI")); got != 0 {
+ t.Errorf("IndexFunc(%v, equalToIndex(strings.EqualFold, %q)) = %d, want %d", s1, "HI", got, 0)
+ }
+}
+
+func BenchmarkIndexFunc_Large(b *testing.B) {
+ type Large [4 * 1024]byte
+
+ ss := make([]Large, 1024)
+ for i := 0; i < b.N; i++ {
+ _ = IndexFunc(ss, func(e Large) bool {
+ return e == Large{1}
+ })
+ }
+}
+
+func TestContains(t *testing.T) {
+ for _, test := range indexTests {
+ if got := Contains(test.s, test.v); got != (test.want != -1) {
+ t.Errorf("Contains(%v, %v) = %t, want %t", test.s, test.v, got, test.want != -1)
+ }
+ }
+}
+
+func TestContainsFunc(t *testing.T) {
+ for _, test := range indexTests {
+ if got := ContainsFunc(test.s, equalToIndex(equal[int], test.v)); got != (test.want != -1) {
+ t.Errorf("ContainsFunc(%v, equalToIndex(equal[int], %v)) = %t, want %t", test.s, test.v, got, test.want != -1)
+ }
+ }
+
+ s1 := []string{"hi", "HI"}
+ if got := ContainsFunc(s1, equalToIndex(equal[string], "HI")); got != true {
+ t.Errorf("ContainsFunc(%v, equalToContains(equal[string], %q)) = %t, want %t", s1, "HI", got, true)
+ }
+ if got := ContainsFunc(s1, equalToIndex(equal[string], "hI")); got != false {
+ t.Errorf("ContainsFunc(%v, equalToContains(strings.EqualFold, %q)) = %t, want %t", s1, "hI", got, false)
+ }
+ if got := ContainsFunc(s1, equalToIndex(strings.EqualFold, "hI")); got != true {
+ t.Errorf("ContainsFunc(%v, equalToContains(strings.EqualFold, %q)) = %t, want %t", s1, "hI", got, true)
+ }
+}
+
+var insertTests = []struct {
+ s []int
+ i int
+ add []int
+ want []int
+}{
+ {
+ []int{1, 2, 3},
+ 0,
+ []int{4},
+ []int{4, 1, 2, 3},
+ },
+ {
+ []int{1, 2, 3},
+ 1,
+ []int{4},
+ []int{1, 4, 2, 3},
+ },
+ {
+ []int{1, 2, 3},
+ 3,
+ []int{4},
+ []int{1, 2, 3, 4},
+ },
+ {
+ []int{1, 2, 3},
+ 2,
+ []int{4, 5},
+ []int{1, 2, 4, 5, 3},
+ },
+}
+
+func TestInsert(t *testing.T) {
+ s := []int{1, 2, 3}
+ if got := Insert(s, 0); !Equal(got, s) {
+ t.Errorf("Insert(%v, 0) = %v, want %v", s, got, s)
+ }
+ for _, test := range insertTests {
+ copy := Clone(test.s)
+ if got := Insert(copy, test.i, test.add...); !Equal(got, test.want) {
+ t.Errorf("Insert(%v, %d, %v...) = %v, want %v", test.s, test.i, test.add, got, test.want)
+ }
+ }
+
+ if !raceEnabled {
+ // Allocations should be amortized.
+ const count = 50
+ n := testing.AllocsPerRun(10, func() {
+ s := []int{1, 2, 3}
+ for i := 0; i < count; i++ {
+ s = Insert(s, 0, 1)
+ }
+ })
+ if n > count/2 {
+ t.Errorf("too many allocations inserting %d elements: got %v, want less than %d", count, n, count/2)
+ }
+ }
+}
+
+func TestInsertOverlap(t *testing.T) {
+ const N = 10
+ a := make([]int, N)
+ want := make([]int, 2*N)
+ for n := 0; n <= N; n++ { // length
+ for i := 0; i <= n; i++ { // insertion point
+ for x := 0; x <= N; x++ { // start of inserted data
+ for y := x; y <= N; y++ { // end of inserted data
+ for k := 0; k < N; k++ {
+ a[k] = k
+ }
+ want = want[:0]
+ want = append(want, a[:i]...)
+ want = append(want, a[x:y]...)
+ want = append(want, a[i:n]...)
+ got := Insert(a[:n], i, a[x:y]...)
+ if !Equal(got, want) {
+ t.Errorf("Insert with overlap failed n=%d i=%d x=%d y=%d, got %v want %v", n, i, x, y, got, want)
+ }
+ }
+ }
+ }
+ }
+}
+
+var deleteTests = []struct {
+ s []int
+ i, j int
+ want []int
+}{
+ {
+ []int{1, 2, 3},
+ 0,
+ 0,
+ []int{1, 2, 3},
+ },
+ {
+ []int{1, 2, 3},
+ 0,
+ 1,
+ []int{2, 3},
+ },
+ {
+ []int{1, 2, 3},
+ 3,
+ 3,
+ []int{1, 2, 3},
+ },
+ {
+ []int{1, 2, 3},
+ 0,
+ 2,
+ []int{3},
+ },
+ {
+ []int{1, 2, 3},
+ 0,
+ 3,
+ []int{},
+ },
+}
+
+func TestDelete(t *testing.T) {
+ for _, test := range deleteTests {
+ copy := Clone(test.s)
+ if got := Delete(copy, test.i, test.j); !Equal(got, test.want) {
+ t.Errorf("Delete(%v, %d, %d) = %v, want %v", test.s, test.i, test.j, got, test.want)
+ }
+ }
+}
+
+var deleteFuncTests = []struct {
+ s []int
+ fn func(int) bool
+ want []int
+}{
+ {
+ nil,
+ func(int) bool { return true },
+ nil,
+ },
+ {
+ []int{1, 2, 3},
+ func(int) bool { return true },
+ nil,
+ },
+ {
+ []int{1, 2, 3},
+ func(int) bool { return false },
+ []int{1, 2, 3},
+ },
+ {
+ []int{1, 2, 3},
+ func(i int) bool { return i > 2 },
+ []int{1, 2},
+ },
+ {
+ []int{1, 2, 3},
+ func(i int) bool { return i < 2 },
+ []int{2, 3},
+ },
+ {
+ []int{10, 2, 30},
+ func(i int) bool { return i >= 10 },
+ []int{2},
+ },
+}
+
+func TestDeleteFunc(t *testing.T) {
+ for i, test := range deleteFuncTests {
+ copy := Clone(test.s)
+ if got := DeleteFunc(copy, test.fn); !Equal(got, test.want) {
+ t.Errorf("DeleteFunc case %d: got %v, want %v", i, got, test.want)
+ }
+ }
+}
+
+func panics(f func()) (b bool) {
+ defer func() {
+ if x := recover(); x != nil {
+ b = true
+ }
+ }()
+ f()
+ return false
+}
+
+func TestDeletePanics(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ s []int
+ i, j int
+ }{
+ {"with negative first index", []int{42}, -2, 1},
+ {"with negative second index", []int{42}, 1, -1},
+ {"with out-of-bounds first index", []int{42}, 2, 3},
+ {"with out-of-bounds second index", []int{42}, 0, 2},
+ {"with invalid i>j", []int{42}, 1, 0},
+ } {
+ if !panics(func() { Delete(test.s, test.i, test.j) }) {
+ t.Errorf("Delete %s: got no panic, want panic", test.name)
+ }
+ }
+}
+
+func TestClone(t *testing.T) {
+ s1 := []int{1, 2, 3}
+ s2 := Clone(s1)
+ if !Equal(s1, s2) {
+ t.Errorf("Clone(%v) = %v, want %v", s1, s2, s1)
+ }
+ s1[0] = 4
+ want := []int{1, 2, 3}
+ if !Equal(s2, want) {
+ t.Errorf("Clone(%v) changed unexpectedly to %v", want, s2)
+ }
+ if got := Clone([]int(nil)); got != nil {
+ t.Errorf("Clone(nil) = %#v, want nil", got)
+ }
+ if got := Clone(s1[:0]); got == nil || len(got) != 0 {
+ t.Errorf("Clone(%v) = %#v, want %#v", s1[:0], got, s1[:0])
+ }
+}
+
+var compactTests = []struct {
+ name string
+ s []int
+ want []int
+}{
+ {
+ "nil",
+ nil,
+ nil,
+ },
+ {
+ "one",
+ []int{1},
+ []int{1},
+ },
+ {
+ "sorted",
+ []int{1, 2, 3},
+ []int{1, 2, 3},
+ },
+ {
+ "1 item",
+ []int{1, 1, 2},
+ []int{1, 2},
+ },
+ {
+ "unsorted",
+ []int{1, 2, 1},
+ []int{1, 2, 1},
+ },
+ {
+ "many",
+ []int{1, 2, 2, 3, 3, 4},
+ []int{1, 2, 3, 4},
+ },
+}
+
+func TestCompact(t *testing.T) {
+ for _, test := range compactTests {
+ copy := Clone(test.s)
+ if got := Compact(copy); !Equal(got, test.want) {
+ t.Errorf("Compact(%v) = %v, want %v", test.s, got, test.want)
+ }
+ }
+}
+
+func BenchmarkCompact(b *testing.B) {
+ for _, c := range compactTests {
+ b.Run(c.name, func(b *testing.B) {
+ ss := make([]int, 0, 64)
+ for k := 0; k < b.N; k++ {
+ ss = ss[:0]
+ ss = append(ss, c.s...)
+ _ = Compact(ss)
+ }
+ })
+ }
+}
+
+func BenchmarkCompact_Large(b *testing.B) {
+ type Large [4 * 1024]byte
+
+ ss := make([]Large, 1024)
+ for i := 0; i < b.N; i++ {
+ _ = Compact(ss)
+ }
+}
+
+func TestCompactFunc(t *testing.T) {
+ for _, test := range compactTests {
+ copy := Clone(test.s)
+ if got := CompactFunc(copy, equal[int]); !Equal(got, test.want) {
+ t.Errorf("CompactFunc(%v, equal[int]) = %v, want %v", test.s, got, test.want)
+ }
+ }
+
+ s1 := []string{"a", "a", "A", "B", "b"}
+ copy := Clone(s1)
+ want := []string{"a", "B"}
+ if got := CompactFunc(copy, strings.EqualFold); !Equal(got, want) {
+ t.Errorf("CompactFunc(%v, strings.EqualFold) = %v, want %v", s1, got, want)
+ }
+}
+
+func BenchmarkCompactFunc_Large(b *testing.B) {
+ type Large [4 * 1024]byte
+
+ ss := make([]Large, 1024)
+ for i := 0; i < b.N; i++ {
+ _ = CompactFunc(ss, func(a, b Large) bool { return a == b })
+ }
+}
+
+func TestGrow(t *testing.T) {
+ s1 := []int{1, 2, 3}
+
+ copy := Clone(s1)
+ s2 := Grow(copy, 1000)
+ if !Equal(s1, s2) {
+ t.Errorf("Grow(%v) = %v, want %v", s1, s2, s1)
+ }
+ if cap(s2) < 1000+len(s1) {
+ t.Errorf("after Grow(%v) cap = %d, want >= %d", s1, cap(s2), 1000+len(s1))
+ }
+
+ // Test mutation of elements between length and capacity.
+ copy = Clone(s1)
+ s3 := Grow(copy[:1], 2)[:3]
+ if !Equal(s1, s3) {
+ t.Errorf("Grow should not mutate elements between length and capacity")
+ }
+ s3 = Grow(copy[:1], 1000)[:3]
+ if !Equal(s1, s3) {
+ t.Errorf("Grow should not mutate elements between length and capacity")
+ }
+
+ // Test number of allocations.
+ if n := testing.AllocsPerRun(100, func() { Grow(s2, cap(s2)-len(s2)) }); n != 0 {
+ t.Errorf("Grow should not allocate when given sufficient capacity; allocated %v times", n)
+ }
+ if n := testing.AllocsPerRun(100, func() { Grow(s2, cap(s2)-len(s2)+1) }); n != 1 {
+ errorf := t.Errorf
+ if raceEnabled {
+ errorf = t.Logf // this allocates multiple times in race detector mode
+ }
+ errorf("Grow should allocate once when given insufficient capacity; allocated %v times", n)
+ }
+
+ // Test for negative growth sizes.
+ var gotPanic bool
+ func() {
+ defer func() { gotPanic = recover() != nil }()
+ Grow(s1, -1)
+ }()
+ if !gotPanic {
+ t.Errorf("Grow(-1) did not panic; expected a panic")
+ }
+}
+
+func TestClip(t *testing.T) {
+ s1 := []int{1, 2, 3, 4, 5, 6}[:3]
+ orig := Clone(s1)
+ if len(s1) != 3 {
+ t.Errorf("len(%v) = %d, want 3", s1, len(s1))
+ }
+ if cap(s1) < 6 {
+ t.Errorf("cap(%v[:3]) = %d, want >= 6", orig, cap(s1))
+ }
+ s2 := Clip(s1)
+ if !Equal(s1, s2) {
+ t.Errorf("Clip(%v) = %v, want %v", s1, s2, s1)
+ }
+ if cap(s2) != 3 {
+ t.Errorf("cap(Clip(%v)) = %d, want 3", orig, cap(s2))
+ }
+}
+
+func TestReverse(t *testing.T) {
+ even := []int{3, 1, 4, 1, 5, 9} // len = 6
+ Reverse(even)
+ if want := []int{9, 5, 1, 4, 1, 3}; !Equal(even, want) {
+ t.Errorf("Reverse(even) = %v, want %v", even, want)
+ }
+
+ odd := []int{3, 1, 4, 1, 5, 9, 2} // len = 7
+ Reverse(odd)
+ if want := []int{2, 9, 5, 1, 4, 1, 3}; !Equal(odd, want) {
+ t.Errorf("Reverse(odd) = %v, want %v", odd, want)
+ }
+
+ words := strings.Fields("one two three")
+ Reverse(words)
+ if want := strings.Fields("three two one"); !Equal(words, want) {
+ t.Errorf("Reverse(words) = %v, want %v", words, want)
+ }
+
+ singleton := []string{"one"}
+ Reverse(singleton)
+ if want := []string{"one"}; !Equal(singleton, want) {
+ t.Errorf("Reverse(singeleton) = %v, want %v", singleton, want)
+ }
+
+ Reverse[[]string](nil)
+}
+
+// naiveReplace is a baseline implementation to the Replace function.
+func naiveReplace[S ~[]E, E any](s S, i, j int, v ...E) S {
+ s = Delete(s, i, j)
+ s = Insert(s, i, v...)
+ return s
+}
+
+func TestReplace(t *testing.T) {
+ for _, test := range []struct {
+ s, v []int
+ i, j int
+ }{
+ {}, // all zero value
+ {
+ s: []int{1, 2, 3, 4},
+ v: []int{5},
+ i: 1,
+ j: 2,
+ },
+ {
+ s: []int{1, 2, 3, 4},
+ v: []int{5, 6, 7, 8},
+ i: 1,
+ j: 2,
+ },
+ {
+ s: func() []int {
+ s := make([]int, 3, 20)
+ s[0] = 0
+ s[1] = 1
+ s[2] = 2
+ return s
+ }(),
+ v: []int{3, 4, 5, 6, 7},
+ i: 0,
+ j: 1,
+ },
+ } {
+ ss, vv := Clone(test.s), Clone(test.v)
+ want := naiveReplace(ss, test.i, test.j, vv...)
+ got := Replace(test.s, test.i, test.j, test.v...)
+ if !Equal(got, want) {
+ t.Errorf("Replace(%v, %v, %v, %v) = %v, want %v", test.s, test.i, test.j, test.v, got, want)
+ }
+ }
+}
+
+func TestReplacePanics(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ s, v []int
+ i, j int
+ }{
+ {"indexes out of order", []int{1, 2}, []int{3}, 2, 1},
+ {"large index", []int{1, 2}, []int{3}, 1, 10},
+ {"negative index", []int{1, 2}, []int{3}, -1, 2},
+ } {
+ ss, vv := Clone(test.s), Clone(test.v)
+ if !panics(func() { Replace(ss, test.i, test.j, vv...) }) {
+ t.Errorf("Replace %s: should have panicked", test.name)
+ }
+ }
+}
+
+func TestReplaceOverlap(t *testing.T) {
+ const N = 10
+ a := make([]int, N)
+ want := make([]int, 2*N)
+ for n := 0; n <= N; n++ { // length
+ for i := 0; i <= n; i++ { // insertion point 1
+ for j := i; j <= n; j++ { // insertion point 2
+ for x := 0; x <= N; x++ { // start of inserted data
+ for y := x; y <= N; y++ { // end of inserted data
+ for k := 0; k < N; k++ {
+ a[k] = k
+ }
+ want = want[:0]
+ want = append(want, a[:i]...)
+ want = append(want, a[x:y]...)
+ want = append(want, a[j:n]...)
+ got := Replace(a[:n], i, j, a[x:y]...)
+ if !Equal(got, want) {
+ t.Errorf("Insert with overlap failed n=%d i=%d j=%d x=%d y=%d, got %v want %v", n, i, j, x, y, got, want)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+func BenchmarkReplace(b *testing.B) {
+ cases := []struct {
+ name string
+ s, v func() []int
+ i, j int
+ }{
+ {
+ name: "fast",
+ s: func() []int {
+ return make([]int, 100)
+ },
+ v: func() []int {
+ return make([]int, 20)
+ },
+ i: 10,
+ j: 40,
+ },
+ {
+ name: "slow",
+ s: func() []int {
+ return make([]int, 100)
+ },
+ v: func() []int {
+ return make([]int, 20)
+ },
+ i: 0,
+ j: 2,
+ },
+ }
+
+ for _, c := range cases {
+ b.Run("naive-"+c.name, func(b *testing.B) {
+ for k := 0; k < b.N; k++ {
+ s := c.s()
+ v := c.v()
+ _ = naiveReplace(s, c.i, c.j, v...)
+ }
+ })
+ b.Run("optimized-"+c.name, func(b *testing.B) {
+ for k := 0; k < b.N; k++ {
+ s := c.s()
+ v := c.v()
+ _ = Replace(s, c.i, c.j, v...)
+ }
+ })
+ }
+
+}
+
+func TestRotate(t *testing.T) {
+ const N = 10
+ s := make([]int, 0, N)
+ for n := 0; n < N; n++ {
+ for r := 0; r < n; r++ {
+ s = s[:0]
+ for i := 0; i < n; i++ {
+ s = append(s, i)
+ }
+ rotateLeft(s, r)
+ for i := 0; i < n; i++ {
+ if s[i] != (i+r)%n {
+ t.Errorf("expected n=%d r=%d i:%d want:%d got:%d", n, r, i, (i+r)%n, s[i])
+ }
+ }
+ }
+ }
+}
+
+func TestInsertGrowthRate(t *testing.T) {
+ b := make([]byte, 1)
+ maxCap := cap(b)
+ nGrow := 0
+ const N = 1e6
+ for i := 0; i < N; i++ {
+ b = Insert(b, len(b)-1, 0)
+ if cap(b) > maxCap {
+ maxCap = cap(b)
+ nGrow++
+ }
+ }
+ want := int(math.Log(N) / math.Log(1.25)) // 1.25 == growth rate for large slices
+ if nGrow > want {
+ t.Errorf("too many grows. got:%d want:%d", nGrow, want)
+ }
+}
+
+func TestReplaceGrowthRate(t *testing.T) {
+ b := make([]byte, 2)
+ maxCap := cap(b)
+ nGrow := 0
+ const N = 1e6
+ for i := 0; i < N; i++ {
+ b = Replace(b, len(b)-2, len(b)-1, 0, 0)
+ if cap(b) > maxCap {
+ maxCap = cap(b)
+ nGrow++
+ }
+ }
+ want := int(math.Log(N) / math.Log(1.25)) // 1.25 == growth rate for large slices
+ if nGrow > want {
+ t.Errorf("too many grows. got:%d want:%d", nGrow, want)
+ }
+}
diff --git a/slices/sort.go b/slices/sort.go
new file mode 100644
index 0000000..b67897f
--- /dev/null
+++ b/slices/sort.go
@@ -0,0 +1,195 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp
+
+package slices
+
+import (
+ "math/bits"
+
+ "golang.org/x/exp/constraints"
+)
+
+// Sort sorts a slice of any ordered type in ascending order.
+// When sorting floating-point numbers, NaNs are ordered before other values.
+func Sort[S ~[]E, E constraints.Ordered](x S) {
+ n := len(x)
+ pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
+}
+
+// SortFunc sorts the slice x in ascending order as determined by the cmp
+// function. This sort is not guaranteed to be stable.
+// cmp(a, b) should return a negative number when a < b, a positive number when
+// a > b and zero when a == b.
+//
+// SortFunc requires that cmp is a strict weak ordering.
+// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
+func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
+ n := len(x)
+ pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp)
+}
+
+// SortStableFunc sorts the slice x while keeping the original order of equal
+// elements, using cmp to compare elements in the same way as [SortFunc].
+func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
+ stableCmpFunc(x, len(x), cmp)
+}
+
+// IsSorted reports whether x is sorted in ascending order.
+func IsSorted[S ~[]E, E constraints.Ordered](x S) bool {
+ for i := len(x) - 1; i > 0; i-- {
+ if cmpLess(x[i], x[i-1]) {
+ return false
+ }
+ }
+ return true
+}
+
+// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
+// comparison function as defined by [SortFunc].
+func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
+ for i := len(x) - 1; i > 0; i-- {
+ if cmp(x[i], x[i-1]) < 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// Min returns the minimal value in x. It panics if x is empty.
+// For floating-point numbers, Min propagates NaNs (any NaN value in x
+// forces the output to be NaN).
+func Min[S ~[]E, E constraints.Ordered](x S) E {
+ if len(x) < 1 {
+ panic("slices.Min: empty list")
+ }
+ m := x[0]
+ for i := 1; i < len(x); i++ {
+ m = min(m, x[i])
+ }
+ return m
+}
+
+// MinFunc returns the minimal value in x, using cmp to compare elements.
+// It panics if x is empty. If there is more than one minimal element
+// according to the cmp function, MinFunc returns the first one.
+func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
+ if len(x) < 1 {
+ panic("slices.MinFunc: empty list")
+ }
+ m := x[0]
+ for i := 1; i < len(x); i++ {
+ if cmp(x[i], m) < 0 {
+ m = x[i]
+ }
+ }
+ return m
+}
+
+// Max returns the maximal value in x. It panics if x is empty.
+// For floating-point E, Max propagates NaNs (any NaN value in x
+// forces the output to be NaN).
+func Max[S ~[]E, E constraints.Ordered](x S) E {
+ if len(x) < 1 {
+ panic("slices.Max: empty list")
+ }
+ m := x[0]
+ for i := 1; i < len(x); i++ {
+ m = max(m, x[i])
+ }
+ return m
+}
+
+// MaxFunc returns the maximal value in x, using cmp to compare elements.
+// It panics if x is empty. If there is more than one maximal element
+// according to the cmp function, MaxFunc returns the first one.
+func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
+ if len(x) < 1 {
+ panic("slices.MaxFunc: empty list")
+ }
+ m := x[0]
+ for i := 1; i < len(x); i++ {
+ if cmp(x[i], m) > 0 {
+ m = x[i]
+ }
+ }
+ return m
+}
+
+// BinarySearch searches for target in a sorted slice and returns the position
+// where target is found, or the position where target would appear in the
+// sort order; it also returns a bool saying whether the target is really found
+// in the slice. The slice must be sorted in increasing order.
+func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) {
+ // Inlining is faster than calling BinarySearchFunc with a lambda.
+ n := len(x)
+ // Define x[-1] < target and x[n] >= target.
+ // Invariant: x[i-1] < target, x[j] >= target.
+ i, j := 0, n
+ for i < j {
+ h := int(uint(i+j) >> 1) // avoid overflow when computing h
+ // i ≀ h < j
+ if cmpLess(x[h], target) {
+ i = h + 1 // preserves x[i-1] < target
+ } else {
+ j = h // preserves x[j] >= target
+ }
+ }
+ // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i.
+ return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target)))
+}
+
+// BinarySearchFunc works like [BinarySearch], but uses a custom comparison
+// function. The slice must be sorted in increasing order, where "increasing"
+// is defined by cmp. cmp should return 0 if the slice element matches
+// the target, a negative number if the slice element precedes the target,
+// or a positive number if the slice element follows the target.
+// cmp must implement the same ordering as the slice, such that if
+// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
+func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
+ n := len(x)
+ // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 .
+ // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0.
+ i, j := 0, n
+ for i < j {
+ h := int(uint(i+j) >> 1) // avoid overflow when computing h
+ // i ≀ h < j
+ if cmp(x[h], target) < 0 {
+ i = h + 1 // preserves cmp(x[i - 1], target) < 0
+ } else {
+ j = h // preserves cmp(x[j], target) >= 0
+ }
+ }
+ // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i.
+ return i, i < n && cmp(x[i], target) == 0
+}
+
+type sortedHint int // hint for pdqsort when choosing the pivot
+
+const (
+ unknownHint sortedHint = iota
+ increasingHint
+ decreasingHint
+)
+
+// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
+type xorshift uint64
+
+func (r *xorshift) Next() uint64 {
+ *r ^= *r << 13
+ *r ^= *r >> 17
+ *r ^= *r << 5
+ return uint64(*r)
+}
+
+func nextPowerOfTwo(length int) uint {
+ return 1 << bits.Len(uint(length))
+}
+
+// isNaN reports whether x is a NaN without requiring the math package.
+// This will always return false if T is not floating-point.
+func isNaN[T constraints.Ordered](x T) bool {
+ return x != x
+}
diff --git a/slices/sort_benchmark_test.go b/slices/sort_benchmark_test.go
new file mode 100644
index 0000000..6aa03c8
--- /dev/null
+++ b/slices/sort_benchmark_test.go
@@ -0,0 +1,284 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import (
+ "fmt"
+ "math/rand"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// These benchmarks compare sorting a large slice of int with sort.Ints vs.
+// slices.Sort
+func makeRandomInts(n int) []int {
+ rand.Seed(42)
+ ints := make([]int, n)
+ for i := 0; i < n; i++ {
+ ints[i] = rand.Intn(n)
+ }
+ return ints
+}
+
+func makeSortedInts(n int) []int {
+ ints := make([]int, n)
+ for i := 0; i < n; i++ {
+ ints[i] = i
+ }
+ return ints
+}
+
+func makeReversedInts(n int) []int {
+ ints := make([]int, n)
+ for i := 0; i < n; i++ {
+ ints[i] = n - i
+ }
+ return ints
+}
+
+const N = 100_000
+
+func BenchmarkSortInts(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ints := makeRandomInts(N)
+ b.StartTimer()
+ sort.Ints(ints)
+ }
+}
+
+func makeSortedStrings(n int) []string {
+ x := make([]string, n)
+ for i := 0; i < n; i++ {
+ x[i] = strconv.Itoa(i)
+ }
+ Sort(x)
+ return x
+}
+
+func BenchmarkSlicesSortInts(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ints := makeRandomInts(N)
+ b.StartTimer()
+ Sort(ints)
+ }
+}
+
+func BenchmarkSlicesSortInts_Sorted(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ints := makeSortedInts(N)
+ b.StartTimer()
+ Sort(ints)
+ }
+}
+
+func BenchmarkSlicesSortInts_Reversed(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ints := makeReversedInts(N)
+ b.StartTimer()
+ Sort(ints)
+ }
+}
+
+func BenchmarkIntsAreSorted(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ints := makeSortedInts(N)
+ b.StartTimer()
+ sort.IntsAreSorted(ints)
+ }
+}
+
+func BenchmarkIsSorted(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ints := makeSortedInts(N)
+ b.StartTimer()
+ IsSorted(ints)
+ }
+}
+
+// Since we're benchmarking these sorts against each other, make sure that they
+// generate similar results.
+func TestIntSorts(t *testing.T) {
+ ints := makeRandomInts(200)
+ ints2 := Clone(ints)
+
+ sort.Ints(ints)
+ Sort(ints2)
+
+ for i := range ints {
+ if ints[i] != ints2[i] {
+ t.Fatalf("ints2 mismatch at %d; %d != %d", i, ints[i], ints2[i])
+ }
+ }
+}
+
+// The following is a benchmark for sorting strings.
+
+// makeRandomStrings generates n random strings with alphabetic runes of
+// varying lengths.
+func makeRandomStrings(n int) []string {
+ rand.Seed(42)
+ var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ ss := make([]string, n)
+ for i := 0; i < n; i++ {
+ var sb strings.Builder
+ slen := 2 + rand.Intn(50)
+ for j := 0; j < slen; j++ {
+ sb.WriteRune(letters[rand.Intn(len(letters))])
+ }
+ ss[i] = sb.String()
+ }
+ return ss
+}
+
+func TestStringSorts(t *testing.T) {
+ ss := makeRandomStrings(200)
+ ss2 := Clone(ss)
+
+ sort.Strings(ss)
+ Sort(ss2)
+
+ for i := range ss {
+ if ss[i] != ss2[i] {
+ t.Fatalf("ss2 mismatch at %d; %s != %s", i, ss[i], ss2[i])
+ }
+ }
+}
+
+func BenchmarkSortStrings(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ss := makeRandomStrings(N)
+ b.StartTimer()
+ sort.Strings(ss)
+ }
+}
+
+func BenchmarkSortStrings_Sorted(b *testing.B) {
+ ss := makeSortedStrings(N)
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ sort.Strings(ss)
+ }
+}
+
+func BenchmarkSlicesSortStrings(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ss := makeRandomStrings(N)
+ b.StartTimer()
+ Sort(ss)
+ }
+}
+
+func BenchmarkSlicesSortStrings_Sorted(b *testing.B) {
+ ss := makeSortedStrings(N)
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ Sort(ss)
+ }
+}
+
+// These benchmarks compare sorting a slice of structs with sort.Sort vs.
+// slices.SortFunc.
+type myStruct struct {
+ a, b, c, d string
+ n int
+}
+
+type myStructs []*myStruct
+
+func (s myStructs) Len() int { return len(s) }
+func (s myStructs) Less(i, j int) bool { return s[i].n < s[j].n }
+func (s myStructs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+func makeRandomStructs(n int) myStructs {
+ rand.Seed(42)
+ structs := make([]*myStruct, n)
+ for i := 0; i < n; i++ {
+ structs[i] = &myStruct{n: rand.Intn(n)}
+ }
+ return structs
+}
+
+func TestStructSorts(t *testing.T) {
+ ss := makeRandomStructs(200)
+ ss2 := make([]*myStruct, len(ss))
+ for i := range ss {
+ ss2[i] = &myStruct{n: ss[i].n}
+ }
+
+ sort.Sort(ss)
+ SortFunc(ss2, func(a, b *myStruct) int { return a.n - b.n })
+
+ for i := range ss {
+ if *ss[i] != *ss2[i] {
+ t.Fatalf("ints2 mismatch at %d; %v != %v", i, *ss[i], *ss2[i])
+ }
+ }
+}
+
+func BenchmarkSortStructs(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ss := makeRandomStructs(N)
+ b.StartTimer()
+ sort.Sort(ss)
+ }
+}
+
+func BenchmarkSortFuncStructs(b *testing.B) {
+ cmpFunc := func(a, b *myStruct) int { return a.n - b.n }
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ ss := makeRandomStructs(N)
+ b.StartTimer()
+ SortFunc(ss, cmpFunc)
+ }
+}
+
+func BenchmarkBinarySearchFloats(b *testing.B) {
+ for _, size := range []int{16, 32, 64, 128, 512, 1024} {
+ b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
+ floats := make([]float64, size)
+ for i := range floats {
+ floats[i] = float64(i)
+ }
+ midpoint := len(floats) / 2
+ needle := (floats[midpoint] + floats[midpoint+1]) / 2
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ BinarySearch(floats, needle)
+ }
+ })
+ }
+}
+
+func BenchmarkBinarySearchFuncStruct(b *testing.B) {
+ for _, size := range []int{16, 32, 64, 128, 512, 1024} {
+ b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
+ structs := make([]*myStruct, size)
+ for i := range structs {
+ structs[i] = &myStruct{n: i}
+ }
+ midpoint := len(structs) / 2
+ needle := &myStruct{n: (structs[midpoint].n + structs[midpoint+1].n) / 2}
+ lessFunc := func(a, b *myStruct) int { return a.n - b.n }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ BinarySearchFunc(structs, needle, lessFunc)
+ }
+ })
+ }
+}
diff --git a/slices/sort_test.go b/slices/sort_test.go
new file mode 100644
index 0000000..501b5ee
--- /dev/null
+++ b/slices/sort_test.go
@@ -0,0 +1,441 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import (
+ "fmt"
+ "math"
+ "math/rand"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
+var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8, 74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3}
+var float64sWithNaNs = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8}
+var strs = [...]string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"}
+
+func TestSortIntSlice(t *testing.T) {
+ data := Clone(ints[:])
+ Sort(data)
+ if !IsSorted(data) {
+ t.Errorf("sorted %v", ints)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortFuncIntSlice(t *testing.T) {
+ data := Clone(ints[:])
+ SortFunc(data, func(a, b int) int { return a - b })
+ if !IsSorted(data) {
+ t.Errorf("sorted %v", ints)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortFloat64Slice(t *testing.T) {
+ data := Clone(float64s[:])
+ Sort(data)
+ if !IsSorted(data) {
+ t.Errorf("sorted %v", float64s)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortFloat64SliceWithNaNs(t *testing.T) {
+ data := float64sWithNaNs[:]
+ data2 := Clone(data)
+
+ Sort(data)
+ sort.Float64s(data2)
+
+ if !IsSorted(data) {
+ t.Error("IsSorted indicates data isn't sorted")
+ }
+
+ // Compare for equality using cmp.Compare, which considers NaNs equal.
+ if !EqualFunc(data, data2, func(a, b float64) bool { return cmpCompare(a, b) == 0 }) {
+ t.Errorf("mismatch between Sort and sort.Float64: got %v, want %v", data, data2)
+ }
+}
+
+func TestSortStringSlice(t *testing.T) {
+ data := Clone(strs[:])
+ Sort(data)
+ if !IsSorted(data) {
+ t.Errorf("sorted %v", strs)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortLarge_Random(t *testing.T) {
+ n := 1000000
+ if testing.Short() {
+ n /= 100
+ }
+ data := make([]int, n)
+ for i := 0; i < len(data); i++ {
+ data[i] = rand.Intn(100)
+ }
+ if IsSorted(data) {
+ t.Fatalf("terrible rand.rand")
+ }
+ Sort(data)
+ if !IsSorted(data) {
+ t.Errorf("sort didn't sort - 1M ints")
+ }
+}
+
+type intPair struct {
+ a, b int
+}
+
+type intPairs []intPair
+
+// Pairs compare on a only.
+func intPairCmp(x, y intPair) int {
+ return x.a - y.a
+}
+
+// Record initial order in B.
+func (d intPairs) initB() {
+ for i := range d {
+ d[i].b = i
+ }
+}
+
+// InOrder checks if a-equal elements were not reordered.
+func (d intPairs) inOrder() bool {
+ lastA, lastB := -1, 0
+ for i := 0; i < len(d); i++ {
+ if lastA != d[i].a {
+ lastA = d[i].a
+ lastB = d[i].b
+ continue
+ }
+ if d[i].b <= lastB {
+ return false
+ }
+ lastB = d[i].b
+ }
+ return true
+}
+
+func TestStability(t *testing.T) {
+ n, m := 100000, 1000
+ if testing.Short() {
+ n, m = 1000, 100
+ }
+ data := make(intPairs, n)
+
+ // random distribution
+ for i := 0; i < len(data); i++ {
+ data[i].a = rand.Intn(m)
+ }
+ if IsSortedFunc(data, intPairCmp) {
+ t.Fatalf("terrible rand.rand")
+ }
+ data.initB()
+ SortStableFunc(data, intPairCmp)
+ if !IsSortedFunc(data, intPairCmp) {
+ t.Errorf("Stable didn't sort %d ints", n)
+ }
+ if !data.inOrder() {
+ t.Errorf("Stable wasn't stable on %d ints", n)
+ }
+
+ // already sorted
+ data.initB()
+ SortStableFunc(data, intPairCmp)
+ if !IsSortedFunc(data, intPairCmp) {
+ t.Errorf("Stable shuffled sorted %d ints (order)", n)
+ }
+ if !data.inOrder() {
+ t.Errorf("Stable shuffled sorted %d ints (stability)", n)
+ }
+
+ // sorted reversed
+ for i := 0; i < len(data); i++ {
+ data[i].a = len(data) - i
+ }
+ data.initB()
+ SortStableFunc(data, intPairCmp)
+ if !IsSortedFunc(data, intPairCmp) {
+ t.Errorf("Stable didn't sort %d ints", n)
+ }
+ if !data.inOrder() {
+ t.Errorf("Stable wasn't stable on %d ints", n)
+ }
+}
+
+type S struct {
+ a int
+ b string
+}
+
+func cmpS(s1, s2 S) int {
+ return cmpCompare(s1.a, s2.a)
+}
+
+func TestMinMax(t *testing.T) {
+ intCmp := func(a, b int) int { return a - b }
+
+ tests := []struct {
+ data []int
+ wantMin int
+ wantMax int
+ }{
+ {[]int{7}, 7, 7},
+ {[]int{1, 2}, 1, 2},
+ {[]int{2, 1}, 1, 2},
+ {[]int{1, 2, 3}, 1, 3},
+ {[]int{3, 2, 1}, 1, 3},
+ {[]int{2, 1, 3}, 1, 3},
+ {[]int{2, 2, 3}, 2, 3},
+ {[]int{3, 2, 3}, 2, 3},
+ {[]int{0, 2, -9}, -9, 2},
+ }
+ for _, tt := range tests {
+ t.Run(fmt.Sprintf("%v", tt.data), func(t *testing.T) {
+ gotMin := Min(tt.data)
+ if gotMin != tt.wantMin {
+ t.Errorf("Min got %v, want %v", gotMin, tt.wantMin)
+ }
+
+ gotMinFunc := MinFunc(tt.data, intCmp)
+ if gotMinFunc != tt.wantMin {
+ t.Errorf("MinFunc got %v, want %v", gotMinFunc, tt.wantMin)
+ }
+
+ gotMax := Max(tt.data)
+ if gotMax != tt.wantMax {
+ t.Errorf("Max got %v, want %v", gotMax, tt.wantMax)
+ }
+
+ gotMaxFunc := MaxFunc(tt.data, intCmp)
+ if gotMaxFunc != tt.wantMax {
+ t.Errorf("MaxFunc got %v, want %v", gotMaxFunc, tt.wantMax)
+ }
+ })
+ }
+
+ svals := []S{
+ {1, "a"},
+ {2, "a"},
+ {1, "b"},
+ {2, "b"},
+ }
+
+ gotMin := MinFunc(svals, cmpS)
+ wantMin := S{1, "a"}
+ if gotMin != wantMin {
+ t.Errorf("MinFunc(%v) = %v, want %v", svals, gotMin, wantMin)
+ }
+
+ gotMax := MaxFunc(svals, cmpS)
+ wantMax := S{2, "a"}
+ if gotMax != wantMax {
+ t.Errorf("MaxFunc(%v) = %v, want %v", svals, gotMax, wantMax)
+ }
+}
+
+func TestMinMaxNaNs(t *testing.T) {
+ fs := []float64{1.0, 999.9, 3.14, -400.4, -5.14}
+ if Min(fs) != -400.4 {
+ t.Errorf("got min %v, want -400.4", Min(fs))
+ }
+ if Max(fs) != 999.9 {
+ t.Errorf("got max %v, want 999.9", Max(fs))
+ }
+
+ // No matter which element of fs is replaced with a NaN, both Min and Max
+ // should propagate the NaN to their output.
+ for i := 0; i < len(fs); i++ {
+ testfs := Clone(fs)
+ testfs[i] = math.NaN()
+
+ fmin := Min(testfs)
+ if !math.IsNaN(fmin) {
+ t.Errorf("got min %v, want NaN", fmin)
+ }
+
+ fmax := Max(testfs)
+ if !math.IsNaN(fmax) {
+ t.Errorf("got max %v, want NaN", fmax)
+ }
+ }
+}
+
+func TestMinMaxPanics(t *testing.T) {
+ intCmp := func(a, b int) int { return a - b }
+ emptySlice := []int{}
+
+ if !panics(func() { Min(emptySlice) }) {
+ t.Errorf("Min([]): got no panic, want panic")
+ }
+
+ if !panics(func() { Max(emptySlice) }) {
+ t.Errorf("Max([]): got no panic, want panic")
+ }
+
+ if !panics(func() { MinFunc(emptySlice, intCmp) }) {
+ t.Errorf("MinFunc([]): got no panic, want panic")
+ }
+
+ if !panics(func() { MaxFunc(emptySlice, intCmp) }) {
+ t.Errorf("MaxFunc([]): got no panic, want panic")
+ }
+}
+
+func TestBinarySearch(t *testing.T) {
+ str1 := []string{"foo"}
+ str2 := []string{"ab", "ca"}
+ str3 := []string{"mo", "qo", "vo"}
+ str4 := []string{"ab", "ad", "ca", "xy"}
+
+ // slice with repeating elements
+ strRepeats := []string{"ba", "ca", "da", "da", "da", "ka", "ma", "ma", "ta"}
+
+ // slice with all element equal
+ strSame := []string{"xx", "xx", "xx"}
+
+ tests := []struct {
+ data []string
+ target string
+ wantPos int
+ wantFound bool
+ }{
+ {[]string{}, "foo", 0, false},
+ {[]string{}, "", 0, false},
+
+ {str1, "foo", 0, true},
+ {str1, "bar", 0, false},
+ {str1, "zx", 1, false},
+
+ {str2, "aa", 0, false},
+ {str2, "ab", 0, true},
+ {str2, "ad", 1, false},
+ {str2, "ca", 1, true},
+ {str2, "ra", 2, false},
+
+ {str3, "bb", 0, false},
+ {str3, "mo", 0, true},
+ {str3, "nb", 1, false},
+ {str3, "qo", 1, true},
+ {str3, "tr", 2, false},
+ {str3, "vo", 2, true},
+ {str3, "xr", 3, false},
+
+ {str4, "aa", 0, false},
+ {str4, "ab", 0, true},
+ {str4, "ac", 1, false},
+ {str4, "ad", 1, true},
+ {str4, "ax", 2, false},
+ {str4, "ca", 2, true},
+ {str4, "cc", 3, false},
+ {str4, "dd", 3, false},
+ {str4, "xy", 3, true},
+ {str4, "zz", 4, false},
+
+ {strRepeats, "da", 2, true},
+ {strRepeats, "db", 5, false},
+ {strRepeats, "ma", 6, true},
+ {strRepeats, "mb", 8, false},
+
+ {strSame, "xx", 0, true},
+ {strSame, "ab", 0, false},
+ {strSame, "zz", 3, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.target, func(t *testing.T) {
+ {
+ pos, found := BinarySearch(tt.data, tt.target)
+ if pos != tt.wantPos || found != tt.wantFound {
+ t.Errorf("BinarySearch got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound)
+ }
+ }
+
+ {
+ pos, found := BinarySearchFunc(tt.data, tt.target, strings.Compare)
+ if pos != tt.wantPos || found != tt.wantFound {
+ t.Errorf("BinarySearchFunc got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound)
+ }
+ }
+ })
+ }
+}
+
+func TestBinarySearchInts(t *testing.T) {
+ data := []int{20, 30, 40, 50, 60, 70, 80, 90}
+ tests := []struct {
+ target int
+ wantPos int
+ wantFound bool
+ }{
+ {20, 0, true},
+ {23, 1, false},
+ {43, 3, false},
+ {80, 6, true},
+ }
+ for _, tt := range tests {
+ t.Run(strconv.Itoa(tt.target), func(t *testing.T) {
+ {
+ pos, found := BinarySearch(data, tt.target)
+ if pos != tt.wantPos || found != tt.wantFound {
+ t.Errorf("BinarySearch got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound)
+ }
+ }
+
+ {
+ cmp := func(a, b int) int {
+ return a - b
+ }
+ pos, found := BinarySearchFunc(data, tt.target, cmp)
+ if pos != tt.wantPos || found != tt.wantFound {
+ t.Errorf("BinarySearchFunc got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound)
+ }
+ }
+ })
+ }
+}
+
+func TestBinarySearchFloats(t *testing.T) {
+ data := []float64{math.NaN(), -0.25, 0.0, 1.4}
+ tests := []struct {
+ target float64
+ wantPos int
+ wantFound bool
+ }{
+ {math.NaN(), 0, true},
+ {math.Inf(-1), 1, false},
+ {-0.25, 1, true},
+ {0.0, 2, true},
+ {1.4, 3, true},
+ {1.5, 4, false},
+ }
+ for _, tt := range tests {
+ t.Run(fmt.Sprintf("%v", tt.target), func(t *testing.T) {
+ {
+ pos, found := BinarySearch(data, tt.target)
+ if pos != tt.wantPos || found != tt.wantFound {
+ t.Errorf("BinarySearch got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound)
+ }
+ }
+ })
+ }
+}
+
+func TestBinarySearchFunc(t *testing.T) {
+ data := []int{1, 10, 11, 2} // sorted lexicographically
+ cmp := func(a int, b string) int {
+ return strings.Compare(strconv.Itoa(a), b)
+ }
+ pos, found := BinarySearchFunc(data, "2", cmp)
+ if pos != 3 || !found {
+ t.Errorf("BinarySearchFunc(%v, %q, cmp) = %v, %v, want %v, %v", data, "2", pos, found, 3, true)
+ }
+}
diff --git a/slices/zsortanyfunc.go b/slices/zsortanyfunc.go
new file mode 100644
index 0000000..06f2c7a
--- /dev/null
+++ b/slices/zsortanyfunc.go
@@ -0,0 +1,479 @@
+// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+// insertionSortCmpFunc sorts data[a:b] using insertion sort.
+func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
+ for i := a + 1; i < b; i++ {
+ for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- {
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+}
+
+// siftDownCmpFunc implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) {
+ root := lo
+ for {
+ child := 2*root + 1
+ if child >= hi {
+ break
+ }
+ if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) {
+ child++
+ }
+ if !(cmp(data[first+root], data[first+child]) < 0) {
+ return
+ }
+ data[first+root], data[first+child] = data[first+child], data[first+root]
+ root = child
+ }
+}
+
+func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
+ first := a
+ lo := 0
+ hi := b - a
+
+ // Build heap with greatest element at top.
+ for i := (hi - 1) / 2; i >= 0; i-- {
+ siftDownCmpFunc(data, i, hi, first, cmp)
+ }
+
+ // Pop elements, largest first, into end of data.
+ for i := hi - 1; i >= 0; i-- {
+ data[first], data[first+i] = data[first+i], data[first]
+ siftDownCmpFunc(data, lo, i, first, cmp)
+ }
+}
+
+// pdqsortCmpFunc sorts data[a:b].
+// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
+// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
+// C++ implementation: https://github.com/orlp/pdqsort
+// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
+// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
+func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) {
+ const maxInsertion = 12
+
+ var (
+ wasBalanced = true // whether the last partitioning was reasonably balanced
+ wasPartitioned = true // whether the slice was already partitioned
+ )
+
+ for {
+ length := b - a
+
+ if length <= maxInsertion {
+ insertionSortCmpFunc(data, a, b, cmp)
+ return
+ }
+
+ // Fall back to heapsort if too many bad choices were made.
+ if limit == 0 {
+ heapSortCmpFunc(data, a, b, cmp)
+ return
+ }
+
+ // If the last partitioning was imbalanced, we need to breaking patterns.
+ if !wasBalanced {
+ breakPatternsCmpFunc(data, a, b, cmp)
+ limit--
+ }
+
+ pivot, hint := choosePivotCmpFunc(data, a, b, cmp)
+ if hint == decreasingHint {
+ reverseRangeCmpFunc(data, a, b, cmp)
+ // The chosen pivot was pivot-a elements after the start of the array.
+ // After reversing it is pivot-a elements before the end of the array.
+ // The idea came from Rust's implementation.
+ pivot = (b - 1) - (pivot - a)
+ hint = increasingHint
+ }
+
+ // The slice is likely already sorted.
+ if wasBalanced && wasPartitioned && hint == increasingHint {
+ if partialInsertionSortCmpFunc(data, a, b, cmp) {
+ return
+ }
+ }
+
+ // Probably the slice contains many duplicate elements, partition the slice into
+ // elements equal to and elements greater than the pivot.
+ if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) {
+ mid := partitionEqualCmpFunc(data, a, b, pivot, cmp)
+ a = mid
+ continue
+ }
+
+ mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp)
+ wasPartitioned = alreadyPartitioned
+
+ leftLen, rightLen := mid-a, b-mid
+ balanceThreshold := length / 8
+ if leftLen < rightLen {
+ wasBalanced = leftLen >= balanceThreshold
+ pdqsortCmpFunc(data, a, mid, limit, cmp)
+ a = mid + 1
+ } else {
+ wasBalanced = rightLen >= balanceThreshold
+ pdqsortCmpFunc(data, mid+1, b, limit, cmp)
+ b = mid
+ }
+ }
+}
+
+// partitionCmpFunc does one quicksort partition.
+// Let p = data[pivot]
+// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
+// On return, data[newpivot] = p
+func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) {
+ data[a], data[pivot] = data[pivot], data[a]
+ i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
+
+ for i <= j && (cmp(data[i], data[a]) < 0) {
+ i++
+ }
+ for i <= j && !(cmp(data[j], data[a]) < 0) {
+ j--
+ }
+ if i > j {
+ data[j], data[a] = data[a], data[j]
+ return j, true
+ }
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+
+ for {
+ for i <= j && (cmp(data[i], data[a]) < 0) {
+ i++
+ }
+ for i <= j && !(cmp(data[j], data[a]) < 0) {
+ j--
+ }
+ if i > j {
+ break
+ }
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+ }
+ data[j], data[a] = data[a], data[j]
+ return j, false
+}
+
+// partitionEqualCmpFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
+// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
+func partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) {
+ data[a], data[pivot] = data[pivot], data[a]
+ i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
+
+ for {
+ for i <= j && !(cmp(data[a], data[i]) < 0) {
+ i++
+ }
+ for i <= j && (cmp(data[a], data[j]) < 0) {
+ j--
+ }
+ if i > j {
+ break
+ }
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+ }
+ return i
+}
+
+// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end.
+func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) bool {
+ const (
+ maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
+ shortestShifting = 50 // don't shift any elements on short arrays
+ )
+ i := a + 1
+ for j := 0; j < maxSteps; j++ {
+ for i < b && !(cmp(data[i], data[i-1]) < 0) {
+ i++
+ }
+
+ if i == b {
+ return true
+ }
+
+ if b-a < shortestShifting {
+ return false
+ }
+
+ data[i], data[i-1] = data[i-1], data[i]
+
+ // Shift the smaller one to the left.
+ if i-a >= 2 {
+ for j := i - 1; j >= 1; j-- {
+ if !(cmp(data[j], data[j-1]) < 0) {
+ break
+ }
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+ // Shift the greater one to the right.
+ if b-i >= 2 {
+ for j := i + 1; j < b; j++ {
+ if !(cmp(data[j], data[j-1]) < 0) {
+ break
+ }
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+ }
+ return false
+}
+
+// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns
+// that might cause imbalanced partitions in quicksort.
+func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
+ length := b - a
+ if length >= 8 {
+ random := xorshift(length)
+ modulus := nextPowerOfTwo(length)
+
+ for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
+ other := int(uint(random.Next()) & (modulus - 1))
+ if other >= length {
+ other -= length
+ }
+ data[idx], data[a+other] = data[a+other], data[idx]
+ }
+ }
+}
+
+// choosePivotCmpFunc chooses a pivot in data[a:b].
+//
+// [0,8): chooses a static pivot.
+// [8,shortestNinther): uses the simple median-of-three method.
+// [shortestNinther,∞): uses the Tukey ninther method.
+func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) {
+ const (
+ shortestNinther = 50
+ maxSwaps = 4 * 3
+ )
+
+ l := b - a
+
+ var (
+ swaps int
+ i = a + l/4*1
+ j = a + l/4*2
+ k = a + l/4*3
+ )
+
+ if l >= 8 {
+ if l >= shortestNinther {
+ // Tukey ninther method, the idea came from Rust's implementation.
+ i = medianAdjacentCmpFunc(data, i, &swaps, cmp)
+ j = medianAdjacentCmpFunc(data, j, &swaps, cmp)
+ k = medianAdjacentCmpFunc(data, k, &swaps, cmp)
+ }
+ // Find the median among i, j, k and stores it into j.
+ j = medianCmpFunc(data, i, j, k, &swaps, cmp)
+ }
+
+ switch swaps {
+ case 0:
+ return j, increasingHint
+ case maxSwaps:
+ return j, decreasingHint
+ default:
+ return j, unknownHint
+ }
+}
+
+// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
+func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) {
+ if cmp(data[b], data[a]) < 0 {
+ *swaps++
+ return b, a
+ }
+ return a, b
+}
+
+// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
+func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int {
+ a, b = order2CmpFunc(data, a, b, swaps, cmp)
+ b, c = order2CmpFunc(data, b, c, swaps, cmp)
+ a, b = order2CmpFunc(data, a, b, swaps, cmp)
+ return b
+}
+
+// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
+func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int {
+ return medianCmpFunc(data, a-1, a, a+1, swaps, cmp)
+}
+
+func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
+ i := a
+ j := b - 1
+ for i < j {
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+ }
+}
+
+func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) {
+ for i := 0; i < n; i++ {
+ data[a+i], data[b+i] = data[b+i], data[a+i]
+ }
+}
+
+func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) {
+ blockSize := 20 // must be > 0
+ a, b := 0, blockSize
+ for b <= n {
+ insertionSortCmpFunc(data, a, b, cmp)
+ a = b
+ b += blockSize
+ }
+ insertionSortCmpFunc(data, a, n, cmp)
+
+ for blockSize < n {
+ a, b = 0, 2*blockSize
+ for b <= n {
+ symMergeCmpFunc(data, a, a+blockSize, b, cmp)
+ a = b
+ b += 2 * blockSize
+ }
+ if m := a + blockSize; m < n {
+ symMergeCmpFunc(data, a, m, n, cmp)
+ }
+ blockSize *= 2
+ }
+}
+
+// symMergeCmpFunc merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[a] into data[m:b]
+ // if data[a:m] only contains one element.
+ if m-a == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] >= data[a] for m <= i < b.
+ // Exit the search loop with i == b in case no such index exists.
+ i := m
+ j := b
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if cmp(data[h], data[a]) < 0 {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[a] reaches the position before i.
+ for k := a; k < i-1; k++ {
+ data[k], data[k+1] = data[k+1], data[k]
+ }
+ return
+ }
+
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[m] into data[a:m]
+ // if data[m:b] only contains one element.
+ if b-m == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] > data[m] for a <= i < m.
+ // Exit the search loop with i == m in case no such index exists.
+ i := a
+ j := m
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if !(cmp(data[m], data[h]) < 0) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[m] reaches the position i.
+ for k := m; k > i; k-- {
+ data[k], data[k-1] = data[k-1], data[k]
+ }
+ return
+ }
+
+ mid := int(uint(a+b) >> 1)
+ n := mid + m
+ var start, r int
+ if m > mid {
+ start = n - b
+ r = mid
+ } else {
+ start = a
+ r = m
+ }
+ p := n - 1
+
+ for start < r {
+ c := int(uint(start+r) >> 1)
+ if !(cmp(data[p-c], data[c]) < 0) {
+ start = c + 1
+ } else {
+ r = c
+ }
+ }
+
+ end := n - start
+ if start < m && m < end {
+ rotateCmpFunc(data, start, m, end, cmp)
+ }
+ if a < start && start < mid {
+ symMergeCmpFunc(data, a, start, mid, cmp)
+ }
+ if mid < end && end < b {
+ symMergeCmpFunc(data, mid, end, b, cmp)
+ }
+}
+
+// rotateCmpFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
+ i := m - a
+ j := b - m
+
+ for i != j {
+ if i > j {
+ swapRangeCmpFunc(data, m-i, m, j, cmp)
+ i -= j
+ } else {
+ swapRangeCmpFunc(data, m-i, m+j-i, i, cmp)
+ j -= i
+ }
+ }
+ // i == j
+ swapRangeCmpFunc(data, m-i, m, i, cmp)
+}
diff --git a/slices/zsortordered.go b/slices/zsortordered.go
new file mode 100644
index 0000000..99b47c3
--- /dev/null
+++ b/slices/zsortordered.go
@@ -0,0 +1,481 @@
+// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slices
+
+import "golang.org/x/exp/constraints"
+
+// insertionSortOrdered sorts data[a:b] using insertion sort.
+func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) {
+ for i := a + 1; i < b; i++ {
+ for j := i; j > a && cmpLess(data[j], data[j-1]); j-- {
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+}
+
+// siftDownOrdered implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) {
+ root := lo
+ for {
+ child := 2*root + 1
+ if child >= hi {
+ break
+ }
+ if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) {
+ child++
+ }
+ if !cmpLess(data[first+root], data[first+child]) {
+ return
+ }
+ data[first+root], data[first+child] = data[first+child], data[first+root]
+ root = child
+ }
+}
+
+func heapSortOrdered[E constraints.Ordered](data []E, a, b int) {
+ first := a
+ lo := 0
+ hi := b - a
+
+ // Build heap with greatest element at top.
+ for i := (hi - 1) / 2; i >= 0; i-- {
+ siftDownOrdered(data, i, hi, first)
+ }
+
+ // Pop elements, largest first, into end of data.
+ for i := hi - 1; i >= 0; i-- {
+ data[first], data[first+i] = data[first+i], data[first]
+ siftDownOrdered(data, lo, i, first)
+ }
+}
+
+// pdqsortOrdered sorts data[a:b].
+// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
+// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
+// C++ implementation: https://github.com/orlp/pdqsort
+// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
+// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
+func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) {
+ const maxInsertion = 12
+
+ var (
+ wasBalanced = true // whether the last partitioning was reasonably balanced
+ wasPartitioned = true // whether the slice was already partitioned
+ )
+
+ for {
+ length := b - a
+
+ if length <= maxInsertion {
+ insertionSortOrdered(data, a, b)
+ return
+ }
+
+ // Fall back to heapsort if too many bad choices were made.
+ if limit == 0 {
+ heapSortOrdered(data, a, b)
+ return
+ }
+
+ // If the last partitioning was imbalanced, we need to breaking patterns.
+ if !wasBalanced {
+ breakPatternsOrdered(data, a, b)
+ limit--
+ }
+
+ pivot, hint := choosePivotOrdered(data, a, b)
+ if hint == decreasingHint {
+ reverseRangeOrdered(data, a, b)
+ // The chosen pivot was pivot-a elements after the start of the array.
+ // After reversing it is pivot-a elements before the end of the array.
+ // The idea came from Rust's implementation.
+ pivot = (b - 1) - (pivot - a)
+ hint = increasingHint
+ }
+
+ // The slice is likely already sorted.
+ if wasBalanced && wasPartitioned && hint == increasingHint {
+ if partialInsertionSortOrdered(data, a, b) {
+ return
+ }
+ }
+
+ // Probably the slice contains many duplicate elements, partition the slice into
+ // elements equal to and elements greater than the pivot.
+ if a > 0 && !cmpLess(data[a-1], data[pivot]) {
+ mid := partitionEqualOrdered(data, a, b, pivot)
+ a = mid
+ continue
+ }
+
+ mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot)
+ wasPartitioned = alreadyPartitioned
+
+ leftLen, rightLen := mid-a, b-mid
+ balanceThreshold := length / 8
+ if leftLen < rightLen {
+ wasBalanced = leftLen >= balanceThreshold
+ pdqsortOrdered(data, a, mid, limit)
+ a = mid + 1
+ } else {
+ wasBalanced = rightLen >= balanceThreshold
+ pdqsortOrdered(data, mid+1, b, limit)
+ b = mid
+ }
+ }
+}
+
+// partitionOrdered does one quicksort partition.
+// Let p = data[pivot]
+// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
+// On return, data[newpivot] = p
+func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) {
+ data[a], data[pivot] = data[pivot], data[a]
+ i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
+
+ for i <= j && cmpLess(data[i], data[a]) {
+ i++
+ }
+ for i <= j && !cmpLess(data[j], data[a]) {
+ j--
+ }
+ if i > j {
+ data[j], data[a] = data[a], data[j]
+ return j, true
+ }
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+
+ for {
+ for i <= j && cmpLess(data[i], data[a]) {
+ i++
+ }
+ for i <= j && !cmpLess(data[j], data[a]) {
+ j--
+ }
+ if i > j {
+ break
+ }
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+ }
+ data[j], data[a] = data[a], data[j]
+ return j, false
+}
+
+// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
+// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
+func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) {
+ data[a], data[pivot] = data[pivot], data[a]
+ i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
+
+ for {
+ for i <= j && !cmpLess(data[a], data[i]) {
+ i++
+ }
+ for i <= j && cmpLess(data[a], data[j]) {
+ j--
+ }
+ if i > j {
+ break
+ }
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+ }
+ return i
+}
+
+// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end.
+func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool {
+ const (
+ maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
+ shortestShifting = 50 // don't shift any elements on short arrays
+ )
+ i := a + 1
+ for j := 0; j < maxSteps; j++ {
+ for i < b && !cmpLess(data[i], data[i-1]) {
+ i++
+ }
+
+ if i == b {
+ return true
+ }
+
+ if b-a < shortestShifting {
+ return false
+ }
+
+ data[i], data[i-1] = data[i-1], data[i]
+
+ // Shift the smaller one to the left.
+ if i-a >= 2 {
+ for j := i - 1; j >= 1; j-- {
+ if !cmpLess(data[j], data[j-1]) {
+ break
+ }
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+ // Shift the greater one to the right.
+ if b-i >= 2 {
+ for j := i + 1; j < b; j++ {
+ if !cmpLess(data[j], data[j-1]) {
+ break
+ }
+ data[j], data[j-1] = data[j-1], data[j]
+ }
+ }
+ }
+ return false
+}
+
+// breakPatternsOrdered scatters some elements around in an attempt to break some patterns
+// that might cause imbalanced partitions in quicksort.
+func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) {
+ length := b - a
+ if length >= 8 {
+ random := xorshift(length)
+ modulus := nextPowerOfTwo(length)
+
+ for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
+ other := int(uint(random.Next()) & (modulus - 1))
+ if other >= length {
+ other -= length
+ }
+ data[idx], data[a+other] = data[a+other], data[idx]
+ }
+ }
+}
+
+// choosePivotOrdered chooses a pivot in data[a:b].
+//
+// [0,8): chooses a static pivot.
+// [8,shortestNinther): uses the simple median-of-three method.
+// [shortestNinther,∞): uses the Tukey ninther method.
+func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) {
+ const (
+ shortestNinther = 50
+ maxSwaps = 4 * 3
+ )
+
+ l := b - a
+
+ var (
+ swaps int
+ i = a + l/4*1
+ j = a + l/4*2
+ k = a + l/4*3
+ )
+
+ if l >= 8 {
+ if l >= shortestNinther {
+ // Tukey ninther method, the idea came from Rust's implementation.
+ i = medianAdjacentOrdered(data, i, &swaps)
+ j = medianAdjacentOrdered(data, j, &swaps)
+ k = medianAdjacentOrdered(data, k, &swaps)
+ }
+ // Find the median among i, j, k and stores it into j.
+ j = medianOrdered(data, i, j, k, &swaps)
+ }
+
+ switch swaps {
+ case 0:
+ return j, increasingHint
+ case maxSwaps:
+ return j, decreasingHint
+ default:
+ return j, unknownHint
+ }
+}
+
+// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
+func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) {
+ if cmpLess(data[b], data[a]) {
+ *swaps++
+ return b, a
+ }
+ return a, b
+}
+
+// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
+func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int {
+ a, b = order2Ordered(data, a, b, swaps)
+ b, c = order2Ordered(data, b, c, swaps)
+ a, b = order2Ordered(data, a, b, swaps)
+ return b
+}
+
+// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
+func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int {
+ return medianOrdered(data, a-1, a, a+1, swaps)
+}
+
+func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) {
+ i := a
+ j := b - 1
+ for i < j {
+ data[i], data[j] = data[j], data[i]
+ i++
+ j--
+ }
+}
+
+func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) {
+ for i := 0; i < n; i++ {
+ data[a+i], data[b+i] = data[b+i], data[a+i]
+ }
+}
+
+func stableOrdered[E constraints.Ordered](data []E, n int) {
+ blockSize := 20 // must be > 0
+ a, b := 0, blockSize
+ for b <= n {
+ insertionSortOrdered(data, a, b)
+ a = b
+ b += blockSize
+ }
+ insertionSortOrdered(data, a, n)
+
+ for blockSize < n {
+ a, b = 0, 2*blockSize
+ for b <= n {
+ symMergeOrdered(data, a, a+blockSize, b)
+ a = b
+ b += 2 * blockSize
+ }
+ if m := a + blockSize; m < n {
+ symMergeOrdered(data, a, m, n)
+ }
+ blockSize *= 2
+ }
+}
+
+// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) {
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[a] into data[m:b]
+ // if data[a:m] only contains one element.
+ if m-a == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] >= data[a] for m <= i < b.
+ // Exit the search loop with i == b in case no such index exists.
+ i := m
+ j := b
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if cmpLess(data[h], data[a]) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[a] reaches the position before i.
+ for k := a; k < i-1; k++ {
+ data[k], data[k+1] = data[k+1], data[k]
+ }
+ return
+ }
+
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[m] into data[a:m]
+ // if data[m:b] only contains one element.
+ if b-m == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] > data[m] for a <= i < m.
+ // Exit the search loop with i == m in case no such index exists.
+ i := a
+ j := m
+ for i < j {
+ h := int(uint(i+j) >> 1)
+ if !cmpLess(data[m], data[h]) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[m] reaches the position i.
+ for k := m; k > i; k-- {
+ data[k], data[k-1] = data[k-1], data[k]
+ }
+ return
+ }
+
+ mid := int(uint(a+b) >> 1)
+ n := mid + m
+ var start, r int
+ if m > mid {
+ start = n - b
+ r = mid
+ } else {
+ start = a
+ r = m
+ }
+ p := n - 1
+
+ for start < r {
+ c := int(uint(start+r) >> 1)
+ if !cmpLess(data[p-c], data[c]) {
+ start = c + 1
+ } else {
+ r = c
+ }
+ }
+
+ end := n - start
+ if start < m && m < end {
+ rotateOrdered(data, start, m, end)
+ }
+ if a < start && start < mid {
+ symMergeOrdered(data, a, start, mid)
+ }
+ if mid < end && end < b {
+ symMergeOrdered(data, mid, end, b)
+ }
+}
+
+// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) {
+ i := m - a
+ j := b - m
+
+ for i != j {
+ if i > j {
+ swapRangeOrdered(data, m-i, m, j)
+ i -= j
+ } else {
+ swapRangeOrdered(data, m-i, m+j-i, i)
+ j -= i
+ }
+ }
+ // i == j
+ swapRangeOrdered(data, m-i, m, i)
+}
diff --git a/slog/attr.go b/slog/attr.go
new file mode 100644
index 0000000..a180d0e
--- /dev/null
+++ b/slog/attr.go
@@ -0,0 +1,102 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "fmt"
+ "time"
+)
+
+// An Attr is a key-value pair.
+type Attr struct {
+ Key string
+ Value Value
+}
+
+// String returns an Attr for a string value.
+func String(key, value string) Attr {
+ return Attr{key, StringValue(value)}
+}
+
+// Int64 returns an Attr for an int64.
+func Int64(key string, value int64) Attr {
+ return Attr{key, Int64Value(value)}
+}
+
+// Int converts an int to an int64 and returns
+// an Attr with that value.
+func Int(key string, value int) Attr {
+ return Int64(key, int64(value))
+}
+
+// Uint64 returns an Attr for a uint64.
+func Uint64(key string, v uint64) Attr {
+ return Attr{key, Uint64Value(v)}
+}
+
+// Float64 returns an Attr for a floating-point number.
+func Float64(key string, v float64) Attr {
+ return Attr{key, Float64Value(v)}
+}
+
+// Bool returns an Attr for a bool.
+func Bool(key string, v bool) Attr {
+ return Attr{key, BoolValue(v)}
+}
+
+// Time returns an Attr for a time.Time.
+// It discards the monotonic portion.
+func Time(key string, v time.Time) Attr {
+ return Attr{key, TimeValue(v)}
+}
+
+// Duration returns an Attr for a time.Duration.
+func Duration(key string, v time.Duration) Attr {
+ return Attr{key, DurationValue(v)}
+}
+
+// Group returns an Attr for a Group Value.
+// The first argument is the key; the remaining arguments
+// are converted to Attrs as in [Logger.Log].
+//
+// Use Group to collect several key-value pairs under a single
+// key on a log line, or as the result of LogValue
+// in order to log a single value as multiple Attrs.
+func Group(key string, args ...any) Attr {
+ return Attr{key, GroupValue(argsToAttrSlice(args)...)}
+}
+
+func argsToAttrSlice(args []any) []Attr {
+ var (
+ attr Attr
+ attrs []Attr
+ )
+ for len(args) > 0 {
+ attr, args = argsToAttr(args)
+ attrs = append(attrs, attr)
+ }
+ return attrs
+}
+
+// Any returns an Attr for the supplied value.
+// See [Value.AnyValue] for how values are treated.
+func Any(key string, value any) Attr {
+ return Attr{key, AnyValue(value)}
+}
+
+// Equal reports whether a and b have equal keys and values.
+func (a Attr) Equal(b Attr) bool {
+ return a.Key == b.Key && a.Value.Equal(b.Value)
+}
+
+func (a Attr) String() string {
+ return fmt.Sprintf("%s=%s", a.Key, a.Value)
+}
+
+// isEmpty reports whether a has an empty key and a nil value.
+// That can be written as Attr{} or Any("", nil).
+func (a Attr) isEmpty() bool {
+ return a.Key == "" && a.Value.num == 0 && a.Value.any == nil
+}
diff --git a/slog/attr_test.go b/slog/attr_test.go
new file mode 100644
index 0000000..6b1f19a
--- /dev/null
+++ b/slog/attr_test.go
@@ -0,0 +1,41 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "testing"
+ "time"
+)
+
+func TestAttrNoAlloc(t *testing.T) {
+ // Assign values just to make sure the compiler doesn't optimize away the statements.
+ var (
+ i int64
+ u uint64
+ f float64
+ b bool
+ s string
+ x any
+ p = &i
+ d time.Duration
+ )
+ a := int(testing.AllocsPerRun(5, func() {
+ i = Int64("key", 1).Value.Int64()
+ u = Uint64("key", 1).Value.Uint64()
+ f = Float64("key", 1).Value.Float64()
+ b = Bool("key", true).Value.Bool()
+ s = String("key", "foo").Value.String()
+ d = Duration("key", d).Value.Duration()
+ x = Any("key", p).Value.Any()
+ }))
+ if a != 0 {
+ t.Errorf("got %d allocs, want zero", a)
+ }
+ _ = u
+ _ = f
+ _ = b
+ _ = s
+ _ = x
+}
diff --git a/slog/benchmarks/Makefile b/slog/benchmarks/Makefile
new file mode 100644
index 0000000..60f5896
--- /dev/null
+++ b/slog/benchmarks/Makefile
@@ -0,0 +1,25 @@
+# Run and compare benchmarks.
+# This requires a version of benchstat that supports
+# the -ignore flag. The flag was added on or before 13 January 2023,
+# so a compatible version can be obtained by running
+# go install golang.org/x/perf/cmd/benchstat@latest
+
+count = 10
+
+default: compare-zap compare-zerolog
+
+compare-%: %_benchmarks/out.bench slog.bench
+ benchstat -ignore pkg $^
+
+slog.bench: *.go ../*.go ../../go.mod
+ go test -run NONE -bench . -count $(count) > $@
+
+slog-nopc.bench: *.go ../*.go ../../go.mod
+ go test -nopc -run NONE -bench . -count $(count) > $@
+
+%_benchmarks/out.bench: %_benchmarks/*.go %_benchmarks/go.mod
+ go test -C $*_benchmarks -bench . -count $(count) > $@
+
+# Don't delete the out.bench files after a comparison.
+.PRECIOUS: %_benchmarks/out.bench
+
diff --git a/slog/benchmarks/benchmarks.go b/slog/benchmarks/benchmarks.go
new file mode 100644
index 0000000..498bda6
--- /dev/null
+++ b/slog/benchmarks/benchmarks.go
@@ -0,0 +1,60 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package benchmarks contains benchmarks for slog.
+//
+// These benchmarks are loosely based on github.com/uber-go/zap/benchmarks.
+// They have the following desirable properties:
+//
+// - They test a complete log event, from the user's call to its return.
+//
+// - The benchmarked code is run concurrently in multiple goroutines, to
+// better simulate a real server (the most common environment for structured
+// logs).
+//
+// - Some handlers are optimistic versions of real handlers, doing real-world
+// tasks as fast as possible (and sometimes faster, in that an
+// implementation may not be concurrency-safe). This gives us a lower bound
+// on handler performance, so we can evaluate the (handler-independent) core
+// activity of the package in an end-to-end context without concern that a
+// slow handler implementation is skewing the results.
+//
+// - We also test the built-in handlers, for comparison.
+//
+// As of Go 1.20, fetching the pc for a single nearby frame is slow. We hope to
+// improve its speed before this package is released. Run the benchmarks with
+//
+// -tags nopc
+//
+// to remove this cost.
+package benchmarks
+
+import (
+ "errors"
+ "time"
+
+ "golang.org/x/exp/slog"
+)
+
+// The symbols in this file are exported so that the Zap benchmarks can use them.
+
+const TestMessage = "Test logging, but use a somewhat realistic message length."
+
+var (
+ TestTime = time.Date(2022, time.May, 1, 0, 0, 0, 0, time.UTC)
+ TestString = "7e3b3b2aaeff56a7108fe11e154200dd/7819479873059528190"
+ TestInt = 32768
+ TestDuration = 23 * time.Second
+ TestError = errors.New("fail")
+)
+
+var TestAttrs = []slog.Attr{
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+}
+
+const WantText = "time=1651363200 level=0 msg=Test logging, but use a somewhat realistic message length. string=7e3b3b2aaeff56a7108fe11e154200dd/7819479873059528190 status=32768 duration=23000000000 time=1651363200 error=fail\n"
diff --git a/slog/benchmarks/benchmarks_test.go b/slog/benchmarks/benchmarks_test.go
new file mode 100644
index 0000000..2e44707
--- /dev/null
+++ b/slog/benchmarks/benchmarks_test.go
@@ -0,0 +1,147 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package benchmarks
+
+import (
+ "context"
+ "flag"
+ "io"
+ "testing"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/internal"
+)
+
+func init() {
+ flag.BoolVar(&internal.IgnorePC, "nopc", false, "do not invoke runtime.Callers")
+}
+
+// We pass Attrs (or zap.Fields) inline because it affects allocations: building
+// up a list outside of the benchmarked code and passing it in with "..."
+// reduces measured allocations.
+
+func BenchmarkAttrs(b *testing.B) {
+ ctx := context.Background()
+ for _, handler := range []struct {
+ name string
+ h slog.Handler
+ }{
+ {"disabled", disabledHandler{}},
+ {"async discard", newAsyncHandler()},
+ {"fastText discard", newFastTextHandler(io.Discard)},
+ {"Text discard", slog.NewTextHandler(io.Discard, nil)},
+ {"JSON discard", slog.NewJSONHandler(io.Discard, nil)},
+ } {
+ logger := slog.New(handler.h)
+ b.Run(handler.name, func(b *testing.B) {
+ for _, call := range []struct {
+ name string
+ f func()
+ }{
+ {
+ // The number should match nAttrsInline in slog/record.go.
+ // This should exercise the code path where no allocations
+ // happen in Record or Attr. If there are allocations, they
+ // should only be from Duration.String and Time.String.
+ "5 args",
+ func() {
+ logger.LogAttrs(nil, slog.LevelInfo, TestMessage,
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ )
+ },
+ },
+ {
+ "5 args ctx",
+ func() {
+ logger.LogAttrs(ctx, slog.LevelInfo, TestMessage,
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ )
+ },
+ },
+ {
+ "10 args",
+ func() {
+ logger.LogAttrs(nil, slog.LevelInfo, TestMessage,
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ )
+ },
+ },
+ {
+ "40 args",
+ func() {
+ logger.LogAttrs(nil, slog.LevelInfo, TestMessage,
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ slog.String("string", TestString),
+ slog.Int("status", TestInt),
+ slog.Duration("duration", TestDuration),
+ slog.Time("time", TestTime),
+ slog.Any("error", TestError),
+ )
+ },
+ },
+ } {
+ b.Run(call.name, func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ call.f()
+ }
+ })
+ })
+ }
+ })
+ }
+}
diff --git a/slog/benchmarks/handlers.go b/slog/benchmarks/handlers.go
new file mode 100644
index 0000000..90c7aed
--- /dev/null
+++ b/slog/benchmarks/handlers.go
@@ -0,0 +1,147 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package benchmarks
+
+// Handlers for benchmarking.
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "strconv"
+ "time"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/internal/buffer"
+)
+
+// A fastTextHandler writes a Record to an io.Writer in a format similar to
+// slog.TextHandler, but without quoting or locking. It has a few other
+// performance-motivated shortcuts, like writing times as seconds since the
+// epoch instead of strings.
+//
+// It is intended to represent a high-performance Handler that synchronously
+// writes text (as opposed to binary).
+type fastTextHandler struct {
+ w io.Writer
+}
+
+func newFastTextHandler(w io.Writer) slog.Handler {
+ return &fastTextHandler{w: w}
+}
+
+func (h *fastTextHandler) Enabled(context.Context, slog.Level) bool { return true }
+
+func (h *fastTextHandler) Handle(_ context.Context, r slog.Record) error {
+ buf := buffer.New()
+ defer buf.Free()
+
+ if !r.Time.IsZero() {
+ buf.WriteString("time=")
+ h.appendTime(buf, r.Time)
+ buf.WriteByte(' ')
+ }
+ buf.WriteString("level=")
+ *buf = strconv.AppendInt(*buf, int64(r.Level), 10)
+ buf.WriteByte(' ')
+ buf.WriteString("msg=")
+ buf.WriteString(r.Message)
+ r.Attrs(func(a slog.Attr) bool {
+ buf.WriteByte(' ')
+ buf.WriteString(a.Key)
+ buf.WriteByte('=')
+ h.appendValue(buf, a.Value)
+ return true
+ })
+ buf.WriteByte('\n')
+ _, err := h.w.Write(*buf)
+ return err
+}
+
+func (h *fastTextHandler) appendValue(buf *buffer.Buffer, v slog.Value) {
+ switch v.Kind() {
+ case slog.KindString:
+ buf.WriteString(v.String())
+ case slog.KindInt64:
+ *buf = strconv.AppendInt(*buf, v.Int64(), 10)
+ case slog.KindUint64:
+ *buf = strconv.AppendUint(*buf, v.Uint64(), 10)
+ case slog.KindFloat64:
+ *buf = strconv.AppendFloat(*buf, v.Float64(), 'g', -1, 64)
+ case slog.KindBool:
+ *buf = strconv.AppendBool(*buf, v.Bool())
+ case slog.KindDuration:
+ *buf = strconv.AppendInt(*buf, v.Duration().Nanoseconds(), 10)
+ case slog.KindTime:
+ h.appendTime(buf, v.Time())
+ case slog.KindAny:
+ a := v.Any()
+ switch a := a.(type) {
+ case error:
+ buf.WriteString(a.Error())
+ default:
+ buf.WriteString(fmt.Sprint(a))
+ }
+ default:
+ panic(fmt.Sprintf("bad kind: %s", v.Kind()))
+ }
+}
+
+func (h *fastTextHandler) appendTime(buf *buffer.Buffer, t time.Time) {
+ *buf = strconv.AppendInt(*buf, t.Unix(), 10)
+}
+
+func (h *fastTextHandler) WithAttrs([]slog.Attr) slog.Handler {
+ panic("fastTextHandler: With unimplemented")
+}
+
+func (*fastTextHandler) WithGroup(string) slog.Handler {
+ panic("fastTextHandler: WithGroup unimplemented")
+}
+
+// An asyncHandler simulates a Handler that passes Records to a
+// background goroutine for processing.
+// Because sending to a channel can be expensive due to locking,
+// we simulate a lock-free queue by adding the Record to a ring buffer.
+// Omitting the locking makes this little more than a copy of the Record,
+// but that is a worthwhile thing to measure because Records are on the large
+// side.
+type asyncHandler struct {
+ ringBuffer [100]slog.Record
+ next int
+}
+
+func newAsyncHandler() *asyncHandler {
+ return &asyncHandler{}
+}
+
+func (*asyncHandler) Enabled(context.Context, slog.Level) bool { return true }
+
+func (h *asyncHandler) Handle(_ context.Context, r slog.Record) error {
+ h.ringBuffer[h.next] = r.Clone()
+ h.next = (h.next + 1) % len(h.ringBuffer)
+ return nil
+}
+
+func (*asyncHandler) WithAttrs([]slog.Attr) slog.Handler {
+ panic("asyncHandler: With unimplemented")
+}
+
+func (*asyncHandler) WithGroup(string) slog.Handler {
+ panic("asyncHandler: WithGroup unimplemented")
+}
+
+type disabledHandler struct{}
+
+func (disabledHandler) Enabled(context.Context, slog.Level) bool { return false }
+func (disabledHandler) Handle(context.Context, slog.Record) error { panic("should not be called") }
+
+func (disabledHandler) WithAttrs([]slog.Attr) slog.Handler {
+ panic("disabledHandler: With unimplemented")
+}
+
+func (disabledHandler) WithGroup(string) slog.Handler {
+ panic("disabledHandler: WithGroup unimplemented")
+}
diff --git a/slog/benchmarks/handlers_test.go b/slog/benchmarks/handlers_test.go
new file mode 100644
index 0000000..9847f67
--- /dev/null
+++ b/slog/benchmarks/handlers_test.go
@@ -0,0 +1,43 @@
+package benchmarks
+
+import (
+ "bytes"
+ "context"
+ "testing"
+
+ "golang.org/x/exp/slices"
+ "golang.org/x/exp/slog"
+)
+
+func TestHandlers(t *testing.T) {
+ ctx := context.Background()
+ r := slog.NewRecord(TestTime, slog.LevelInfo, TestMessage, 0)
+ r.AddAttrs(TestAttrs...)
+ t.Run("text", func(t *testing.T) {
+ var b bytes.Buffer
+ h := newFastTextHandler(&b)
+ if err := h.Handle(ctx, r); err != nil {
+ t.Fatal(err)
+ }
+ got := b.String()
+ if got != WantText {
+ t.Errorf("\ngot %q\nwant %q", got, WantText)
+ }
+ })
+ t.Run("async", func(t *testing.T) {
+ h := newAsyncHandler()
+ if err := h.Handle(ctx, r); err != nil {
+ t.Fatal(err)
+ }
+ got := h.ringBuffer[0]
+ if !got.Time.Equal(r.Time) || !slices.EqualFunc(attrSlice(got), attrSlice(r), slog.Attr.Equal) {
+ t.Errorf("got %+v, want %+v", got, r)
+ }
+ })
+}
+
+func attrSlice(r slog.Record) []slog.Attr {
+ var as []slog.Attr
+ r.Attrs(func(a slog.Attr) bool { as = append(as, a); return true })
+ return as
+}
diff --git a/slog/benchmarks/slog-nopc.bench b/slog/benchmarks/slog-nopc.bench
new file mode 100644
index 0000000..7c478d3
--- /dev/null
+++ b/slog/benchmarks/slog-nopc.bench
@@ -0,0 +1,206 @@
+goos: linux
+goarch: amd64
+pkg: golang.org/x/exp/slog/benchmarks
+cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
+BenchmarkAttrs/disabled/5_args-8 96666853 11.50 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 93822624 11.52 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 93498656 11.50 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.34 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.27 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.55 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 98321294 11.44 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.19 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 97612837 11.17 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 91372897 11.47 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 61208725 19.13 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 63628257 19.69 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 57953264 18.73 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 62812270 19.46 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 57251260 19.27 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 61932922 20.04 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 61243114 19.21 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 59434218 19.90 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 64204924 19.21 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 63116061 18.77 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 56439350 20.38 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 57480078 20.69 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 59042649 20.47 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 56442658 20.72 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 55750566 20.49 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 57809546 20.54 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 55997990 21.12 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 53568574 21.60 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 49113771 23.03 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 52396518 22.46 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 15550318 73.67 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 15800660 78.36 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 14491339 76.37 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 15809065 73.16 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16208973 74.71 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16328282 75.02 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16293915 73.25 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16769311 72.24 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16190818 77.24 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 15298545 75.12 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 12126757 113.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 11220534 112.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 10304506 97.45 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 12205324 96.65 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 11156934 104.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 11478124 103.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 11881509 104.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 11149441 97.91 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 11795527 94.65 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 12327957 100.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 10814191 114.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 10285345 108.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 10800776 99.18 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 11485023 104.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 11583052 100.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 10625286 102.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 12110364 102.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 10148967 104.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 11966101 102.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 11707686 103.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3464420 360.6 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3190548 372.2 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2996740 394.5 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3137779 349.4 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3399565 342.7 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3154626 384.1 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3780799 328.8 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3136921 359.5 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3452958 389.5 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 3486763 346.6 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 1000000 1417 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 716872 1448 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 961792 1290 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 913196 1313 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 814600 1367 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 849133 1373 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 792927 1268 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 944186 1257 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 1000000 1266 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 1000000 1313 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 7067176 171.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 6854912 189.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 6477049 194.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 5754541 231.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 5994988 187.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 6226411 199.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 7055413 182.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 6862999 171.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 7167945 189.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 5665896 223.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 6328725 220.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 5980275 202.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 5859608 212.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 6417674 212.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 5264354 248.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 6446439 225.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 5482052 198.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 5419814 223.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 6455188 192.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 6946065 184.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2318042 555.9 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2207629 567.4 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2096774 571.4 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2109498 551.6 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2056164 553.2 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2261677 531.6 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2085157 589.5 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2193956 553.1 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1962531 534.2 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 2347599 758.9 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 361731 3279 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 376836 2673 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 478945 2499 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 576418 2670 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 535083 2430 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 506319 2501 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 439126 2566 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 446274 2451 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 544543 2259 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 540310 2312 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1475912 832.4 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1337040 850.3 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1357222 803.1 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1310484 826.5 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1453827 792.9 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1505486 875.6 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1412893 833.8 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1501142 770.5 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1541641 840.8 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1460121 813.6 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1433220 847.9 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1425093 785.0 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1367612 870.2 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1348792 911.9 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1470273 847.4 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1441452 879.6 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1345639 966.5 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1416301 840.1 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1483112 834.0 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1443690 850.7 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 793375 1550 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 883533 1543 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 940844 1570 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 773037 1393 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 982622 1416 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 949856 1328 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 878445 1453 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 871341 1428 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 724788 1596 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 985714 1472 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 236900 4795 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 317960 4955 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 250573 4851 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 257638 5089 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 261318 4671 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 254628 4732 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 239418 5454 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 272034 4705 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 271911 4808 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 248612 4969 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1992364 640.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1609268 767.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1924783 633.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1686579 658.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1863207 648.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1817850 606.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1917662 611.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1668811 763.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1795159 638.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1889841 676.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1658430 677.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1683805 617.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 2013060 557.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 2090346 589.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1664809 703.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 2149461 558.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1799823 651.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1846158 662.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1509614 679.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1802488 618.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1146 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1128 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1240 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 930816 1248 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1095 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1298 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1170 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 984279 1204 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1189 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1153 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 257323 4641 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 274695 4339 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 323830 4143 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 291009 4123 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 296120 4689 ns/op 1411 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 233097 4445 ns/op 1411 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 263600 4506 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 305414 4082 ns/op 1411 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 289495 4249 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 363501 4202 ns/op 1411 B/op 1 allocs/op
+PASS
+ok golang.org/x/exp/slog/benchmarks 293.287s
diff --git a/slog/benchmarks/slog.bench b/slog/benchmarks/slog.bench
new file mode 100644
index 0000000..9384458
--- /dev/null
+++ b/slog/benchmarks/slog.bench
@@ -0,0 +1,206 @@
+goos: linux
+goarch: amd64
+pkg: golang.org/x/exp/slog/benchmarks
+cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
+BenchmarkAttrs/disabled/5_args-8 85028928 11.94 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 97603412 11.20 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 98026633 11.41 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 98189282 11.44 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.54 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 99336901 11.69 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 94035765 11.55 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.26 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.27 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args-8 100000000 11.15 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 64397640 18.53 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 65978652 18.69 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 75175520 18.56 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 67500830 19.13 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 63446371 18.54 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 61653506 19.26 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 62390637 18.82 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 54138097 19.05 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 64863612 18.90 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/5_args_ctx-8 63702469 19.24 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 58628262 20.38 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 54573738 20.43 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 57079495 20.44 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 56993048 20.26 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 57084168 20.40 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 54865020 20.47 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 56883810 20.68 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 57454248 20.23 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 58203282 20.41 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/10_args-8 53383620 20.71 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16833667 71.12 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16628658 71.27 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16729125 72.50 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16386619 71.76 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16549412 71.29 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16790506 72.17 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16463736 72.50 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16542148 72.09 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16668524 73.04 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/disabled/40_args-8 16624455 72.58 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 4012514 303.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3993012 297.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 4061977 299.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 4008031 304.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3919713 308.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3841856 307.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3907417 300.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3629317 300.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 4037016 303.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 4073185 295.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 4065908 301.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3974256 310.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3553280 312.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3492072 319.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3975591 297.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3990794 303.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3865233 304.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 3839840 313.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 4023607 311.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/5_args_ctx-8 4048357 297.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2134437 550.9 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2101972 557.3 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2145542 552.7 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2183551 551.4 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2160979 556.7 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2251357 553.8 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2099067 556.3 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2066022 561.3 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2101921 560.8 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2074694 563.1 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 682222 2026 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 611084 2068 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 572643 2096 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 593726 2046 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 607022 2037 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 580706 2043 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 571491 2071 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 578845 2031 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 581244 2084 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 608149 2069 ns/op 1408 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3144124 387.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3161896 380.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3172803 396.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3154216 379.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3153247 378.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3185548 375.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3123097 381.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3163221 402.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3047324 388.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 3151058 384.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3153196 383.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3138043 382.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3030826 421.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3118032 399.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3103054 384.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3040407 398.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3057907 384.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 2804430 380.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3109689 383.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/5_args_ctx-8 3161005 382.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1558144 769.7 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1618090 703.0 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1641794 709.1 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1642668 726.3 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1614679 715.0 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1664410 747.8 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1375760 735.9 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1654279 776.4 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1617708 728.0 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1622902 724.0 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 385968 2840 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 448968 2741 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 430761 2729 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 488749 2836 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 446198 2909 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 392104 2860 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 435178 2807 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 408925 2878 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 420307 2824 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 417450 2853 ns/op 1413 B/op 1 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1221808 1013 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1222080 969.3 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1226036 966.0 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1298272 939.0 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1237682 960.2 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1278205 977.6 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1000000 1007 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1000000 1050 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1205269 1043 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args-8 1000000 1016 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1206166 985.5 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1000000 1020 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1000000 1039 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1000000 1044 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1252417 973.3 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1230265 1025 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1202907 1049 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1000000 1061 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1000000 1057 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/5_args_ctx-8 1000000 1048 ns/op 8 B/op 2 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 722588 1609 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 841220 1562 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 803983 1600 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 823732 1598 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 797410 1617 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 827581 1648 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 731049 1647 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 818089 1572 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 808208 1502 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/10_args-8 851550 1574 ns/op 224 B/op 5 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 247522 5274 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 239428 5060 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 232135 4937 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 213756 5148 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 256321 5316 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 212294 5628 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 229651 5011 ns/op 1476 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 220143 5153 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 219468 5484 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/Text_discard/40_args-8 223111 4813 ns/op 1477 B/op 17 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1523245 825.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1532900 781.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1482728 788.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1526634 806.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1568881 762.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1454412 731.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1564548 793.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1469851 784.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1393617 867.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1500427 786.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1519324 794.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1303532 791.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1389266 780.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1487719 763.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1461187 772.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1463911 826.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1550698 797.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1487751 744.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1514853 797.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args_ctx-8 1605760 750.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1330 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 957187 1326 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 914820 1307 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 969138 1301 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 840184 1258 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 991464 1273 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 942420 1314 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 998630 1286 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 898899 1272 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 901351 1300 ns/op 208 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 298953 4364 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 253590 4395 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 271449 4437 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 276140 4354 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 258526 4685 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 292897 4555 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 262538 4392 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 274155 4349 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 296401 4584 ns/op 1412 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 277298 4393 ns/op 1412 B/op 1 allocs/op
+PASS
+ok golang.org/x/exp/slog/benchmarks 296.660s
diff --git a/slog/benchmarks/zap_benchmarks/go.mod b/slog/benchmarks/zap_benchmarks/go.mod
new file mode 100644
index 0000000..b0ce009
--- /dev/null
+++ b/slog/benchmarks/zap_benchmarks/go.mod
@@ -0,0 +1,15 @@
+module golang.org/x/exp/slog/benchmarks/zap_benchmarks
+
+go 1.19
+
+require (
+ go.uber.org/zap v1.24.0
+ golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561
+)
+
+require (
+ go.uber.org/atomic v1.7.0 // indirect
+ go.uber.org/multierr v1.6.0 // indirect
+)
+
+replace golang.org/x/exp => ../../..
diff --git a/slog/benchmarks/zap_benchmarks/go.sum b/slog/benchmarks/zap_benchmarks/go.sum
new file mode 100644
index 0000000..017cf3a
--- /dev/null
+++ b/slog/benchmarks/zap_benchmarks/go.sum
@@ -0,0 +1,18 @@
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/slog/benchmarks/zap_benchmarks/out.bench b/slog/benchmarks/zap_benchmarks/out.bench
new file mode 100644
index 0000000..5965fa1
--- /dev/null
+++ b/slog/benchmarks/zap_benchmarks/out.bench
@@ -0,0 +1,96 @@
+goos: linux
+goarch: amd64
+pkg: golang.org/x/exp/slog/benchmarks/zap_benchmarks
+cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
+BenchmarkAttrs/async_discard/5_args-8 3510766 350.9 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3405861 338.9 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3537150 335.5 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3553928 357.7 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3555067 347.1 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3556012 349.7 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3439095 344.5 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3488470 343.8 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3330931 355.3 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/5_args-8 3451645 351.8 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 1947522 592.2 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2029161 586.8 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2018308 600.5 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2063016 630.4 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2063624 604.0 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2027398 642.2 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2036904 601.1 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 2048212 596.8 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 1916887 606.3 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/10_args-8 1963885 586.8 ns/op 640 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 571792 2174 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 579630 1964 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 557364 2169 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 689043 1973 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 531727 1947 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 544908 1999 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 584899 1962 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 577837 1974 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 649815 1909 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/async_discard/40_args-8 591880 1928 ns/op 2689 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2430532 500.0 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2449464 497.4 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2461346 511.7 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2358171 500.4 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2389845 501.9 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2371099 507.1 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2383549 499.4 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2348478 505.9 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2397246 509.3 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/5_args-8 2405686 500.8 ns/op 320 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1409547 863.9 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1422519 870.7 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1417478 858.7 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1426738 857.8 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1397716 829.8 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1337100 849.0 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1388724 874.8 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1363362 853.7 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1354309 836.8 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/10_args-8 1376586 834.2 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 403941 2805 ns/op 2697 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 422526 2809 ns/op 2698 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 346633 3071 ns/op 2697 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 434634 2908 ns/op 2697 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 394057 2922 ns/op 2699 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 425965 2871 ns/op 2698 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 405974 2795 ns/op 2698 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 433692 2881 ns/op 2698 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 385582 3073 ns/op 2698 B/op 1 allocs/op
+BenchmarkAttrs/fastText_discard/40_args-8 410702 2843 ns/op 2697 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1635340 787.4 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1560625 771.1 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1566784 783.5 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1491864 771.4 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1548451 752.1 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1566960 765.3 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1548302 810.1 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1518164 766.6 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1651222 759.2 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 1569230 748.1 ns/op 321 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1246 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1273 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 942969 1243 ns/op 641 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 982197 1225 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1245 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1230 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 959143 1239 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1344 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 1000000 1315 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 908618 1277 ns/op 642 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 277483 4209 ns/op 2699 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 331118 4211 ns/op 2699 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 274591 4073 ns/op 2700 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 282768 4124 ns/op 2699 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 265305 4051 ns/op 2701 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 273698 3996 ns/op 2700 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 310609 4036 ns/op 2700 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 290971 4094 ns/op 2700 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 277810 4109 ns/op 2699 B/op 1 allocs/op
+BenchmarkAttrs/JSON_discard/40_args-8 286332 4221 ns/op 2700 B/op 1 allocs/op
+PASS
+ok golang.org/x/exp/slog/benchmarks/zap_benchmarks 140.608s
diff --git a/slog/benchmarks/zap_benchmarks/zap_benchmarks_test.go b/slog/benchmarks/zap_benchmarks/zap_benchmarks_test.go
new file mode 100644
index 0000000..ffc0550
--- /dev/null
+++ b/slog/benchmarks/zap_benchmarks/zap_benchmarks_test.go
@@ -0,0 +1,138 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zap_benchmarks
+
+import (
+ "testing"
+ "time"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ slogbench "golang.org/x/exp/slog/benchmarks"
+)
+
+// Keep in sync (same names and behavior) as the
+// benchmarks in the parent directory.
+
+func BenchmarkAttrs(b *testing.B) {
+ for _, logger := range []struct {
+ name string
+ l *zap.Logger
+ }{
+ {
+ "async discard",
+ zap.New(&asyncCore{}),
+ },
+ {
+ "fastText discard",
+ zap.New(zapcore.NewCore(
+ &fastTextEncoder{},
+ &discarder{},
+ zap.DebugLevel)),
+ },
+ {
+ "JSON discard",
+ zap.New(zapcore.NewCore(
+ zapcore.NewJSONEncoder(zapcore.EncoderConfig{
+ MessageKey: "msg",
+ LevelKey: "level",
+ TimeKey: "time",
+ EncodeTime: zapcore.TimeEncoderOfLayout(time.RFC3339Nano),
+ }),
+ &discarder{},
+ zap.DebugLevel)),
+ }} {
+ l := logger.l
+ b.Run(logger.name, func(b *testing.B) {
+ for _, call := range []struct {
+ name string
+ f func()
+ }{
+ {
+ "5 args",
+ func() {
+ l.Info(slogbench.TestMessage,
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError))
+ },
+ },
+ {
+ "10 args",
+ func() {
+ l.Info(slogbench.TestMessage,
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError))
+ },
+ },
+ {
+ "40 args",
+ func() {
+ l.Info(slogbench.TestMessage,
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError),
+ zap.String("string", slogbench.TestString),
+ zap.Int("status", slogbench.TestInt),
+ zap.Duration("duration", slogbench.TestDuration),
+ zap.Time("time", slogbench.TestTime),
+ zap.Any("error", slogbench.TestError))
+ },
+ },
+ } {
+ b.Run(call.name, func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ call.f()
+ }
+ })
+ })
+ }
+ })
+ }
+}
diff --git a/slog/benchmarks/zap_benchmarks/zap_encoders.go b/slog/benchmarks/zap_benchmarks/zap_encoders.go
new file mode 100644
index 0000000..918d2dd
--- /dev/null
+++ b/slog/benchmarks/zap_benchmarks/zap_encoders.go
@@ -0,0 +1,150 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zap_benchmarks
+
+import (
+ "io"
+ "sync"
+ "time"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/buffer"
+ "go.uber.org/zap/zapcore"
+)
+
+// from zap/internal/ztest
+
+// A syncer is a spy for the Sync portion of zapcore.WriteSyncer.
+type syncer struct {
+ err error
+ called bool
+}
+
+// SetError sets the error that the Sync method will return.
+func (s *syncer) SetError(err error) {
+ s.err = err
+}
+
+// Sync records that it was called, then returns the user-supplied error (if
+// any).
+func (s *syncer) Sync() error {
+ s.called = true
+ return s.err
+}
+
+// Called reports whether the Sync method was called.
+func (s *syncer) Called() bool {
+ return s.called
+}
+
+// A discarder sends all writes to ioutil.Discard.
+type discarder struct{ syncer }
+
+// Write implements io.Writer.
+func (d *discarder) Write(b []byte) (int, error) {
+ return io.Discard.Write(b)
+}
+
+// fastTextEncoder mimics slog/benchmarks.fastTextHandler.
+type fastTextEncoder struct {
+ buf *buffer.Buffer
+ zapcore.ObjectEncoder
+}
+
+var bufferPool = buffer.NewPool()
+
+var tePool = sync.Pool{
+ New: func() any { return &fastTextEncoder{} },
+}
+
+func newFastTextEncoder() *fastTextEncoder {
+ e := tePool.Get().(*fastTextEncoder)
+ e.buf = bufferPool.Get()
+ return e
+}
+
+func (c *fastTextEncoder) Clone() zapcore.Encoder {
+ clone := newFastTextEncoder()
+ if c.buf != nil {
+ panic("buf should be nil")
+ clone.buf.Write(c.buf.Bytes())
+ }
+ return clone
+}
+
+func (c *fastTextEncoder) EncodeEntry(e zapcore.Entry, fs []zap.Field) (*buffer.Buffer, error) {
+ c2 := newFastTextEncoder()
+ if !e.Time.IsZero() {
+ c2.buf.AppendString("time=")
+ c2.appendTime(e.Time)
+ c2.buf.AppendByte(' ')
+ }
+ c2.buf.AppendString("level=")
+ c2.buf.AppendInt(int64(e.Level))
+ c2.buf.AppendByte(' ')
+ c2.buf.AppendString("msg=")
+ c2.buf.AppendString(e.Message)
+ for _, f := range fs {
+ c2.buf.AppendByte(' ')
+ f.AddTo(c2)
+ }
+ c2.buf.AppendString("\n")
+ buf := c2.buf
+ tePool.Put(c2)
+ return buf, nil
+}
+
+func (c *fastTextEncoder) AddString(key, value string) {
+ c.buf.AppendString(key)
+ c.buf.AppendByte('=')
+ c.buf.AppendString(value)
+}
+
+func (c *fastTextEncoder) AddTime(key string, value time.Time) {
+ c.buf.AppendString(key)
+ c.buf.AppendByte('=')
+ c.appendTime(value)
+}
+
+func (c *fastTextEncoder) AddDuration(key string, value time.Duration) {
+ c.buf.AppendString(key)
+ c.buf.AppendByte('=')
+ c.buf.AppendInt(value.Nanoseconds())
+}
+
+func (c *fastTextEncoder) AddInt64(key string, value int64) {
+ c.buf.AppendString(key)
+ c.buf.AppendByte('=')
+ c.buf.AppendInt(value)
+}
+func (c *fastTextEncoder) appendTime(t time.Time) {
+ c.buf.AppendInt(t.Unix())
+}
+
+// asyncCore mimics slog/benchmarks.asyncHandler.
+type asyncCore struct {
+ ringBuffer [100]entryAndFields
+ next int
+}
+
+type entryAndFields struct {
+ e zapcore.Entry
+ f []zap.Field
+}
+
+func (*asyncCore) Enabled(zapcore.Level) bool { return true }
+func (c *asyncCore) With([]zap.Field) zapcore.Core { return c }
+func (*asyncCore) Sync() error { return nil }
+
+// Also needed to make this non-trivial.
+func (c *asyncCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
+ return ce.AddCore(ent, c)
+}
+
+func (c *asyncCore) Write(e zapcore.Entry, f []zap.Field) error {
+ c.ringBuffer[c.next] = entryAndFields{e, f}
+ c.next = (c.next + 1) % len(c.ringBuffer)
+ return nil
+}
diff --git a/slog/benchmarks/zap_benchmarks/zap_encoders_test.go b/slog/benchmarks/zap_benchmarks/zap_encoders_test.go
new file mode 100644
index 0000000..9bcb9f3
--- /dev/null
+++ b/slog/benchmarks/zap_benchmarks/zap_encoders_test.go
@@ -0,0 +1,60 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zap_benchmarks
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "golang.org/x/exp/slog"
+ slogbench "golang.org/x/exp/slog/benchmarks"
+)
+
+func TestEncoders(t *testing.T) {
+ entry := zapcore.Entry{Time: slogbench.TestTime, Message: slogbench.TestMessage}
+ fields := attrsToFields(slogbench.TestAttrs)
+ t.Run("text", func(t *testing.T) {
+ te := newFastTextEncoder()
+ defer tePool.Put(te)
+ buf, err := te.EncodeEntry(entry, fields)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer buf.Free()
+ got := strings.ToLower(buf.String())
+ want := strings.ToLower(slogbench.WantText)
+ if got != want {
+ t.Errorf("\ngot %s\nwant %s", got, want)
+ }
+ })
+}
+
+func attrsToFields(attrs []slog.Attr) []zap.Field {
+ var fields []zap.Field
+ for _, a := range slogbench.TestAttrs {
+ var f zap.Field
+ k := a.Key
+ v := a.Value
+ switch v.Kind() {
+ case slog.KindString:
+ f = zap.String(k, v.String())
+ case slog.KindInt64:
+ f = zap.Int64(k, v.Int64())
+ case slog.KindDuration:
+ f = zap.Duration(k, v.Duration())
+ case slog.KindTime:
+ f = zap.Time(k, v.Time())
+ case slog.KindAny:
+ f = zap.Any(k, v)
+ default:
+ panic(fmt.Sprintf("unknown kind %d", v.Kind()))
+ }
+ fields = append(fields, f)
+ }
+ return fields
+}
diff --git a/slog/benchmarks/zerolog_benchmarks/go.mod b/slog/benchmarks/zerolog_benchmarks/go.mod
new file mode 100644
index 0000000..41f470f
--- /dev/null
+++ b/slog/benchmarks/zerolog_benchmarks/go.mod
@@ -0,0 +1,16 @@
+module golang.org/x/exp/slog/benchmarks/zerolog_benchmarks
+
+go 1.20
+
+require (
+ github.com/rs/zerolog v1.28.0
+ golang.org/x/exp v0.0.0-20230113152452-c42ee1cf562e
+)
+
+require (
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+)
+
+replace golang.org/x/exp => ../../..
diff --git a/slog/benchmarks/zerolog_benchmarks/go.sum b/slog/benchmarks/zerolog_benchmarks/go.sum
new file mode 100644
index 0000000..e2a7353
--- /dev/null
+++ b/slog/benchmarks/zerolog_benchmarks/go.sum
@@ -0,0 +1,14 @@
+github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
+github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/slog/benchmarks/zerolog_benchmarks/out.bench b/slog/benchmarks/zerolog_benchmarks/out.bench
new file mode 100644
index 0000000..a7bb3c8
--- /dev/null
+++ b/slog/benchmarks/zerolog_benchmarks/out.bench
@@ -0,0 +1,26 @@
+goos: linux
+goarch: amd64
+pkg: golang.org/x/exp/slog/benchmarks/zerolog_benchmarks
+cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
+BenchmarkAttrs/JSON_discard/5_args-8 4137742 295.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4177807 290.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4067436 292.4 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4089474 294.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4057003 300.5 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 3972150 290.9 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4090122 312.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4305754 315.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 3576042 327.3 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/5_args-8 4073211 290.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3213050 389.1 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3510774 350.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3804168 319.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3812370 334.6 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3738373 315.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3918612 321.0 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3649148 348.2 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3699234 338.7 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3639038 330.8 ns/op 0 B/op 0 allocs/op
+BenchmarkAttrs/JSON_discard/10_args-8 3439284 323.2 ns/op 0 B/op 0 allocs/op
+PASS
+ok golang.org/x/exp/slog/benchmarks/zerolog_benchmarks 30.876s
diff --git a/slog/benchmarks/zerolog_benchmarks/zerolog_benchmarks_test.go b/slog/benchmarks/zerolog_benchmarks/zerolog_benchmarks_test.go
new file mode 100644
index 0000000..61d14bf
--- /dev/null
+++ b/slog/benchmarks/zerolog_benchmarks/zerolog_benchmarks_test.go
@@ -0,0 +1,55 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package zerolog_benchmarks
+
+import (
+ "io"
+ "testing"
+
+ "github.com/rs/zerolog"
+ slogbench "golang.org/x/exp/slog/benchmarks"
+)
+
+// Keep in sync (same names and behavior) as the
+// benchmarks in the parent directory.
+
+func BenchmarkAttrs(b *testing.B) {
+ logger := zerolog.New(zerolog.SyncWriter(io.Discard)).With().Timestamp().Logger()
+ b.Run("JSON discard", func(b *testing.B) {
+ b.Run("5 args", func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ logger.Info().
+ Str("string", slogbench.TestString).
+ Int("status", slogbench.TestInt).
+ Dur("duration", slogbench.TestDuration).
+ Time("time", slogbench.TestTime).
+ Err(slogbench.TestError).
+ Msg(slogbench.TestMessage)
+ }
+ })
+ })
+ b.Run("10 args", func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ logger.Info().
+ Str("string", slogbench.TestString).
+ Int("status", slogbench.TestInt).
+ Dur("duration", slogbench.TestDuration).
+ Time("time", slogbench.TestTime).
+ Err(slogbench.TestError).
+ Str("string", slogbench.TestString).
+ Int("status", slogbench.TestInt).
+ Dur("duration", slogbench.TestDuration).
+ Time("time", slogbench.TestTime).
+ Err(slogbench.TestError).
+ Msg(slogbench.TestMessage)
+ }
+ })
+ })
+ })
+}
diff --git a/slog/doc.go b/slog/doc.go
new file mode 100644
index 0000000..4beaf86
--- /dev/null
+++ b/slog/doc.go
@@ -0,0 +1,316 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package slog provides structured logging,
+in which log records include a message,
+a severity level, and various other attributes
+expressed as key-value pairs.
+
+It defines a type, [Logger],
+which provides several methods (such as [Logger.Info] and [Logger.Error])
+for reporting events of interest.
+
+Each Logger is associated with a [Handler].
+A Logger output method creates a [Record] from the method arguments
+and passes it to the Handler, which decides how to handle it.
+There is a default Logger accessible through top-level functions
+(such as [Info] and [Error]) that call the corresponding Logger methods.
+
+A log record consists of a time, a level, a message, and a set of key-value
+pairs, where the keys are strings and the values may be of any type.
+As an example,
+
+ slog.Info("hello", "count", 3)
+
+creates a record containing the time of the call,
+a level of Info, the message "hello", and a single
+pair with key "count" and value 3.
+
+The [Info] top-level function calls the [Logger.Info] method on the default Logger.
+In addition to [Logger.Info], there are methods for Debug, Warn and Error levels.
+Besides these convenience methods for common levels,
+there is also a [Logger.Log] method which takes the level as an argument.
+Each of these methods has a corresponding top-level function that uses the
+default logger.
+
+The default handler formats the log record's message, time, level, and attributes
+as a string and passes it to the [log] package.
+
+ 2022/11/08 15:28:26 INFO hello count=3
+
+For more control over the output format, create a logger with a different handler.
+This statement uses [New] to create a new logger with a TextHandler
+that writes structured records in text form to standard error:
+
+ logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
+
+[TextHandler] output is a sequence of key=value pairs, easily and unambiguously
+parsed by machine. This statement:
+
+ logger.Info("hello", "count", 3)
+
+produces this output:
+
+ time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3
+
+The package also provides [JSONHandler], whose output is line-delimited JSON:
+
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
+ logger.Info("hello", "count", 3)
+
+produces this output:
+
+ {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}
+
+Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions].
+There are options for setting the minimum level (see Levels, below),
+displaying the source file and line of the log call, and
+modifying attributes before they are logged.
+
+Setting a logger as the default with
+
+ slog.SetDefault(logger)
+
+will cause the top-level functions like [Info] to use it.
+[SetDefault] also updates the default logger used by the [log] package,
+so that existing applications that use [log.Printf] and related functions
+will send log records to the logger's handler without needing to be rewritten.
+
+Some attributes are common to many log calls.
+For example, you may wish to include the URL or trace identifier of a server request
+with all log events arising from the request.
+Rather than repeat the attribute with every log call, you can use [Logger.With]
+to construct a new Logger containing the attributes:
+
+ logger2 := logger.With("url", r.URL)
+
+The arguments to With are the same key-value pairs used in [Logger.Info].
+The result is a new Logger with the same handler as the original, but additional
+attributes that will appear in the output of every call.
+
+# Levels
+
+A [Level] is an integer representing the importance or severity of a log event.
+The higher the level, the more severe the event.
+This package defines constants for the most common levels,
+but any int can be used as a level.
+
+In an application, you may wish to log messages only at a certain level or greater.
+One common configuration is to log messages at Info or higher levels,
+suppressing debug logging until it is needed.
+The built-in handlers can be configured with the minimum level to output by
+setting [HandlerOptions.Level].
+The program's `main` function typically does this.
+The default value is LevelInfo.
+
+Setting the [HandlerOptions.Level] field to a [Level] value
+fixes the handler's minimum level throughout its lifetime.
+Setting it to a [LevelVar] allows the level to be varied dynamically.
+A LevelVar holds a Level and is safe to read or write from multiple
+goroutines.
+To vary the level dynamically for an entire program, first initialize
+a global LevelVar:
+
+ var programLevel = new(slog.LevelVar) // Info by default
+
+Then use the LevelVar to construct a handler, and make it the default:
+
+ h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})
+ slog.SetDefault(slog.New(h))
+
+Now the program can change its logging level with a single statement:
+
+ programLevel.Set(slog.LevelDebug)
+
+# Groups
+
+Attributes can be collected into groups.
+A group has a name that is used to qualify the names of its attributes.
+How this qualification is displayed depends on the handler.
+[TextHandler] separates the group and attribute names with a dot.
+[JSONHandler] treats each group as a separate JSON object, with the group name as the key.
+
+Use [Group] to create a Group attribute from a name and a list of key-value pairs:
+
+ slog.Group("request",
+ "method", r.Method,
+ "url", r.URL)
+
+TextHandler would display this group as
+
+ request.method=GET request.url=http://example.com
+
+JSONHandler would display it as
+
+ "request":{"method":"GET","url":"http://example.com"}
+
+Use [Logger.WithGroup] to qualify all of a Logger's output
+with a group name. Calling WithGroup on a Logger results in a
+new Logger with the same Handler as the original, but with all
+its attributes qualified by the group name.
+
+This can help prevent duplicate attribute keys in large systems,
+where subsystems might use the same keys.
+Pass each subsystem a different Logger with its own group name so that
+potential duplicates are qualified:
+
+ logger := slog.Default().With("id", systemID)
+ parserLogger := logger.WithGroup("parser")
+ parseInput(input, parserLogger)
+
+When parseInput logs with parserLogger, its keys will be qualified with "parser",
+so even if it uses the common key "id", the log line will have distinct keys.
+
+# Contexts
+
+Some handlers may wish to include information from the [context.Context] that is
+available at the call site. One example of such information
+is the identifier for the current span when tracing is enabled.
+
+The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first
+argument, as do their corresponding top-level functions.
+
+Although the convenience methods on Logger (Info and so on) and the
+corresponding top-level functions do not take a context, the alternatives ending
+in "Context" do. For example,
+
+ slog.InfoContext(ctx, "message")
+
+It is recommended to pass a context to an output method if one is available.
+
+# Attrs and Values
+
+An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as
+alternating keys and values. The statement
+
+ slog.Info("hello", slog.Int("count", 3))
+
+behaves the same as
+
+ slog.Info("hello", "count", 3)
+
+There are convenience constructors for [Attr] such as [Int], [String], and [Bool]
+for common types, as well as the function [Any] for constructing Attrs of any
+type.
+
+The value part of an Attr is a type called [Value].
+Like an [any], a Value can hold any Go value,
+but it can represent typical values, including all numbers and strings,
+without an allocation.
+
+For the most efficient log output, use [Logger.LogAttrs].
+It is similar to [Logger.Log] but accepts only Attrs, not alternating
+keys and values; this allows it, too, to avoid allocation.
+
+The call
+
+ logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3))
+
+is the most efficient way to achieve the same output as
+
+ slog.Info("hello", "count", 3)
+
+# Customizing a type's logging behavior
+
+If a type implements the [LogValuer] interface, the [Value] returned from its LogValue
+method is used for logging. You can use this to control how values of the type
+appear in logs. For example, you can redact secret information like passwords,
+or gather a struct's fields in a Group. See the examples under [LogValuer] for
+details.
+
+A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve]
+method handles these cases carefully, avoiding infinite loops and unbounded recursion.
+Handler authors and others may wish to use Value.Resolve instead of calling LogValue directly.
+
+# Wrapping output methods
+
+The logger functions use reflection over the call stack to find the file name
+and line number of the logging call within the application. This can produce
+incorrect source information for functions that wrap slog. For instance, if you
+define this function in file mylog.go:
+
+ func Infof(format string, args ...any) {
+ slog.Default().Info(fmt.Sprintf(format, args...))
+ }
+
+and you call it like this in main.go:
+
+ Infof(slog.Default(), "hello, %s", "world")
+
+then slog will report the source file as mylog.go, not main.go.
+
+A correct implementation of Infof will obtain the source location
+(pc) and pass it to NewRecord.
+The Infof function in the package-level example called "wrapping"
+demonstrates how to do this.
+
+# Working with Records
+
+Sometimes a Handler will need to modify a Record
+before passing it on to another Handler or backend.
+A Record contains a mixture of simple public fields (e.g. Time, Level, Message)
+and hidden fields that refer to state (such as attributes) indirectly. This
+means that modifying a simple copy of a Record (e.g. by calling
+[Record.Add] or [Record.AddAttrs] to add attributes)
+may have unexpected effects on the original.
+Before modifying a Record, use [Clone] to
+create a copy that shares no state with the original,
+or create a new Record with [NewRecord]
+and build up its Attrs by traversing the old ones with [Record.Attrs].
+
+# Performance considerations
+
+If profiling your application demonstrates that logging is taking significant time,
+the following suggestions may help.
+
+If many log lines have a common attribute, use [Logger.With] to create a Logger with
+that attribute. The built-in handlers will format that attribute only once, at the
+call to [Logger.With]. The [Handler] interface is designed to allow that optimization,
+and a well-written Handler should take advantage of it.
+
+The arguments to a log call are always evaluated, even if the log event is discarded.
+If possible, defer computation so that it happens only if the value is actually logged.
+For example, consider the call
+
+ slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily
+
+The URL.String method will be called even if the logger discards Info-level events.
+Instead, pass the URL directly:
+
+ slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed
+
+The built-in [TextHandler] will call its String method, but only
+if the log event is enabled.
+Avoiding the call to String also preserves the structure of the underlying value.
+For example [JSONHandler] emits the components of the parsed URL as a JSON object.
+If you want to avoid eagerly paying the cost of the String call
+without causing the handler to potentially inspect the structure of the value,
+wrap the value in a fmt.Stringer implementation that hides its Marshal methods.
+
+You can also use the [LogValuer] interface to avoid unnecessary work in disabled log
+calls. Say you need to log some expensive value:
+
+ slog.Debug("frobbing", "value", computeExpensiveValue(arg))
+
+Even if this line is disabled, computeExpensiveValue will be called.
+To avoid that, define a type implementing LogValuer:
+
+ type expensive struct { arg int }
+
+ func (e expensive) LogValue() slog.Value {
+ return slog.AnyValue(computeExpensiveValue(e.arg))
+ }
+
+Then use a value of that type in log calls:
+
+ slog.Debug("frobbing", "value", expensive{arg})
+
+Now computeExpensiveValue will only be called when the line is enabled.
+
+The built-in handlers acquire a lock before calling [io.Writer.Write]
+to ensure that each record is written in one piece. User-defined
+handlers are responsible for their own locking.
+*/
+package slog
diff --git a/slog/example_custom_levels_test.go b/slog/example_custom_levels_test.go
new file mode 100644
index 0000000..9422fc9
--- /dev/null
+++ b/slog/example_custom_levels_test.go
@@ -0,0 +1,92 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "os"
+
+ "golang.org/x/exp/slog"
+)
+
+// This example demonstrates using custom log levels and custom log level names.
+// In addition to the default log levels, it introduces Trace, Notice, and
+// Emergency levels. The ReplaceAttr changes the way levels are printed for both
+// the standard log levels and the custom log levels.
+func ExampleHandlerOptions_customLevels() {
+ // Exported constants from a custom logging package.
+ const (
+ LevelTrace = slog.Level(-8)
+ LevelDebug = slog.LevelDebug
+ LevelInfo = slog.LevelInfo
+ LevelNotice = slog.Level(2)
+ LevelWarning = slog.LevelWarn
+ LevelError = slog.LevelError
+ LevelEmergency = slog.Level(12)
+ )
+
+ th := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
+ // Set a custom level to show all log output. The default value is
+ // LevelInfo, which would drop Debug and Trace logs.
+ Level: LevelTrace,
+
+ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
+ // Remove time from the output for predictable test output.
+ if a.Key == slog.TimeKey {
+ return slog.Attr{}
+ }
+
+ // Customize the name of the level key and the output string, including
+ // custom level values.
+ if a.Key == slog.LevelKey {
+ // Rename the level key from "level" to "sev".
+ a.Key = "sev"
+
+ // Handle custom level values.
+ level := a.Value.Any().(slog.Level)
+
+ // This could also look up the name from a map or other structure, but
+ // this demonstrates using a switch statement to rename levels. For
+ // maximum performance, the string values should be constants, but this
+ // example uses the raw strings for readability.
+ switch {
+ case level < LevelDebug:
+ a.Value = slog.StringValue("TRACE")
+ case level < LevelInfo:
+ a.Value = slog.StringValue("DEBUG")
+ case level < LevelNotice:
+ a.Value = slog.StringValue("INFO")
+ case level < LevelWarning:
+ a.Value = slog.StringValue("NOTICE")
+ case level < LevelError:
+ a.Value = slog.StringValue("WARNING")
+ case level < LevelEmergency:
+ a.Value = slog.StringValue("ERROR")
+ default:
+ a.Value = slog.StringValue("EMERGENCY")
+ }
+ }
+
+ return a
+ },
+ })
+
+ logger := slog.New(th)
+ logger.Log(nil, LevelEmergency, "missing pilots")
+ logger.Error("failed to start engines", "err", "missing fuel")
+ logger.Warn("falling back to default value")
+ logger.Log(nil, LevelNotice, "all systems are running")
+ logger.Info("initiating launch")
+ logger.Debug("starting background job")
+ logger.Log(nil, LevelTrace, "button clicked")
+
+ // Output:
+ // sev=EMERGENCY msg="missing pilots"
+ // sev=ERROR msg="failed to start engines" err="missing fuel"
+ // sev=WARNING msg="falling back to default value"
+ // sev=NOTICE msg="all systems are running"
+ // sev=INFO msg="initiating launch"
+ // sev=DEBUG msg="starting background job"
+ // sev=TRACE msg="button clicked"
+}
diff --git a/slog/example_level_handler_test.go b/slog/example_level_handler_test.go
new file mode 100644
index 0000000..6c7221d
--- /dev/null
+++ b/slog/example_level_handler_test.go
@@ -0,0 +1,74 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "context"
+ "os"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/internal/testutil"
+)
+
+// A LevelHandler wraps a Handler with an Enabled method
+// that returns false for levels below a minimum.
+type LevelHandler struct {
+ level slog.Leveler
+ handler slog.Handler
+}
+
+// NewLevelHandler returns a LevelHandler with the given level.
+// All methods except Enabled delegate to h.
+func NewLevelHandler(level slog.Leveler, h slog.Handler) *LevelHandler {
+ // Optimization: avoid chains of LevelHandlers.
+ if lh, ok := h.(*LevelHandler); ok {
+ h = lh.Handler()
+ }
+ return &LevelHandler{level, h}
+}
+
+// Enabled implements Handler.Enabled by reporting whether
+// level is at least as large as h's level.
+func (h *LevelHandler) Enabled(_ context.Context, level slog.Level) bool {
+ return level >= h.level.Level()
+}
+
+// Handle implements Handler.Handle.
+func (h *LevelHandler) Handle(ctx context.Context, r slog.Record) error {
+ return h.handler.Handle(ctx, r)
+}
+
+// WithAttrs implements Handler.WithAttrs.
+func (h *LevelHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ return NewLevelHandler(h.level, h.handler.WithAttrs(attrs))
+}
+
+// WithGroup implements Handler.WithGroup.
+func (h *LevelHandler) WithGroup(name string) slog.Handler {
+ return NewLevelHandler(h.level, h.handler.WithGroup(name))
+}
+
+// Handler returns the Handler wrapped by h.
+func (h *LevelHandler) Handler() slog.Handler {
+ return h.handler
+}
+
+// This example shows how to Use a LevelHandler to change the level of an
+// existing Handler while preserving its other behavior.
+//
+// This example demonstrates increasing the log level to reduce a logger's
+// output.
+//
+// Another typical use would be to decrease the log level (to LevelDebug, say)
+// during a part of the program that was suspected of containing a bug.
+func ExampleHandler_levelHandler() {
+ th := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime})
+ logger := slog.New(NewLevelHandler(slog.LevelWarn, th))
+ logger.Info("not printed")
+ logger.Warn("printed")
+
+ // Output:
+ // level=WARN msg=printed
+}
diff --git a/slog/example_logvaluer_group_test.go b/slog/example_logvaluer_group_test.go
new file mode 100644
index 0000000..61892eb
--- /dev/null
+++ b/slog/example_logvaluer_group_test.go
@@ -0,0 +1,35 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import "golang.org/x/exp/slog"
+
+type Name struct {
+ First, Last string
+}
+
+// LogValue implements slog.LogValuer.
+// It returns a group containing the fields of
+// the Name, so that they appear together in the log output.
+func (n Name) LogValue() slog.Value {
+ return slog.GroupValue(
+ slog.String("first", n.First),
+ slog.String("last", n.Last))
+}
+
+func ExampleLogValuer_group() {
+ n := Name{"Perry", "Platypus"}
+ slog.Info("mission accomplished", "agent", n)
+
+ // JSON Output would look in part like:
+ // {
+ // ...
+ // "msg": "mission accomplished",
+ // "agent": {
+ // "first": "Perry",
+ // "last": "Platypus"
+ // }
+ // }
+}
diff --git a/slog/example_logvaluer_secret_test.go b/slog/example_logvaluer_secret_test.go
new file mode 100644
index 0000000..0d40519
--- /dev/null
+++ b/slog/example_logvaluer_secret_test.go
@@ -0,0 +1,32 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "os"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/internal/testutil"
+)
+
+// A token is a secret value that grants permissions.
+type Token string
+
+// LogValue implements slog.LogValuer.
+// It avoids revealing the token.
+func (Token) LogValue() slog.Value {
+ return slog.StringValue("REDACTED_TOKEN")
+}
+
+// This example demonstrates a Value that replaces itself
+// with an alternative representation to avoid revealing secrets.
+func ExampleLogValuer_secret() {
+ t := Token("shhhh!")
+ logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}))
+ logger.Info("permission granted", "user", "Perry", "token", t)
+
+ // Output:
+ // level=INFO msg="permission granted" user=Perry token=REDACTED_TOKEN
+}
diff --git a/slog/example_test.go b/slog/example_test.go
new file mode 100644
index 0000000..ad5d000
--- /dev/null
+++ b/slog/example_test.go
@@ -0,0 +1,32 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "net/http"
+ "os"
+ "time"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/internal/testutil"
+)
+
+func ExampleGroup() {
+ r, _ := http.NewRequest("GET", "localhost", nil)
+ // ...
+
+ logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}))
+ slog.SetDefault(logger)
+
+ slog.Info("finished",
+ slog.Group("req",
+ slog.String("method", r.Method),
+ slog.String("url", r.URL.String())),
+ slog.Int("status", http.StatusOK),
+ slog.Duration("duration", time.Second))
+
+ // Output:
+ // level=INFO msg=finished req.method=GET req.url=localhost status=200 duration=1s
+}
diff --git a/slog/example_wrap_test.go b/slog/example_wrap_test.go
new file mode 100644
index 0000000..6feceef
--- /dev/null
+++ b/slog/example_wrap_test.go
@@ -0,0 +1,48 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "time"
+
+ "golang.org/x/exp/slog"
+)
+
+// Infof is an example of a user-defined logging function that wraps slog.
+// The log record contains the source position of the caller of Infof.
+func Infof(logger *slog.Logger, format string, args ...any) {
+ if !logger.Enabled(context.Background(), slog.LevelInfo) {
+ return
+ }
+ var pcs [1]uintptr
+ runtime.Callers(2, pcs[:]) // skip [Callers, Infof]
+ r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf(format, args...), pcs[0])
+ _ = logger.Handler().Handle(context.Background(), r)
+}
+
+func Example_wrapping() {
+ replace := func(groups []string, a slog.Attr) slog.Attr {
+ // Remove time.
+ if a.Key == slog.TimeKey && len(groups) == 0 {
+ return slog.Attr{}
+ }
+ // Remove the directory from the source's filename.
+ if a.Key == slog.SourceKey {
+ source := a.Value.Any().(*slog.Source)
+ source.File = filepath.Base(source.File)
+ }
+ return a
+ }
+ logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}))
+ Infof(logger, "message, %s", "formatted")
+
+ // Output:
+ // level=INFO source=example_wrap_test.go:44 msg="message, formatted"
+}
diff --git a/slog/handler.go b/slog/handler.go
new file mode 100644
index 0000000..74f8873
--- /dev/null
+++ b/slog/handler.go
@@ -0,0 +1,559 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "strconv"
+ "sync"
+ "time"
+
+ "golang.org/x/exp/slices"
+ "golang.org/x/exp/slog/internal/buffer"
+)
+
+// A Handler handles log records produced by a Logger..
+//
+// A typical handler may print log records to standard error,
+// or write them to a file or database, or perhaps augment them
+// with additional attributes and pass them on to another handler.
+//
+// Any of the Handler's methods may be called concurrently with itself
+// or with other methods. It is the responsibility of the Handler to
+// manage this concurrency.
+//
+// Users of the slog package should not invoke Handler methods directly.
+// They should use the methods of [Logger] instead.
+type Handler interface {
+ // Enabled reports whether the handler handles records at the given level.
+ // The handler ignores records whose level is lower.
+ // It is called early, before any arguments are processed,
+ // to save effort if the log event should be discarded.
+ // If called from a Logger method, the first argument is the context
+ // passed to that method, or context.Background() if nil was passed
+ // or the method does not take a context.
+ // The context is passed so Enabled can use its values
+ // to make a decision.
+ Enabled(context.Context, Level) bool
+
+ // Handle handles the Record.
+ // It will only be called when Enabled returns true.
+ // The Context argument is as for Enabled.
+ // It is present solely to provide Handlers access to the context's values.
+ // Canceling the context should not affect record processing.
+ // (Among other things, log messages may be necessary to debug a
+ // cancellation-related problem.)
+ //
+ // Handle methods that produce output should observe the following rules:
+ // - If r.Time is the zero time, ignore the time.
+ // - If r.PC is zero, ignore it.
+ // - Attr's values should be resolved.
+ // - If an Attr's key and value are both the zero value, ignore the Attr.
+ // This can be tested with attr.Equal(Attr{}).
+ // - If a group's key is empty, inline the group's Attrs.
+ // - If a group has no Attrs (even if it has a non-empty key),
+ // ignore it.
+ Handle(context.Context, Record) error
+
+ // WithAttrs returns a new Handler whose attributes consist of
+ // both the receiver's attributes and the arguments.
+ // The Handler owns the slice: it may retain, modify or discard it.
+ WithAttrs(attrs []Attr) Handler
+
+ // WithGroup returns a new Handler with the given group appended to
+ // the receiver's existing groups.
+ // The keys of all subsequent attributes, whether added by With or in a
+ // Record, should be qualified by the sequence of group names.
+ //
+ // How this qualification happens is up to the Handler, so long as
+ // this Handler's attribute keys differ from those of another Handler
+ // with a different sequence of group names.
+ //
+ // A Handler should treat WithGroup as starting a Group of Attrs that ends
+ // at the end of the log event. That is,
+ //
+ // logger.WithGroup("s").LogAttrs(level, msg, slog.Int("a", 1), slog.Int("b", 2))
+ //
+ // should behave like
+ //
+ // logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
+ //
+ // If the name is empty, WithGroup returns the receiver.
+ WithGroup(name string) Handler
+}
+
+type defaultHandler struct {
+ ch *commonHandler
+ // log.Output, except for testing
+ output func(calldepth int, message string) error
+}
+
+func newDefaultHandler(output func(int, string) error) *defaultHandler {
+ return &defaultHandler{
+ ch: &commonHandler{json: false},
+ output: output,
+ }
+}
+
+func (*defaultHandler) Enabled(_ context.Context, l Level) bool {
+ return l >= LevelInfo
+}
+
+// Collect the level, attributes and message in a string and
+// write it with the default log.Logger.
+// Let the log.Logger handle time and file/line.
+func (h *defaultHandler) Handle(ctx context.Context, r Record) error {
+ buf := buffer.New()
+ buf.WriteString(r.Level.String())
+ buf.WriteByte(' ')
+ buf.WriteString(r.Message)
+ state := h.ch.newHandleState(buf, true, " ", nil)
+ defer state.free()
+ state.appendNonBuiltIns(r)
+
+ // skip [h.output, defaultHandler.Handle, handlerWriter.Write, log.Output]
+ return h.output(4, buf.String())
+}
+
+func (h *defaultHandler) WithAttrs(as []Attr) Handler {
+ return &defaultHandler{h.ch.withAttrs(as), h.output}
+}
+
+func (h *defaultHandler) WithGroup(name string) Handler {
+ return &defaultHandler{h.ch.withGroup(name), h.output}
+}
+
+// HandlerOptions are options for a TextHandler or JSONHandler.
+// A zero HandlerOptions consists entirely of default values.
+type HandlerOptions struct {
+ // AddSource causes the handler to compute the source code position
+ // of the log statement and add a SourceKey attribute to the output.
+ AddSource bool
+
+ // Level reports the minimum record level that will be logged.
+ // The handler discards records with lower levels.
+ // If Level is nil, the handler assumes LevelInfo.
+ // The handler calls Level.Level for each record processed;
+ // to adjust the minimum level dynamically, use a LevelVar.
+ Level Leveler
+
+ // ReplaceAttr is called to rewrite each non-group attribute before it is logged.
+ // The attribute's value has been resolved (see [Value.Resolve]).
+ // If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
+ //
+ // The built-in attributes with keys "time", "level", "source", and "msg"
+ // are passed to this function, except that time is omitted
+ // if zero, and source is omitted if AddSource is false.
+ //
+ // The first argument is a list of currently open groups that contain the
+ // Attr. It must not be retained or modified. ReplaceAttr is never called
+ // for Group attributes, only their contents. For example, the attribute
+ // list
+ //
+ // Int("a", 1), Group("g", Int("b", 2)), Int("c", 3)
+ //
+ // results in consecutive calls to ReplaceAttr with the following arguments:
+ //
+ // nil, Int("a", 1)
+ // []string{"g"}, Int("b", 2)
+ // nil, Int("c", 3)
+ //
+ // ReplaceAttr can be used to change the default keys of the built-in
+ // attributes, convert types (for example, to replace a `time.Time` with the
+ // integer seconds since the Unix epoch), sanitize personal information, or
+ // remove attributes from the output.
+ ReplaceAttr func(groups []string, a Attr) Attr
+}
+
+// Keys for "built-in" attributes.
+const (
+ // TimeKey is the key used by the built-in handlers for the time
+ // when the log method is called. The associated Value is a [time.Time].
+ TimeKey = "time"
+ // LevelKey is the key used by the built-in handlers for the level
+ // of the log call. The associated value is a [Level].
+ LevelKey = "level"
+ // MessageKey is the key used by the built-in handlers for the
+ // message of the log call. The associated value is a string.
+ MessageKey = "msg"
+ // SourceKey is the key used by the built-in handlers for the source file
+ // and line of the log call. The associated value is a string.
+ SourceKey = "source"
+)
+
+type commonHandler struct {
+ json bool // true => output JSON; false => output text
+ opts HandlerOptions
+ preformattedAttrs []byte
+ groupPrefix string // for text: prefix of groups opened in preformatting
+ groups []string // all groups started from WithGroup
+ nOpenGroups int // the number of groups opened in preformattedAttrs
+ mu sync.Mutex
+ w io.Writer
+}
+
+func (h *commonHandler) clone() *commonHandler {
+ // We can't use assignment because we can't copy the mutex.
+ return &commonHandler{
+ json: h.json,
+ opts: h.opts,
+ preformattedAttrs: slices.Clip(h.preformattedAttrs),
+ groupPrefix: h.groupPrefix,
+ groups: slices.Clip(h.groups),
+ nOpenGroups: h.nOpenGroups,
+ w: h.w,
+ }
+}
+
+// enabled reports whether l is greater than or equal to the
+// minimum level.
+func (h *commonHandler) enabled(l Level) bool {
+ minLevel := LevelInfo
+ if h.opts.Level != nil {
+ minLevel = h.opts.Level.Level()
+ }
+ return l >= minLevel
+}
+
+func (h *commonHandler) withAttrs(as []Attr) *commonHandler {
+ h2 := h.clone()
+ // Pre-format the attributes as an optimization.
+ prefix := buffer.New()
+ defer prefix.Free()
+ prefix.WriteString(h.groupPrefix)
+ state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "", prefix)
+ defer state.free()
+ if len(h2.preformattedAttrs) > 0 {
+ state.sep = h.attrSep()
+ }
+ state.openGroups()
+ for _, a := range as {
+ state.appendAttr(a)
+ }
+ // Remember the new prefix for later keys.
+ h2.groupPrefix = state.prefix.String()
+ // Remember how many opened groups are in preformattedAttrs,
+ // so we don't open them again when we handle a Record.
+ h2.nOpenGroups = len(h2.groups)
+ return h2
+}
+
+func (h *commonHandler) withGroup(name string) *commonHandler {
+ if name == "" {
+ return h
+ }
+ h2 := h.clone()
+ h2.groups = append(h2.groups, name)
+ return h2
+}
+
+func (h *commonHandler) handle(r Record) error {
+ state := h.newHandleState(buffer.New(), true, "", nil)
+ defer state.free()
+ if h.json {
+ state.buf.WriteByte('{')
+ }
+ // Built-in attributes. They are not in a group.
+ stateGroups := state.groups
+ state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups.
+ rep := h.opts.ReplaceAttr
+ // time
+ if !r.Time.IsZero() {
+ key := TimeKey
+ val := r.Time.Round(0) // strip monotonic to match Attr behavior
+ if rep == nil {
+ state.appendKey(key)
+ state.appendTime(val)
+ } else {
+ state.appendAttr(Time(key, val))
+ }
+ }
+ // level
+ key := LevelKey
+ val := r.Level
+ if rep == nil {
+ state.appendKey(key)
+ state.appendString(val.String())
+ } else {
+ state.appendAttr(Any(key, val))
+ }
+ // source
+ if h.opts.AddSource {
+ state.appendAttr(Any(SourceKey, r.source()))
+ }
+ key = MessageKey
+ msg := r.Message
+ if rep == nil {
+ state.appendKey(key)
+ state.appendString(msg)
+ } else {
+ state.appendAttr(String(key, msg))
+ }
+ state.groups = stateGroups // Restore groups passed to ReplaceAttrs.
+ state.appendNonBuiltIns(r)
+ state.buf.WriteByte('\n')
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ _, err := h.w.Write(*state.buf)
+ return err
+}
+
+func (s *handleState) appendNonBuiltIns(r Record) {
+ // preformatted Attrs
+ if len(s.h.preformattedAttrs) > 0 {
+ s.buf.WriteString(s.sep)
+ s.buf.Write(s.h.preformattedAttrs)
+ s.sep = s.h.attrSep()
+ }
+ // Attrs in Record -- unlike the built-in ones, they are in groups started
+ // from WithGroup.
+ s.prefix = buffer.New()
+ defer s.prefix.Free()
+ s.prefix.WriteString(s.h.groupPrefix)
+ s.openGroups()
+ r.Attrs(func(a Attr) bool {
+ s.appendAttr(a)
+ return true
+ })
+ if s.h.json {
+ // Close all open groups.
+ for range s.h.groups {
+ s.buf.WriteByte('}')
+ }
+ // Close the top-level object.
+ s.buf.WriteByte('}')
+ }
+}
+
+// attrSep returns the separator between attributes.
+func (h *commonHandler) attrSep() string {
+ if h.json {
+ return ","
+ }
+ return " "
+}
+
+// handleState holds state for a single call to commonHandler.handle.
+// The initial value of sep determines whether to emit a separator
+// before the next key, after which it stays true.
+type handleState struct {
+ h *commonHandler
+ buf *buffer.Buffer
+ freeBuf bool // should buf be freed?
+ sep string // separator to write before next key
+ prefix *buffer.Buffer // for text: key prefix
+ groups *[]string // pool-allocated slice of active groups, for ReplaceAttr
+}
+
+var groupPool = sync.Pool{New: func() any {
+ s := make([]string, 0, 10)
+ return &s
+}}
+
+func (h *commonHandler) newHandleState(buf *buffer.Buffer, freeBuf bool, sep string, prefix *buffer.Buffer) handleState {
+ s := handleState{
+ h: h,
+ buf: buf,
+ freeBuf: freeBuf,
+ sep: sep,
+ prefix: prefix,
+ }
+ if h.opts.ReplaceAttr != nil {
+ s.groups = groupPool.Get().(*[]string)
+ *s.groups = append(*s.groups, h.groups[:h.nOpenGroups]...)
+ }
+ return s
+}
+
+func (s *handleState) free() {
+ if s.freeBuf {
+ s.buf.Free()
+ }
+ if gs := s.groups; gs != nil {
+ *gs = (*gs)[:0]
+ groupPool.Put(gs)
+ }
+}
+
+func (s *handleState) openGroups() {
+ for _, n := range s.h.groups[s.h.nOpenGroups:] {
+ s.openGroup(n)
+ }
+}
+
+// Separator for group names and keys.
+const keyComponentSep = '.'
+
+// openGroup starts a new group of attributes
+// with the given name.
+func (s *handleState) openGroup(name string) {
+ if s.h.json {
+ s.appendKey(name)
+ s.buf.WriteByte('{')
+ s.sep = ""
+ } else {
+ s.prefix.WriteString(name)
+ s.prefix.WriteByte(keyComponentSep)
+ }
+ // Collect group names for ReplaceAttr.
+ if s.groups != nil {
+ *s.groups = append(*s.groups, name)
+ }
+}
+
+// closeGroup ends the group with the given name.
+func (s *handleState) closeGroup(name string) {
+ if s.h.json {
+ s.buf.WriteByte('}')
+ } else {
+ (*s.prefix) = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */]
+ }
+ s.sep = s.h.attrSep()
+ if s.groups != nil {
+ *s.groups = (*s.groups)[:len(*s.groups)-1]
+ }
+}
+
+// appendAttr appends the Attr's key and value using app.
+// It handles replacement and checking for an empty key.
+// after replacement).
+func (s *handleState) appendAttr(a Attr) {
+ if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup {
+ var gs []string
+ if s.groups != nil {
+ gs = *s.groups
+ }
+ // Resolve before calling ReplaceAttr, so the user doesn't have to.
+ a.Value = a.Value.Resolve()
+ a = rep(gs, a)
+ }
+ a.Value = a.Value.Resolve()
+ // Elide empty Attrs.
+ if a.isEmpty() {
+ return
+ }
+ // Special case: Source.
+ if v := a.Value; v.Kind() == KindAny {
+ if src, ok := v.Any().(*Source); ok {
+ if s.h.json {
+ a.Value = src.group()
+ } else {
+ a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line))
+ }
+ }
+ }
+ if a.Value.Kind() == KindGroup {
+ attrs := a.Value.Group()
+ // Output only non-empty groups.
+ if len(attrs) > 0 {
+ // Inline a group with an empty key.
+ if a.Key != "" {
+ s.openGroup(a.Key)
+ }
+ for _, aa := range attrs {
+ s.appendAttr(aa)
+ }
+ if a.Key != "" {
+ s.closeGroup(a.Key)
+ }
+ }
+ } else {
+ s.appendKey(a.Key)
+ s.appendValue(a.Value)
+ }
+}
+
+func (s *handleState) appendError(err error) {
+ s.appendString(fmt.Sprintf("!ERROR:%v", err))
+}
+
+func (s *handleState) appendKey(key string) {
+ s.buf.WriteString(s.sep)
+ if s.prefix != nil {
+ // TODO: optimize by avoiding allocation.
+ s.appendString(string(*s.prefix) + key)
+ } else {
+ s.appendString(key)
+ }
+ if s.h.json {
+ s.buf.WriteByte(':')
+ } else {
+ s.buf.WriteByte('=')
+ }
+ s.sep = s.h.attrSep()
+}
+
+func (s *handleState) appendString(str string) {
+ if s.h.json {
+ s.buf.WriteByte('"')
+ *s.buf = appendEscapedJSONString(*s.buf, str)
+ s.buf.WriteByte('"')
+ } else {
+ // text
+ if needsQuoting(str) {
+ *s.buf = strconv.AppendQuote(*s.buf, str)
+ } else {
+ s.buf.WriteString(str)
+ }
+ }
+}
+
+func (s *handleState) appendValue(v Value) {
+ var err error
+ if s.h.json {
+ err = appendJSONValue(s, v)
+ } else {
+ err = appendTextValue(s, v)
+ }
+ if err != nil {
+ s.appendError(err)
+ }
+}
+
+func (s *handleState) appendTime(t time.Time) {
+ if s.h.json {
+ appendJSONTime(s, t)
+ } else {
+ writeTimeRFC3339Millis(s.buf, t)
+ }
+}
+
+// This takes half the time of Time.AppendFormat.
+func writeTimeRFC3339Millis(buf *buffer.Buffer, t time.Time) {
+ year, month, day := t.Date()
+ buf.WritePosIntWidth(year, 4)
+ buf.WriteByte('-')
+ buf.WritePosIntWidth(int(month), 2)
+ buf.WriteByte('-')
+ buf.WritePosIntWidth(day, 2)
+ buf.WriteByte('T')
+ hour, min, sec := t.Clock()
+ buf.WritePosIntWidth(hour, 2)
+ buf.WriteByte(':')
+ buf.WritePosIntWidth(min, 2)
+ buf.WriteByte(':')
+ buf.WritePosIntWidth(sec, 2)
+ ns := t.Nanosecond()
+ buf.WriteByte('.')
+ buf.WritePosIntWidth(ns/1e6, 3)
+ _, offsetSeconds := t.Zone()
+ if offsetSeconds == 0 {
+ buf.WriteByte('Z')
+ } else {
+ offsetMinutes := offsetSeconds / 60
+ if offsetMinutes < 0 {
+ buf.WriteByte('-')
+ offsetMinutes = -offsetMinutes
+ } else {
+ buf.WriteByte('+')
+ }
+ buf.WritePosIntWidth(offsetMinutes/60, 2)
+ buf.WriteByte(':')
+ buf.WritePosIntWidth(offsetMinutes%60, 2)
+ }
+}
diff --git a/slog/handler_test.go b/slog/handler_test.go
new file mode 100644
index 0000000..4b2f085
--- /dev/null
+++ b/slog/handler_test.go
@@ -0,0 +1,517 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// TODO: verify that the output of Marshal{Text,JSON} is suitably escaped.
+
+package slog
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "io"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/slices"
+ "golang.org/x/exp/slog/internal/buffer"
+)
+
+func TestDefaultHandle(t *testing.T) {
+ ctx := context.Background()
+ preAttrs := []Attr{Int("pre", 0)}
+ attrs := []Attr{Int("a", 1), String("b", "two")}
+ for _, test := range []struct {
+ name string
+ with func(Handler) Handler
+ attrs []Attr
+ want string
+ }{
+ {
+ name: "no attrs",
+ want: "INFO message",
+ },
+ {
+ name: "attrs",
+ attrs: attrs,
+ want: "INFO message a=1 b=two",
+ },
+ {
+ name: "preformatted",
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
+ attrs: attrs,
+ want: "INFO message pre=0 a=1 b=two",
+ },
+ {
+ name: "groups",
+ attrs: []Attr{
+ Int("a", 1),
+ Group("g",
+ Int("b", 2),
+ Group("h", Int("c", 3)),
+ Int("d", 4)),
+ Int("e", 5),
+ },
+ want: "INFO message a=1 g.b=2 g.h.c=3 g.d=4 e=5",
+ },
+ {
+ name: "group",
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("s") },
+ attrs: attrs,
+ want: "INFO message pre=0 s.a=1 s.b=two",
+ },
+ {
+ name: "preformatted groups",
+ with: func(h Handler) Handler {
+ return h.WithAttrs([]Attr{Int("p1", 1)}).
+ WithGroup("s1").
+ WithAttrs([]Attr{Int("p2", 2)}).
+ WithGroup("s2")
+ },
+ attrs: attrs,
+ want: "INFO message p1=1 s1.p2=2 s1.s2.a=1 s1.s2.b=two",
+ },
+ {
+ name: "two with-groups",
+ with: func(h Handler) Handler {
+ return h.WithAttrs([]Attr{Int("p1", 1)}).
+ WithGroup("s1").
+ WithGroup("s2")
+ },
+ attrs: attrs,
+ want: "INFO message p1=1 s1.s2.a=1 s1.s2.b=two",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ var got string
+ var h Handler = newDefaultHandler(func(_ int, s string) error {
+ got = s
+ return nil
+ })
+ if test.with != nil {
+ h = test.with(h)
+ }
+ r := NewRecord(time.Time{}, LevelInfo, "message", 0)
+ r.AddAttrs(test.attrs...)
+ if err := h.Handle(ctx, r); err != nil {
+ t.Fatal(err)
+ }
+ if got != test.want {
+ t.Errorf("\ngot %s\nwant %s", got, test.want)
+ }
+ })
+ }
+}
+
+// Verify the common parts of TextHandler and JSONHandler.
+func TestJSONAndTextHandlers(t *testing.T) {
+ ctx := context.Background()
+
+ // remove all Attrs
+ removeAll := func(_ []string, a Attr) Attr { return Attr{} }
+
+ attrs := []Attr{String("a", "one"), Int("b", 2), Any("", nil)}
+ preAttrs := []Attr{Int("pre", 3), String("x", "y")}
+
+ for _, test := range []struct {
+ name string
+ replace func([]string, Attr) Attr
+ addSource bool
+ with func(Handler) Handler
+ preAttrs []Attr
+ attrs []Attr
+ wantText string
+ wantJSON string
+ }{
+ {
+ name: "basic",
+ attrs: attrs,
+ wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2",
+ wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2}`,
+ },
+ {
+ name: "empty key",
+ attrs: append(slices.Clip(attrs), Any("", "v")),
+ wantText: `time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2 ""=v`,
+ wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2,"":"v"}`,
+ },
+ {
+ name: "cap keys",
+ replace: upperCaseKey,
+ attrs: attrs,
+ wantText: "TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG=message A=one B=2",
+ wantJSON: `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"message","A":"one","B":2}`,
+ },
+ {
+ name: "remove all",
+ replace: removeAll,
+ attrs: attrs,
+ wantText: "",
+ wantJSON: `{}`,
+ },
+ {
+ name: "preformatted",
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
+ preAttrs: preAttrs,
+ attrs: attrs,
+ wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message pre=3 x=y a=one b=2",
+ wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","pre":3,"x":"y","a":"one","b":2}`,
+ },
+ {
+ name: "preformatted cap keys",
+ replace: upperCaseKey,
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
+ preAttrs: preAttrs,
+ attrs: attrs,
+ wantText: "TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG=message PRE=3 X=y A=one B=2",
+ wantJSON: `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"message","PRE":3,"X":"y","A":"one","B":2}`,
+ },
+ {
+ name: "preformatted remove all",
+ replace: removeAll,
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
+ preAttrs: preAttrs,
+ attrs: attrs,
+ wantText: "",
+ wantJSON: "{}",
+ },
+ {
+ name: "remove built-in",
+ replace: removeKeys(TimeKey, LevelKey, MessageKey),
+ attrs: attrs,
+ wantText: "a=one b=2",
+ wantJSON: `{"a":"one","b":2}`,
+ },
+ {
+ name: "preformatted remove built-in",
+ replace: removeKeys(TimeKey, LevelKey, MessageKey),
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
+ attrs: attrs,
+ wantText: "pre=3 x=y a=one b=2",
+ wantJSON: `{"pre":3,"x":"y","a":"one","b":2}`,
+ },
+ {
+ name: "groups",
+ replace: removeKeys(TimeKey, LevelKey), // to simplify the result
+ attrs: []Attr{
+ Int("a", 1),
+ Group("g",
+ Int("b", 2),
+ Group("h", Int("c", 3)),
+ Int("d", 4)),
+ Int("e", 5),
+ },
+ wantText: "msg=message a=1 g.b=2 g.h.c=3 g.d=4 e=5",
+ wantJSON: `{"msg":"message","a":1,"g":{"b":2,"h":{"c":3},"d":4},"e":5}`,
+ },
+ {
+ name: "empty group",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{Group("g"), Group("h", Int("a", 1))},
+ wantText: "msg=message h.a=1",
+ wantJSON: `{"msg":"message","h":{"a":1}}`,
+ },
+ {
+ name: "escapes",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{
+ String("a b", "x\t\n\000y"),
+ Group(" b.c=\"\\x2E\t",
+ String("d=e", "f.g\""),
+ Int("m.d", 1)), // dot is not escaped
+ },
+ wantText: `msg=message "a b"="x\t\n\x00y" " b.c=\"\\x2E\t.d=e"="f.g\"" " b.c=\"\\x2E\t.m.d"=1`,
+ wantJSON: `{"msg":"message","a b":"x\t\n\u0000y"," b.c=\"\\x2E\t":{"d=e":"f.g\"","m.d":1}}`,
+ },
+ {
+ name: "LogValuer",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{
+ Int("a", 1),
+ Any("name", logValueName{"Ren", "Hoek"}),
+ Int("b", 2),
+ },
+ wantText: "msg=message a=1 name.first=Ren name.last=Hoek b=2",
+ wantJSON: `{"msg":"message","a":1,"name":{"first":"Ren","last":"Hoek"},"b":2}`,
+ },
+ {
+ // Test resolution when there is no ReplaceAttr function.
+ name: "resolve",
+ attrs: []Attr{
+ Any("", &replace{Value{}}), // should be elided
+ Any("name", logValueName{"Ren", "Hoek"}),
+ },
+ wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message name.first=Ren name.last=Hoek",
+ wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","name":{"first":"Ren","last":"Hoek"}}`,
+ },
+ {
+ name: "with-group",
+ replace: removeKeys(TimeKey, LevelKey),
+ with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("s") },
+ attrs: attrs,
+ wantText: "msg=message pre=3 x=y s.a=one s.b=2",
+ wantJSON: `{"msg":"message","pre":3,"x":"y","s":{"a":"one","b":2}}`,
+ },
+ {
+ name: "preformatted with-groups",
+ replace: removeKeys(TimeKey, LevelKey),
+ with: func(h Handler) Handler {
+ return h.WithAttrs([]Attr{Int("p1", 1)}).
+ WithGroup("s1").
+ WithAttrs([]Attr{Int("p2", 2)}).
+ WithGroup("s2")
+ },
+ attrs: attrs,
+ wantText: "msg=message p1=1 s1.p2=2 s1.s2.a=one s1.s2.b=2",
+ wantJSON: `{"msg":"message","p1":1,"s1":{"p2":2,"s2":{"a":"one","b":2}}}`,
+ },
+ {
+ name: "two with-groups",
+ replace: removeKeys(TimeKey, LevelKey),
+ with: func(h Handler) Handler {
+ return h.WithAttrs([]Attr{Int("p1", 1)}).
+ WithGroup("s1").
+ WithGroup("s2")
+ },
+ attrs: attrs,
+ wantText: "msg=message p1=1 s1.s2.a=one s1.s2.b=2",
+ wantJSON: `{"msg":"message","p1":1,"s1":{"s2":{"a":"one","b":2}}}`,
+ },
+ {
+ name: "GroupValue as Attr value",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{{"v", AnyValue(IntValue(3))}},
+ wantText: "msg=message v=3",
+ wantJSON: `{"msg":"message","v":3}`,
+ },
+ {
+ name: "byte slice",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{Any("bs", []byte{1, 2, 3, 4})},
+ wantText: `msg=message bs="\x01\x02\x03\x04"`,
+ wantJSON: `{"msg":"message","bs":"AQIDBA=="}`,
+ },
+ {
+ name: "json.RawMessage",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{Any("bs", json.RawMessage([]byte("1234")))},
+ wantText: `msg=message bs="1234"`,
+ wantJSON: `{"msg":"message","bs":1234}`,
+ },
+ {
+ name: "inline group",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{
+ Int("a", 1),
+ Group("", Int("b", 2), Int("c", 3)),
+ Int("d", 4),
+ },
+ wantText: `msg=message a=1 b=2 c=3 d=4`,
+ wantJSON: `{"msg":"message","a":1,"b":2,"c":3,"d":4}`,
+ },
+ {
+ name: "Source",
+ replace: func(gs []string, a Attr) Attr {
+ if a.Key == SourceKey {
+ s := a.Value.Any().(*Source)
+ s.File = filepath.Base(s.File)
+ return Any(a.Key, s)
+ }
+ return removeKeys(TimeKey, LevelKey)(gs, a)
+ },
+ addSource: true,
+ wantText: `source=handler_test.go:$LINE msg=message`,
+ wantJSON: `{"source":{"function":"golang.org/x/exp/slog.TestJSONAndTextHandlers","file":"handler_test.go","line":$LINE},"msg":"message"}`,
+ },
+ } {
+ r := NewRecord(testTime, LevelInfo, "message", callerPC(2))
+ line := strconv.Itoa(r.source().Line)
+ r.AddAttrs(test.attrs...)
+ var buf bytes.Buffer
+ opts := HandlerOptions{ReplaceAttr: test.replace, AddSource: test.addSource}
+ t.Run(test.name, func(t *testing.T) {
+ for _, handler := range []struct {
+ name string
+ h Handler
+ want string
+ }{
+ {"text", NewTextHandler(&buf, &opts), test.wantText},
+ {"json", NewJSONHandler(&buf, &opts), test.wantJSON},
+ } {
+ t.Run(handler.name, func(t *testing.T) {
+ h := handler.h
+ if test.with != nil {
+ h = test.with(h)
+ }
+ buf.Reset()
+ if err := h.Handle(ctx, r); err != nil {
+ t.Fatal(err)
+ }
+ want := strings.ReplaceAll(handler.want, "$LINE", line)
+ got := strings.TrimSuffix(buf.String(), "\n")
+ if got != want {
+ t.Errorf("\ngot %s\nwant %s\n", got, want)
+ }
+ })
+ }
+ })
+ }
+}
+
+// removeKeys returns a function suitable for HandlerOptions.ReplaceAttr
+// that removes all Attrs with the given keys.
+func removeKeys(keys ...string) func([]string, Attr) Attr {
+ return func(_ []string, a Attr) Attr {
+ for _, k := range keys {
+ if a.Key == k {
+ return Attr{}
+ }
+ }
+ return a
+ }
+}
+
+func upperCaseKey(_ []string, a Attr) Attr {
+ a.Key = strings.ToUpper(a.Key)
+ return a
+}
+
+type logValueName struct {
+ first, last string
+}
+
+func (n logValueName) LogValue() Value {
+ return GroupValue(
+ String("first", n.first),
+ String("last", n.last))
+}
+
+func TestHandlerEnabled(t *testing.T) {
+ levelVar := func(l Level) *LevelVar {
+ var al LevelVar
+ al.Set(l)
+ return &al
+ }
+
+ for _, test := range []struct {
+ leveler Leveler
+ want bool
+ }{
+ {nil, true},
+ {LevelWarn, false},
+ {&LevelVar{}, true}, // defaults to Info
+ {levelVar(LevelWarn), false},
+ {LevelDebug, true},
+ {levelVar(LevelDebug), true},
+ } {
+ h := &commonHandler{opts: HandlerOptions{Level: test.leveler}}
+ got := h.enabled(LevelInfo)
+ if got != test.want {
+ t.Errorf("%v: got %t, want %t", test.leveler, got, test.want)
+ }
+ }
+}
+
+func TestSecondWith(t *testing.T) {
+ // Verify that a second call to Logger.With does not corrupt
+ // the original.
+ var buf bytes.Buffer
+ h := NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeKeys(TimeKey)})
+ logger := New(h).With(
+ String("app", "playground"),
+ String("role", "tester"),
+ Int("data_version", 2),
+ )
+ appLogger := logger.With("type", "log") // this becomes type=met
+ _ = logger.With("type", "metric")
+ appLogger.Info("foo")
+ got := strings.TrimSpace(buf.String())
+ want := `level=INFO msg=foo app=playground role=tester data_version=2 type=log`
+ if got != want {
+ t.Errorf("\ngot %s\nwant %s", got, want)
+ }
+}
+
+func TestReplaceAttrGroups(t *testing.T) {
+ // Verify that ReplaceAttr is called with the correct groups.
+ type ga struct {
+ groups string
+ key string
+ val string
+ }
+
+ var got []ga
+
+ h := NewTextHandler(io.Discard, &HandlerOptions{ReplaceAttr: func(gs []string, a Attr) Attr {
+ v := a.Value.String()
+ if a.Key == TimeKey {
+ v = "<now>"
+ }
+ got = append(got, ga{strings.Join(gs, ","), a.Key, v})
+ return a
+ }})
+ New(h).
+ With(Int("a", 1)).
+ WithGroup("g1").
+ With(Int("b", 2)).
+ WithGroup("g2").
+ With(
+ Int("c", 3),
+ Group("g3", Int("d", 4)),
+ Int("e", 5)).
+ Info("m",
+ Int("f", 6),
+ Group("g4", Int("h", 7)),
+ Int("i", 8))
+
+ want := []ga{
+ {"", "a", "1"},
+ {"g1", "b", "2"},
+ {"g1,g2", "c", "3"},
+ {"g1,g2,g3", "d", "4"},
+ {"g1,g2", "e", "5"},
+ {"", "time", "<now>"},
+ {"", "level", "INFO"},
+ {"", "msg", "m"},
+ {"g1,g2", "f", "6"},
+ {"g1,g2,g4", "h", "7"},
+ {"g1,g2", "i", "8"},
+ }
+ if !slices.Equal(got, want) {
+ t.Errorf("\ngot %v\nwant %v", got, want)
+ }
+}
+
+const rfc3339Millis = "2006-01-02T15:04:05.000Z07:00"
+
+func TestWriteTimeRFC3339(t *testing.T) {
+ for _, tm := range []time.Time{
+ time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC),
+ time.Date(2000, 1, 2, 3, 4, 5, 400, time.Local),
+ time.Date(2000, 11, 12, 3, 4, 500, 5e7, time.UTC),
+ } {
+ want := tm.Format(rfc3339Millis)
+ buf := buffer.New()
+ defer buf.Free()
+ writeTimeRFC3339Millis(buf, tm)
+ got := buf.String()
+ if got != want {
+ t.Errorf("got %s, want %s", got, want)
+ }
+ }
+}
+
+func BenchmarkWriteTime(b *testing.B) {
+ buf := buffer.New()
+ defer buf.Free()
+ tm := time.Date(2022, 3, 4, 5, 6, 7, 823456789, time.Local)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ writeTimeRFC3339Millis(buf, tm)
+ buf.Reset()
+ }
+}
diff --git a/slog/internal/buffer/buffer.go b/slog/internal/buffer/buffer.go
new file mode 100644
index 0000000..7786c16
--- /dev/null
+++ b/slog/internal/buffer/buffer.go
@@ -0,0 +1,84 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package buffer provides a pool-allocated byte buffer.
+package buffer
+
+import (
+ "sync"
+)
+
+// Buffer adapted from go/src/fmt/print.go
+type Buffer []byte
+
+// Having an initial size gives a dramatic speedup.
+var bufPool = sync.Pool{
+ New: func() any {
+ b := make([]byte, 0, 1024)
+ return (*Buffer)(&b)
+ },
+}
+
+func New() *Buffer {
+ return bufPool.Get().(*Buffer)
+}
+
+func (b *Buffer) Free() {
+ // To reduce peak allocation, return only smaller buffers to the pool.
+ const maxBufferSize = 16 << 10
+ if cap(*b) <= maxBufferSize {
+ *b = (*b)[:0]
+ bufPool.Put(b)
+ }
+}
+
+func (b *Buffer) Reset() {
+ *b = (*b)[:0]
+}
+
+func (b *Buffer) Write(p []byte) (int, error) {
+ *b = append(*b, p...)
+ return len(p), nil
+}
+
+func (b *Buffer) WriteString(s string) {
+ *b = append(*b, s...)
+}
+
+func (b *Buffer) WriteByte(c byte) {
+ *b = append(*b, c)
+}
+
+func (b *Buffer) WritePosInt(i int) {
+ b.WritePosIntWidth(i, 0)
+}
+
+// WritePosIntWidth writes non-negative integer i to the buffer, padded on the left
+// by zeroes to the given width. Use a width of 0 to omit padding.
+func (b *Buffer) WritePosIntWidth(i, width int) {
+ // Cheap integer to fixed-width decimal ASCII.
+ // Copied from log/log.go.
+
+ if i < 0 {
+ panic("negative int")
+ }
+
+ // Assemble decimal in reverse order.
+ var bb [20]byte
+ bp := len(bb) - 1
+ for i >= 10 || width > 1 {
+ width--
+ q := i / 10
+ bb[bp] = byte('0' + i - q*10)
+ bp--
+ i = q
+ }
+ // i < 10
+ bb[bp] = byte('0' + i)
+ b.Write(bb[bp:])
+}
+
+func (b *Buffer) String() string {
+ return string(*b)
+}
diff --git a/slog/internal/buffer/buffer_test.go b/slog/internal/buffer/buffer_test.go
new file mode 100644
index 0000000..323d411
--- /dev/null
+++ b/slog/internal/buffer/buffer_test.go
@@ -0,0 +1,22 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package buffer
+
+import "testing"
+
+func Test(t *testing.T) {
+ b := New()
+ defer b.Free()
+ b.WriteString("hello")
+ b.WriteByte(',')
+ b.Write([]byte(" world"))
+ b.WritePosIntWidth(17, 4)
+
+ got := b.String()
+ want := "hello, world0017"
+ if got != want {
+ t.Errorf("got %q, want %q", got, want)
+ }
+}
diff --git a/slog/internal/buffer/norace_test.go b/slog/internal/buffer/norace_test.go
new file mode 100644
index 0000000..c73ef8a
--- /dev/null
+++ b/slog/internal/buffer/norace_test.go
@@ -0,0 +1,20 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !race
+
+package buffer
+
+import "testing"
+
+func TestAlloc(t *testing.T) {
+ got := int(testing.AllocsPerRun(5, func() {
+ b := New()
+ defer b.Free()
+ b.WriteString("not 1K worth of bytes")
+ }))
+ if got != 0 {
+ t.Errorf("got %d allocs, want 0", got)
+ }
+}
diff --git a/slog/internal/ignorepc.go b/slog/internal/ignorepc.go
new file mode 100644
index 0000000..d125642
--- /dev/null
+++ b/slog/internal/ignorepc.go
@@ -0,0 +1,9 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// If IgnorePC is true, do not invoke runtime.Callers to get the pc.
+// This is solely for benchmarking the slowdown from runtime.Callers.
+var IgnorePC = false
diff --git a/slog/internal/testutil/testutil.go b/slog/internal/testutil/testutil.go
new file mode 100644
index 0000000..540fefb
--- /dev/null
+++ b/slog/internal/testutil/testutil.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package testutil contains support functions for testing.
+package testutil
+
+import "golang.org/x/exp/slog"
+
+// RemoveTime removes the top-level time attribute.
+// It is intended to be used as a ReplaceAttr function,
+// to make example output deterministic.
+func RemoveTime(groups []string, a slog.Attr) slog.Attr {
+ if a.Key == slog.TimeKey && len(groups) == 0 {
+ return slog.Attr{}
+ }
+ return a
+}
diff --git a/slog/json_handler.go b/slog/json_handler.go
new file mode 100644
index 0000000..157ada8
--- /dev/null
+++ b/slog/json_handler.go
@@ -0,0 +1,336 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+ "time"
+ "unicode/utf8"
+
+ "golang.org/x/exp/slog/internal/buffer"
+)
+
+// JSONHandler is a Handler that writes Records to an io.Writer as
+// line-delimited JSON objects.
+type JSONHandler struct {
+ *commonHandler
+}
+
+// NewJSONHandler creates a JSONHandler that writes to w,
+// using the given options.
+// If opts is nil, the default options are used.
+func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
+ if opts == nil {
+ opts = &HandlerOptions{}
+ }
+ return &JSONHandler{
+ &commonHandler{
+ json: true,
+ w: w,
+ opts: *opts,
+ },
+ }
+}
+
+// Enabled reports whether the handler handles records at the given level.
+// The handler ignores records whose level is lower.
+func (h *JSONHandler) Enabled(_ context.Context, level Level) bool {
+ return h.commonHandler.enabled(level)
+}
+
+// WithAttrs returns a new JSONHandler whose attributes consists
+// of h's attributes followed by attrs.
+func (h *JSONHandler) WithAttrs(attrs []Attr) Handler {
+ return &JSONHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
+}
+
+func (h *JSONHandler) WithGroup(name string) Handler {
+ return &JSONHandler{commonHandler: h.commonHandler.withGroup(name)}
+}
+
+// Handle formats its argument Record as a JSON object on a single line.
+//
+// If the Record's time is zero, the time is omitted.
+// Otherwise, the key is "time"
+// and the value is output as with json.Marshal.
+//
+// If the Record's level is zero, the level is omitted.
+// Otherwise, the key is "level"
+// and the value of [Level.String] is output.
+//
+// If the AddSource option is set and source information is available,
+// the key is "source"
+// and the value is output as "FILE:LINE".
+//
+// The message's key is "msg".
+//
+// To modify these or other attributes, or remove them from the output, use
+// [HandlerOptions.ReplaceAttr].
+//
+// Values are formatted as with an [encoding/json.Encoder] with SetEscapeHTML(false),
+// with two exceptions.
+//
+// First, an Attr whose Value is of type error is formatted as a string, by
+// calling its Error method. Only errors in Attrs receive this special treatment,
+// not errors embedded in structs, slices, maps or other data structures that
+// are processed by the encoding/json package.
+//
+// Second, an encoding failure does not cause Handle to return an error.
+// Instead, the error message is formatted as a string.
+//
+// Each call to Handle results in a single serialized call to io.Writer.Write.
+func (h *JSONHandler) Handle(_ context.Context, r Record) error {
+ return h.commonHandler.handle(r)
+}
+
+// Adapted from time.Time.MarshalJSON to avoid allocation.
+func appendJSONTime(s *handleState, t time.Time) {
+ if y := t.Year(); y < 0 || y >= 10000 {
+ // RFC 3339 is clear that years are 4 digits exactly.
+ // See golang.org/issue/4556#c15 for more discussion.
+ s.appendError(errors.New("time.Time year outside of range [0,9999]"))
+ }
+ s.buf.WriteByte('"')
+ *s.buf = t.AppendFormat(*s.buf, time.RFC3339Nano)
+ s.buf.WriteByte('"')
+}
+
+func appendJSONValue(s *handleState, v Value) error {
+ switch v.Kind() {
+ case KindString:
+ s.appendString(v.str())
+ case KindInt64:
+ *s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10)
+ case KindUint64:
+ *s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10)
+ case KindFloat64:
+ // json.Marshal is funny about floats; it doesn't
+ // always match strconv.AppendFloat. So just call it.
+ // That's expensive, but floats are rare.
+ if err := appendJSONMarshal(s.buf, v.Float64()); err != nil {
+ return err
+ }
+ case KindBool:
+ *s.buf = strconv.AppendBool(*s.buf, v.Bool())
+ case KindDuration:
+ // Do what json.Marshal does.
+ *s.buf = strconv.AppendInt(*s.buf, int64(v.Duration()), 10)
+ case KindTime:
+ s.appendTime(v.Time())
+ case KindAny:
+ a := v.Any()
+ _, jm := a.(json.Marshaler)
+ if err, ok := a.(error); ok && !jm {
+ s.appendString(err.Error())
+ } else {
+ return appendJSONMarshal(s.buf, a)
+ }
+ default:
+ panic(fmt.Sprintf("bad kind: %s", v.Kind()))
+ }
+ return nil
+}
+
+func appendJSONMarshal(buf *buffer.Buffer, v any) error {
+ // Use a json.Encoder to avoid escaping HTML.
+ var bb bytes.Buffer
+ enc := json.NewEncoder(&bb)
+ enc.SetEscapeHTML(false)
+ if err := enc.Encode(v); err != nil {
+ return err
+ }
+ bs := bb.Bytes()
+ buf.Write(bs[:len(bs)-1]) // remove final newline
+ return nil
+}
+
+// appendEscapedJSONString escapes s for JSON and appends it to buf.
+// It does not surround the string in quotation marks.
+//
+// Modified from encoding/json/encode.go:encodeState.string,
+// with escapeHTML set to false.
+func appendEscapedJSONString(buf []byte, s string) []byte {
+ char := func(b byte) { buf = append(buf, b) }
+ str := func(s string) { buf = append(buf, s...) }
+
+ start := 0
+ for i := 0; i < len(s); {
+ if b := s[i]; b < utf8.RuneSelf {
+ if safeSet[b] {
+ i++
+ continue
+ }
+ if start < i {
+ str(s[start:i])
+ }
+ char('\\')
+ switch b {
+ case '\\', '"':
+ char(b)
+ case '\n':
+ char('n')
+ case '\r':
+ char('r')
+ case '\t':
+ char('t')
+ default:
+ // This encodes bytes < 0x20 except for \t, \n and \r.
+ str(`u00`)
+ char(hex[b>>4])
+ char(hex[b&0xF])
+ }
+ i++
+ start = i
+ continue
+ }
+ c, size := utf8.DecodeRuneInString(s[i:])
+ if c == utf8.RuneError && size == 1 {
+ if start < i {
+ str(s[start:i])
+ }
+ str(`\ufffd`)
+ i += size
+ start = i
+ continue
+ }
+ // U+2028 is LINE SEPARATOR.
+ // U+2029 is PARAGRAPH SEPARATOR.
+ // They are both technically valid characters in JSON strings,
+ // but don't work in JSONP, which has to be evaluated as JavaScript,
+ // and can lead to security holes there. It is valid JSON to
+ // escape them, so we do so unconditionally.
+ // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
+ if c == '\u2028' || c == '\u2029' {
+ if start < i {
+ str(s[start:i])
+ }
+ str(`\u202`)
+ char(hex[c&0xF])
+ i += size
+ start = i
+ continue
+ }
+ i += size
+ }
+ if start < len(s) {
+ str(s[start:])
+ }
+ return buf
+}
+
+var hex = "0123456789abcdef"
+
+// Copied from encoding/json/tables.go.
+//
+// safeSet holds the value true if the ASCII character with the given array
+// position can be represented inside a JSON string without any further
+// escaping.
+//
+// All values are true except for the ASCII control characters (0-31), the
+// double quote ("), and the backslash character ("\").
+var safeSet = [utf8.RuneSelf]bool{
+ ' ': true,
+ '!': true,
+ '"': false,
+ '#': true,
+ '$': true,
+ '%': true,
+ '&': true,
+ '\'': true,
+ '(': true,
+ ')': true,
+ '*': true,
+ '+': true,
+ ',': true,
+ '-': true,
+ '.': true,
+ '/': true,
+ '0': true,
+ '1': true,
+ '2': true,
+ '3': true,
+ '4': true,
+ '5': true,
+ '6': true,
+ '7': true,
+ '8': true,
+ '9': true,
+ ':': true,
+ ';': true,
+ '<': true,
+ '=': true,
+ '>': true,
+ '?': true,
+ '@': true,
+ 'A': true,
+ 'B': true,
+ 'C': true,
+ 'D': true,
+ 'E': true,
+ 'F': true,
+ 'G': true,
+ 'H': true,
+ 'I': true,
+ 'J': true,
+ 'K': true,
+ 'L': true,
+ 'M': true,
+ 'N': true,
+ 'O': true,
+ 'P': true,
+ 'Q': true,
+ 'R': true,
+ 'S': true,
+ 'T': true,
+ 'U': true,
+ 'V': true,
+ 'W': true,
+ 'X': true,
+ 'Y': true,
+ 'Z': true,
+ '[': true,
+ '\\': false,
+ ']': true,
+ '^': true,
+ '_': true,
+ '`': true,
+ 'a': true,
+ 'b': true,
+ 'c': true,
+ 'd': true,
+ 'e': true,
+ 'f': true,
+ 'g': true,
+ 'h': true,
+ 'i': true,
+ 'j': true,
+ 'k': true,
+ 'l': true,
+ 'm': true,
+ 'n': true,
+ 'o': true,
+ 'p': true,
+ 'q': true,
+ 'r': true,
+ 's': true,
+ 't': true,
+ 'u': true,
+ 'v': true,
+ 'w': true,
+ 'x': true,
+ 'y': true,
+ 'z': true,
+ '{': true,
+ '|': true,
+ '}': true,
+ '~': true,
+ '\u007f': true,
+}
diff --git a/slog/json_handler_test.go b/slog/json_handler_test.go
new file mode 100644
index 0000000..9f27ffd
--- /dev/null
+++ b/slog/json_handler_test.go
@@ -0,0 +1,280 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/slog/internal/buffer"
+)
+
+func TestJSONHandler(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ opts HandlerOptions
+ want string
+ }{
+ {
+ "none",
+ HandlerOptions{},
+ `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"m","a":1,"m":{"b":2}}`,
+ },
+ {
+ "replace",
+ HandlerOptions{ReplaceAttr: upperCaseKey},
+ `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"m","A":1,"M":{"b":2}}`,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ var buf bytes.Buffer
+ h := NewJSONHandler(&buf, &test.opts)
+ r := NewRecord(testTime, LevelInfo, "m", 0)
+ r.AddAttrs(Int("a", 1), Any("m", map[string]int{"b": 2}))
+ if err := h.Handle(context.Background(), r); err != nil {
+ t.Fatal(err)
+ }
+ got := strings.TrimSuffix(buf.String(), "\n")
+ if got != test.want {
+ t.Errorf("\ngot %s\nwant %s", got, test.want)
+ }
+ })
+ }
+}
+
+// for testing json.Marshaler
+type jsonMarshaler struct {
+ s string
+}
+
+func (j jsonMarshaler) String() string { return j.s } // should be ignored
+
+func (j jsonMarshaler) MarshalJSON() ([]byte, error) {
+ if j.s == "" {
+ return nil, errors.New("json: empty string")
+ }
+ return []byte(fmt.Sprintf(`[%q]`, j.s)), nil
+}
+
+type jsonMarshalerError struct {
+ jsonMarshaler
+}
+
+func (jsonMarshalerError) Error() string { return "oops" }
+
+func TestAppendJSONValue(t *testing.T) {
+ // jsonAppendAttrValue should always agree with json.Marshal.
+ for _, value := range []any{
+ "hello",
+ `"[{escape}]"`,
+ "<escapeHTML&>",
+ `-123`,
+ int64(-9_200_123_456_789_123_456),
+ uint64(9_200_123_456_789_123_456),
+ -12.75,
+ 1.23e-9,
+ false,
+ time.Minute,
+ testTime,
+ jsonMarshaler{"xyz"},
+ jsonMarshalerError{jsonMarshaler{"pqr"}},
+ LevelWarn,
+ } {
+ got := jsonValueString(AnyValue(value))
+ want, err := marshalJSON(value)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got != want {
+ t.Errorf("%v: got %s, want %s", value, got, want)
+ }
+ }
+}
+
+func marshalJSON(x any) (string, error) {
+ var buf bytes.Buffer
+ enc := json.NewEncoder(&buf)
+ enc.SetEscapeHTML(false)
+ if err := enc.Encode(x); err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(buf.String()), nil
+}
+
+func TestJSONAppendAttrValueSpecial(t *testing.T) {
+ // Attr values that render differently from json.Marshal.
+ for _, test := range []struct {
+ value any
+ want string
+ }{
+ {math.NaN(), `"!ERROR:json: unsupported value: NaN"`},
+ {math.Inf(+1), `"!ERROR:json: unsupported value: +Inf"`},
+ {math.Inf(-1), `"!ERROR:json: unsupported value: -Inf"`},
+ {io.EOF, `"EOF"`},
+ } {
+ got := jsonValueString(AnyValue(test.value))
+ if got != test.want {
+ t.Errorf("%v: got %s, want %s", test.value, got, test.want)
+ }
+ }
+}
+
+func jsonValueString(v Value) string {
+ var buf []byte
+ s := &handleState{h: &commonHandler{json: true}, buf: (*buffer.Buffer)(&buf)}
+ if err := appendJSONValue(s, v); err != nil {
+ s.appendError(err)
+ }
+ return string(buf)
+}
+
+func BenchmarkJSONHandler(b *testing.B) {
+ for _, bench := range []struct {
+ name string
+ opts HandlerOptions
+ }{
+ {"defaults", HandlerOptions{}},
+ {"time format", HandlerOptions{
+ ReplaceAttr: func(_ []string, a Attr) Attr {
+ v := a.Value
+ if v.Kind() == KindTime {
+ return String(a.Key, v.Time().Format(rfc3339Millis))
+ }
+ if a.Key == "level" {
+ return Attr{"severity", a.Value}
+ }
+ return a
+ },
+ }},
+ {"time unix", HandlerOptions{
+ ReplaceAttr: func(_ []string, a Attr) Attr {
+ v := a.Value
+ if v.Kind() == KindTime {
+ return Int64(a.Key, v.Time().UnixNano())
+ }
+ if a.Key == "level" {
+ return Attr{"severity", a.Value}
+ }
+ return a
+ },
+ }},
+ } {
+ b.Run(bench.name, func(b *testing.B) {
+ l := New(NewJSONHandler(io.Discard, &bench.opts)).With(
+ String("program", "my-test-program"),
+ String("package", "log/slog"),
+ String("traceID", "2039232309232309"),
+ String("URL", "https://pkg.go.dev/golang.org/x/log/slog"))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ l.LogAttrs(nil, LevelInfo, "this is a typical log message",
+ String("module", "github.com/google/go-cmp"),
+ String("version", "v1.23.4"),
+ Int("count", 23),
+ Int("number", 123456),
+ )
+ }
+ })
+ }
+}
+
+func BenchmarkPreformatting(b *testing.B) {
+ type req struct {
+ Method string
+ URL string
+ TraceID string
+ Addr string
+ }
+
+ structAttrs := []any{
+ String("program", "my-test-program"),
+ String("package", "log/slog"),
+ Any("request", &req{
+ Method: "GET",
+ URL: "https://pkg.go.dev/golang.org/x/log/slog",
+ TraceID: "2039232309232309",
+ Addr: "127.0.0.1:8080",
+ }),
+ }
+
+ outFile, err := os.Create(filepath.Join(b.TempDir(), "bench.log"))
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer func() {
+ if err := outFile.Close(); err != nil {
+ b.Fatal(err)
+ }
+ }()
+
+ for _, bench := range []struct {
+ name string
+ wc io.Writer
+ attrs []any
+ }{
+ {"separate", io.Discard, []any{
+ String("program", "my-test-program"),
+ String("package", "log/slog"),
+ String("method", "GET"),
+ String("URL", "https://pkg.go.dev/golang.org/x/log/slog"),
+ String("traceID", "2039232309232309"),
+ String("addr", "127.0.0.1:8080"),
+ }},
+ {"struct", io.Discard, structAttrs},
+ {"struct file", outFile, structAttrs},
+ } {
+ b.Run(bench.name, func(b *testing.B) {
+ l := New(NewJSONHandler(bench.wc, nil)).With(bench.attrs...)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ l.LogAttrs(nil, LevelInfo, "this is a typical log message",
+ String("module", "github.com/google/go-cmp"),
+ String("version", "v1.23.4"),
+ Int("count", 23),
+ Int("number", 123456),
+ )
+ }
+ })
+ }
+}
+
+func BenchmarkJSONEncoding(b *testing.B) {
+ value := 3.14
+ buf := buffer.New()
+ defer buf.Free()
+ b.Run("json.Marshal", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ by, err := json.Marshal(value)
+ if err != nil {
+ b.Fatal(err)
+ }
+ buf.Write(by)
+ *buf = (*buf)[:0]
+ }
+ })
+ b.Run("Encoder.Encode", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ if err := json.NewEncoder(buf).Encode(value); err != nil {
+ b.Fatal(err)
+ }
+ *buf = (*buf)[:0]
+ }
+ })
+ _ = buf
+}
diff --git a/slog/level.go b/slog/level.go
new file mode 100644
index 0000000..b2365f0
--- /dev/null
+++ b/slog/level.go
@@ -0,0 +1,201 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "sync/atomic"
+)
+
+// A Level is the importance or severity of a log event.
+// The higher the level, the more important or severe the event.
+type Level int
+
+// Level numbers are inherently arbitrary,
+// but we picked them to satisfy three constraints.
+// Any system can map them to another numbering scheme if it wishes.
+//
+// First, we wanted the default level to be Info, Since Levels are ints, Info is
+// the default value for int, zero.
+//
+
+// Second, we wanted to make it easy to use levels to specify logger verbosity.
+// Since a larger level means a more severe event, a logger that accepts events
+// with smaller (or more negative) level means a more verbose logger. Logger
+// verbosity is thus the negation of event severity, and the default verbosity
+// of 0 accepts all events at least as severe as INFO.
+//
+// Third, we wanted some room between levels to accommodate schemes with named
+// levels between ours. For example, Google Cloud Logging defines a Notice level
+// between Info and Warn. Since there are only a few of these intermediate
+// levels, the gap between the numbers need not be large. Our gap of 4 matches
+// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the
+// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog
+// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog
+// does not. But those OpenTelemetry levels can still be represented as slog
+// Levels by using the appropriate integers.
+//
+// Names for common levels.
+const (
+ LevelDebug Level = -4
+ LevelInfo Level = 0
+ LevelWarn Level = 4
+ LevelError Level = 8
+)
+
+// String returns a name for the level.
+// If the level has a name, then that name
+// in uppercase is returned.
+// If the level is between named values, then
+// an integer is appended to the uppercased name.
+// Examples:
+//
+// LevelWarn.String() => "WARN"
+// (LevelInfo+2).String() => "INFO+2"
+func (l Level) String() string {
+ str := func(base string, val Level) string {
+ if val == 0 {
+ return base
+ }
+ return fmt.Sprintf("%s%+d", base, val)
+ }
+
+ switch {
+ case l < LevelInfo:
+ return str("DEBUG", l-LevelDebug)
+ case l < LevelWarn:
+ return str("INFO", l-LevelInfo)
+ case l < LevelError:
+ return str("WARN", l-LevelWarn)
+ default:
+ return str("ERROR", l-LevelError)
+ }
+}
+
+// MarshalJSON implements [encoding/json.Marshaler]
+// by quoting the output of [Level.String].
+func (l Level) MarshalJSON() ([]byte, error) {
+ // AppendQuote is sufficient for JSON-encoding all Level strings.
+ // They don't contain any runes that would produce invalid JSON
+ // when escaped.
+ return strconv.AppendQuote(nil, l.String()), nil
+}
+
+// UnmarshalJSON implements [encoding/json.Unmarshaler]
+// It accepts any string produced by [Level.MarshalJSON],
+// ignoring case.
+// It also accepts numeric offsets that would result in a different string on
+// output. For example, "Error-8" would marshal as "INFO".
+func (l *Level) UnmarshalJSON(data []byte) error {
+ s, err := strconv.Unquote(string(data))
+ if err != nil {
+ return err
+ }
+ return l.parse(s)
+}
+
+// MarshalText implements [encoding.TextMarshaler]
+// by calling [Level.String].
+func (l Level) MarshalText() ([]byte, error) {
+ return []byte(l.String()), nil
+}
+
+// UnmarshalText implements [encoding.TextUnmarshaler].
+// It accepts any string produced by [Level.MarshalText],
+// ignoring case.
+// It also accepts numeric offsets that would result in a different string on
+// output. For example, "Error-8" would marshal as "INFO".
+func (l *Level) UnmarshalText(data []byte) error {
+ return l.parse(string(data))
+}
+
+func (l *Level) parse(s string) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("slog: level string %q: %w", s, err)
+ }
+ }()
+
+ name := s
+ offset := 0
+ if i := strings.IndexAny(s, "+-"); i >= 0 {
+ name = s[:i]
+ offset, err = strconv.Atoi(s[i:])
+ if err != nil {
+ return err
+ }
+ }
+ switch strings.ToUpper(name) {
+ case "DEBUG":
+ *l = LevelDebug
+ case "INFO":
+ *l = LevelInfo
+ case "WARN":
+ *l = LevelWarn
+ case "ERROR":
+ *l = LevelError
+ default:
+ return errors.New("unknown name")
+ }
+ *l += Level(offset)
+ return nil
+}
+
+// Level returns the receiver.
+// It implements Leveler.
+func (l Level) Level() Level { return l }
+
+// A LevelVar is a Level variable, to allow a Handler level to change
+// dynamically.
+// It implements Leveler as well as a Set method,
+// and it is safe for use by multiple goroutines.
+// The zero LevelVar corresponds to LevelInfo.
+type LevelVar struct {
+ val atomic.Int64
+}
+
+// Level returns v's level.
+func (v *LevelVar) Level() Level {
+ return Level(int(v.val.Load()))
+}
+
+// Set sets v's level to l.
+func (v *LevelVar) Set(l Level) {
+ v.val.Store(int64(l))
+}
+
+func (v *LevelVar) String() string {
+ return fmt.Sprintf("LevelVar(%s)", v.Level())
+}
+
+// MarshalText implements [encoding.TextMarshaler]
+// by calling [Level.MarshalText].
+func (v *LevelVar) MarshalText() ([]byte, error) {
+ return v.Level().MarshalText()
+}
+
+// UnmarshalText implements [encoding.TextUnmarshaler]
+// by calling [Level.UnmarshalText].
+func (v *LevelVar) UnmarshalText(data []byte) error {
+ var l Level
+ if err := l.UnmarshalText(data); err != nil {
+ return err
+ }
+ v.Set(l)
+ return nil
+}
+
+// A Leveler provides a Level value.
+//
+// As Level itself implements Leveler, clients typically supply
+// a Level value wherever a Leveler is needed, such as in HandlerOptions.
+// Clients who need to vary the level dynamically can provide a more complex
+// Leveler implementation such as *LevelVar.
+type Leveler interface {
+ Level() Level
+}
diff --git a/slog/level_test.go b/slog/level_test.go
new file mode 100644
index 0000000..33b20fd
--- /dev/null
+++ b/slog/level_test.go
@@ -0,0 +1,168 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "flag"
+ "strings"
+ "testing"
+)
+
+func TestLevelString(t *testing.T) {
+ for _, test := range []struct {
+ in Level
+ want string
+ }{
+ {0, "INFO"},
+ {LevelError, "ERROR"},
+ {LevelError + 2, "ERROR+2"},
+ {LevelError - 2, "WARN+2"},
+ {LevelWarn, "WARN"},
+ {LevelWarn - 1, "INFO+3"},
+ {LevelInfo, "INFO"},
+ {LevelInfo + 1, "INFO+1"},
+ {LevelInfo - 3, "DEBUG+1"},
+ {LevelDebug, "DEBUG"},
+ {LevelDebug - 2, "DEBUG-2"},
+ } {
+ got := test.in.String()
+ if got != test.want {
+ t.Errorf("%d: got %s, want %s", test.in, got, test.want)
+ }
+ }
+}
+
+func TestLevelVar(t *testing.T) {
+ var al LevelVar
+ if got, want := al.Level(), LevelInfo; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ al.Set(LevelWarn)
+ if got, want := al.Level(), LevelWarn; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ al.Set(LevelInfo)
+ if got, want := al.Level(), LevelInfo; got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+
+}
+
+func TestMarshalJSON(t *testing.T) {
+ want := LevelWarn - 3
+ data, err := want.MarshalJSON()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got Level
+ if err := got.UnmarshalJSON(data); err != nil {
+ t.Fatal(err)
+ }
+ if got != want {
+ t.Errorf("got %s, want %s", got, want)
+ }
+}
+
+func TestLevelMarshalText(t *testing.T) {
+ want := LevelWarn - 3
+ data, err := want.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got Level
+ if err := got.UnmarshalText(data); err != nil {
+ t.Fatal(err)
+ }
+ if got != want {
+ t.Errorf("got %s, want %s", got, want)
+ }
+}
+
+func TestLevelParse(t *testing.T) {
+ for _, test := range []struct {
+ in string
+ want Level
+ }{
+ {"DEBUG", LevelDebug},
+ {"INFO", LevelInfo},
+ {"WARN", LevelWarn},
+ {"ERROR", LevelError},
+ {"debug", LevelDebug},
+ {"iNfo", LevelInfo},
+ {"INFO+87", LevelInfo + 87},
+ {"Error-18", LevelError - 18},
+ {"Error-8", LevelInfo},
+ } {
+ var got Level
+ if err := got.parse(test.in); err != nil {
+ t.Fatalf("%q: %v", test.in, err)
+ }
+ if got != test.want {
+ t.Errorf("%q: got %s, want %s", test.in, got, test.want)
+ }
+ }
+}
+
+func TestLevelParseError(t *testing.T) {
+ for _, test := range []struct {
+ in string
+ want string // error string should contain this
+ }{
+ {"", "unknown name"},
+ {"dbg", "unknown name"},
+ {"INFO+", "invalid syntax"},
+ {"INFO-", "invalid syntax"},
+ {"ERROR+23x", "invalid syntax"},
+ } {
+ var l Level
+ err := l.parse(test.in)
+ if err == nil || !strings.Contains(err.Error(), test.want) {
+ t.Errorf("%q: got %v, want string containing %q", test.in, err, test.want)
+ }
+ }
+}
+
+func TestLevelFlag(t *testing.T) {
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+ lf := LevelInfo
+ fs.TextVar(&lf, "level", lf, "set level")
+ err := fs.Parse([]string{"-level", "WARN+3"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if g, w := lf, LevelWarn+3; g != w {
+ t.Errorf("got %v, want %v", g, w)
+ }
+}
+
+func TestLevelVarMarshalText(t *testing.T) {
+ var v LevelVar
+ v.Set(LevelWarn)
+ data, err := v.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var v2 LevelVar
+ if err := v2.UnmarshalText(data); err != nil {
+ t.Fatal(err)
+ }
+ if g, w := v2.Level(), LevelWarn; g != w {
+ t.Errorf("got %s, want %s", g, w)
+ }
+}
+
+func TestLevelVarFlag(t *testing.T) {
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+ v := &LevelVar{}
+ v.Set(LevelWarn + 3)
+ fs.TextVar(v, "level", v, "set level")
+ err := fs.Parse([]string{"-level", "WARN+3"})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if g, w := v.Level(), LevelWarn+3; g != w {
+ t.Errorf("got %v, want %v", g, w)
+ }
+}
diff --git a/slog/logger.go b/slog/logger.go
new file mode 100644
index 0000000..e87ec99
--- /dev/null
+++ b/slog/logger.go
@@ -0,0 +1,343 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "context"
+ "log"
+ "runtime"
+ "sync/atomic"
+ "time"
+
+ "golang.org/x/exp/slog/internal"
+)
+
+var defaultLogger atomic.Value
+
+func init() {
+ defaultLogger.Store(New(newDefaultHandler(log.Output)))
+}
+
+// Default returns the default Logger.
+func Default() *Logger { return defaultLogger.Load().(*Logger) }
+
+// SetDefault makes l the default Logger.
+// After this call, output from the log package's default Logger
+// (as with [log.Print], etc.) will be logged at LevelInfo using l's Handler.
+func SetDefault(l *Logger) {
+ defaultLogger.Store(l)
+ // If the default's handler is a defaultHandler, then don't use a handleWriter,
+ // or we'll deadlock as they both try to acquire the log default mutex.
+ // The defaultHandler will use whatever the log default writer is currently
+ // set to, which is correct.
+ // This can occur with SetDefault(Default()).
+ // See TestSetDefault.
+ if _, ok := l.Handler().(*defaultHandler); !ok {
+ capturePC := log.Flags()&(log.Lshortfile|log.Llongfile) != 0
+ log.SetOutput(&handlerWriter{l.Handler(), LevelInfo, capturePC})
+ log.SetFlags(0) // we want just the log message, no time or location
+ }
+}
+
+// handlerWriter is an io.Writer that calls a Handler.
+// It is used to link the default log.Logger to the default slog.Logger.
+type handlerWriter struct {
+ h Handler
+ level Level
+ capturePC bool
+}
+
+func (w *handlerWriter) Write(buf []byte) (int, error) {
+ if !w.h.Enabled(context.Background(), w.level) {
+ return 0, nil
+ }
+ var pc uintptr
+ if !internal.IgnorePC && w.capturePC {
+ // skip [runtime.Callers, w.Write, Logger.Output, log.Print]
+ var pcs [1]uintptr
+ runtime.Callers(4, pcs[:])
+ pc = pcs[0]
+ }
+
+ // Remove final newline.
+ origLen := len(buf) // Report that the entire buf was written.
+ if len(buf) > 0 && buf[len(buf)-1] == '\n' {
+ buf = buf[:len(buf)-1]
+ }
+ r := NewRecord(time.Now(), w.level, string(buf), pc)
+ return origLen, w.h.Handle(context.Background(), r)
+}
+
+// A Logger records structured information about each call to its
+// Log, Debug, Info, Warn, and Error methods.
+// For each call, it creates a Record and passes it to a Handler.
+//
+// To create a new Logger, call [New] or a Logger method
+// that begins "With".
+type Logger struct {
+ handler Handler // for structured logging
+}
+
+func (l *Logger) clone() *Logger {
+ c := *l
+ return &c
+}
+
+// Handler returns l's Handler.
+func (l *Logger) Handler() Handler { return l.handler }
+
+// With returns a new Logger that includes the given arguments, converted to
+// Attrs as in [Logger.Log].
+// The Attrs will be added to each output from the Logger.
+// The new Logger shares the old Logger's context.
+// The new Logger's handler is the result of calling WithAttrs on the receiver's
+// handler.
+func (l *Logger) With(args ...any) *Logger {
+ c := l.clone()
+ c.handler = l.handler.WithAttrs(argsToAttrSlice(args))
+ return c
+}
+
+// WithGroup returns a new Logger that starts a group. The keys of all
+// attributes added to the Logger will be qualified by the given name.
+// (How that qualification happens depends on the [Handler.WithGroup]
+// method of the Logger's Handler.)
+// The new Logger shares the old Logger's context.
+//
+// The new Logger's handler is the result of calling WithGroup on the receiver's
+// handler.
+func (l *Logger) WithGroup(name string) *Logger {
+ c := l.clone()
+ c.handler = l.handler.WithGroup(name)
+ return c
+
+}
+
+// New creates a new Logger with the given non-nil Handler and a nil context.
+func New(h Handler) *Logger {
+ if h == nil {
+ panic("nil Handler")
+ }
+ return &Logger{handler: h}
+}
+
+// With calls Logger.With on the default logger.
+func With(args ...any) *Logger {
+ return Default().With(args...)
+}
+
+// Enabled reports whether l emits log records at the given context and level.
+func (l *Logger) Enabled(ctx context.Context, level Level) bool {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ return l.Handler().Enabled(ctx, level)
+}
+
+// NewLogLogger returns a new log.Logger such that each call to its Output method
+// dispatches a Record to the specified handler. The logger acts as a bridge from
+// the older log API to newer structured logging handlers.
+func NewLogLogger(h Handler, level Level) *log.Logger {
+ return log.New(&handlerWriter{h, level, true}, "", 0)
+}
+
+// Log emits a log record with the current time and the given level and message.
+// The Record's Attrs consist of the Logger's attributes followed by
+// the Attrs specified by args.
+//
+// The attribute arguments are processed as follows:
+// - If an argument is an Attr, it is used as is.
+// - If an argument is a string and this is not the last argument,
+// the following argument is treated as the value and the two are combined
+// into an Attr.
+// - Otherwise, the argument is treated as a value with key "!BADKEY".
+func (l *Logger) Log(ctx context.Context, level Level, msg string, args ...any) {
+ l.log(ctx, level, msg, args...)
+}
+
+// LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs.
+func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
+ l.logAttrs(ctx, level, msg, attrs...)
+}
+
+// Debug logs at LevelDebug.
+func (l *Logger) Debug(msg string, args ...any) {
+ l.log(nil, LevelDebug, msg, args...)
+}
+
+// DebugContext logs at LevelDebug with the given context.
+func (l *Logger) DebugContext(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelDebug, msg, args...)
+}
+
+// DebugCtx logs at LevelDebug with the given context.
+// Deprecated: Use Logger.DebugContext.
+func (l *Logger) DebugCtx(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelDebug, msg, args...)
+}
+
+// Info logs at LevelInfo.
+func (l *Logger) Info(msg string, args ...any) {
+ l.log(nil, LevelInfo, msg, args...)
+}
+
+// InfoContext logs at LevelInfo with the given context.
+func (l *Logger) InfoContext(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelInfo, msg, args...)
+}
+
+// InfoCtx logs at LevelInfo with the given context.
+// Deprecated: Use Logger.InfoContext.
+func (l *Logger) InfoCtx(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelInfo, msg, args...)
+}
+
+// Warn logs at LevelWarn.
+func (l *Logger) Warn(msg string, args ...any) {
+ l.log(nil, LevelWarn, msg, args...)
+}
+
+// WarnContext logs at LevelWarn with the given context.
+func (l *Logger) WarnContext(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelWarn, msg, args...)
+}
+
+// WarnCtx logs at LevelWarn with the given context.
+// Deprecated: Use Logger.WarnContext.
+func (l *Logger) WarnCtx(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelWarn, msg, args...)
+}
+
+// Error logs at LevelError.
+func (l *Logger) Error(msg string, args ...any) {
+ l.log(nil, LevelError, msg, args...)
+}
+
+// ErrorContext logs at LevelError with the given context.
+func (l *Logger) ErrorContext(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelError, msg, args...)
+}
+
+// ErrorCtx logs at LevelError with the given context.
+// Deprecated: Use Logger.ErrorContext.
+func (l *Logger) ErrorCtx(ctx context.Context, msg string, args ...any) {
+ l.log(ctx, LevelError, msg, args...)
+}
+
+// log is the low-level logging method for methods that take ...any.
+// It must always be called directly by an exported logging method
+// or function, because it uses a fixed call depth to obtain the pc.
+func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any) {
+ if !l.Enabled(ctx, level) {
+ return
+ }
+ var pc uintptr
+ if !internal.IgnorePC {
+ var pcs [1]uintptr
+ // skip [runtime.Callers, this function, this function's caller]
+ runtime.Callers(3, pcs[:])
+ pc = pcs[0]
+ }
+ r := NewRecord(time.Now(), level, msg, pc)
+ r.Add(args...)
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ _ = l.Handler().Handle(ctx, r)
+}
+
+// logAttrs is like [Logger.log], but for methods that take ...Attr.
+func (l *Logger) logAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
+ if !l.Enabled(ctx, level) {
+ return
+ }
+ var pc uintptr
+ if !internal.IgnorePC {
+ var pcs [1]uintptr
+ // skip [runtime.Callers, this function, this function's caller]
+ runtime.Callers(3, pcs[:])
+ pc = pcs[0]
+ }
+ r := NewRecord(time.Now(), level, msg, pc)
+ r.AddAttrs(attrs...)
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ _ = l.Handler().Handle(ctx, r)
+}
+
+// Debug calls Logger.Debug on the default logger.
+func Debug(msg string, args ...any) {
+ Default().log(nil, LevelDebug, msg, args...)
+}
+
+// DebugContext calls Logger.DebugContext on the default logger.
+func DebugContext(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelDebug, msg, args...)
+}
+
+// Info calls Logger.Info on the default logger.
+func Info(msg string, args ...any) {
+ Default().log(nil, LevelInfo, msg, args...)
+}
+
+// InfoContext calls Logger.InfoContext on the default logger.
+func InfoContext(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelInfo, msg, args...)
+}
+
+// Warn calls Logger.Warn on the default logger.
+func Warn(msg string, args ...any) {
+ Default().log(nil, LevelWarn, msg, args...)
+}
+
+// WarnContext calls Logger.WarnContext on the default logger.
+func WarnContext(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelWarn, msg, args...)
+}
+
+// Error calls Logger.Error on the default logger.
+func Error(msg string, args ...any) {
+ Default().log(nil, LevelError, msg, args...)
+}
+
+// ErrorContext calls Logger.ErrorContext on the default logger.
+func ErrorContext(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelError, msg, args...)
+}
+
+// DebugCtx calls Logger.DebugContext on the default logger.
+// Deprecated: call DebugContext.
+func DebugCtx(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelDebug, msg, args...)
+}
+
+// InfoCtx calls Logger.InfoContext on the default logger.
+// Deprecated: call InfoContext.
+func InfoCtx(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelInfo, msg, args...)
+}
+
+// WarnCtx calls Logger.WarnContext on the default logger.
+// Deprecated: call WarnContext.
+func WarnCtx(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelWarn, msg, args...)
+}
+
+// ErrorCtx calls Logger.ErrorContext on the default logger.
+// Deprecated: call ErrorContext.
+func ErrorCtx(ctx context.Context, msg string, args ...any) {
+ Default().log(ctx, LevelError, msg, args...)
+}
+
+// Log calls Logger.Log on the default logger.
+func Log(ctx context.Context, level Level, msg string, args ...any) {
+ Default().log(ctx, level, msg, args...)
+}
+
+// LogAttrs calls Logger.LogAttrs on the default logger.
+func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
+ Default().logAttrs(ctx, level, msg, attrs...)
+}
diff --git a/slog/logger_test.go b/slog/logger_test.go
new file mode 100644
index 0000000..2839e05
--- /dev/null
+++ b/slog/logger_test.go
@@ -0,0 +1,509 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "log"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/slices"
+)
+
+const timeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|[+-]\d{2}:\d{2})`
+
+func TestLogTextHandler(t *testing.T) {
+ var buf bytes.Buffer
+
+ l := New(NewTextHandler(&buf, nil))
+
+ check := func(want string) {
+ t.Helper()
+ if want != "" {
+ want = "time=" + timeRE + " " + want
+ }
+ checkLogOutput(t, buf.String(), want)
+ buf.Reset()
+ }
+
+ l.Info("msg", "a", 1, "b", 2)
+ check(`level=INFO msg=msg a=1 b=2`)
+
+ // By default, debug messages are not printed.
+ l.Debug("bg", Int("a", 1), "b", 2)
+ check("")
+
+ l.Warn("w", Duration("dur", 3*time.Second))
+ check(`level=WARN msg=w dur=3s`)
+
+ l.Error("bad", "a", 1)
+ check(`level=ERROR msg=bad a=1`)
+
+ l.Log(nil, LevelWarn+1, "w", Int("a", 1), String("b", "two"))
+ check(`level=WARN\+1 msg=w a=1 b=two`)
+
+ l.LogAttrs(nil, LevelInfo+1, "a b c", Int("a", 1), String("b", "two"))
+ check(`level=INFO\+1 msg="a b c" a=1 b=two`)
+
+ l.Info("info", "a", []Attr{Int("i", 1)})
+ check(`level=INFO msg=info a.i=1`)
+
+ l.Info("info", "a", GroupValue(Int("i", 1)))
+ check(`level=INFO msg=info a.i=1`)
+}
+
+func TestConnections(t *testing.T) {
+ var logbuf, slogbuf bytes.Buffer
+
+ // Revert any changes to the default logger. This is important because other
+ // tests might change the default logger using SetDefault. Also ensure we
+ // restore the default logger at the end of the test.
+ currentLogger := Default()
+ SetDefault(New(newDefaultHandler(log.Output)))
+ t.Cleanup(func() {
+ SetDefault(currentLogger)
+ })
+
+ // The default slog.Logger's handler uses the log package's default output.
+ log.SetOutput(&logbuf)
+ log.SetFlags(log.Lshortfile &^ log.LstdFlags)
+ Info("msg", "a", 1)
+ checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg a=1`)
+ logbuf.Reset()
+ Warn("msg", "b", 2)
+ checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: WARN msg b=2`)
+ logbuf.Reset()
+ Error("msg", "err", io.EOF, "c", 3)
+ checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: ERROR msg err=EOF c=3`)
+
+ // Levels below Info are not printed.
+ logbuf.Reset()
+ Debug("msg", "c", 3)
+ checkLogOutput(t, logbuf.String(), "")
+
+ t.Run("wrap default handler", func(t *testing.T) {
+ // It should be possible to wrap the default handler and get the right output.
+ // But because the call depth to log.Output is hard-coded, the source line is wrong.
+ // We want to use the pc inside the Record, but there is no way to give that to
+ // the log package.
+ //
+ // TODO(jba): when slog lives under log in the standard library, we can
+ // move the bulk of log.Logger.Output to a function in an internal
+ // package, so both log and slog can call it.
+ //
+ // While slog lives in exp, we punt.
+ t.Skip("skip until this package is in the standard library")
+ logger := New(wrappingHandler{Default().Handler()})
+ logger.Info("msg", "d", 4)
+ checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg d=4`)
+ })
+
+ // Once slog.SetDefault is called, the direction is reversed: the default
+ // log.Logger's output goes through the handler.
+ SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{AddSource: true})))
+ log.Print("msg2")
+ checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3} msg=msg2`)
+
+ // The default log.Logger always outputs at Info level.
+ slogbuf.Reset()
+ SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{Level: LevelWarn})))
+ log.Print("should not appear")
+ if got := slogbuf.String(); got != "" {
+ t.Errorf("got %q, want empty", got)
+ }
+
+ // Setting log's output again breaks the connection.
+ logbuf.Reset()
+ slogbuf.Reset()
+ log.SetOutput(&logbuf)
+ log.SetFlags(log.Lshortfile &^ log.LstdFlags)
+ log.Print("msg3")
+ checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: msg3`)
+ if got := slogbuf.String(); got != "" {
+ t.Errorf("got %q, want empty", got)
+ }
+}
+
+type wrappingHandler struct {
+ h Handler
+}
+
+func (h wrappingHandler) Enabled(ctx context.Context, level Level) bool {
+ return h.h.Enabled(ctx, level)
+}
+func (h wrappingHandler) WithGroup(name string) Handler { return h.h.WithGroup(name) }
+func (h wrappingHandler) WithAttrs(as []Attr) Handler { return h.h.WithAttrs(as) }
+func (h wrappingHandler) Handle(ctx context.Context, r Record) error { return h.h.Handle(ctx, r) }
+
+func TestAttrs(t *testing.T) {
+ check := func(got []Attr, want ...Attr) {
+ t.Helper()
+ if !attrsEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ }
+
+ l1 := New(&captureHandler{}).With("a", 1)
+ l2 := New(l1.Handler()).With("b", 2)
+ l2.Info("m", "c", 3)
+ h := l2.Handler().(*captureHandler)
+ check(h.attrs, Int("a", 1), Int("b", 2))
+ check(attrsSlice(h.r), Int("c", 3))
+}
+
+func TestCallDepth(t *testing.T) {
+ h := &captureHandler{}
+ var startLine int
+
+ check := func(count int) {
+ t.Helper()
+ const wantFunc = "golang.org/x/exp/slog.TestCallDepth"
+ const wantFile = "logger_test.go"
+ wantLine := startLine + count*2
+ got := h.r.source()
+ gotFile := filepath.Base(got.File)
+ if got.Function != wantFunc || gotFile != wantFile || got.Line != wantLine {
+ t.Errorf("got (%s, %s, %d), want (%s, %s, %d)",
+ got.Function, gotFile, got.Line, wantFunc, wantFile, wantLine)
+ }
+ }
+
+ logger := New(h)
+ SetDefault(logger)
+
+ // Calls to check must be one line apart.
+ // Determine line where calls start.
+ f, _ := runtime.CallersFrames([]uintptr{callerPC(2)}).Next()
+ startLine = f.Line + 4
+ // Do not change the number of lines between here and the call to check(0).
+
+ logger.Log(nil, LevelInfo, "")
+ check(0)
+ logger.LogAttrs(nil, LevelInfo, "")
+ check(1)
+ logger.Debug("")
+ check(2)
+ logger.Info("")
+ check(3)
+ logger.Warn("")
+ check(4)
+ logger.Error("")
+ check(5)
+ Debug("")
+ check(6)
+ Info("")
+ check(7)
+ Warn("")
+ check(8)
+ Error("")
+ check(9)
+ Log(nil, LevelInfo, "")
+ check(10)
+ LogAttrs(nil, LevelInfo, "")
+ check(11)
+}
+
+func TestAlloc(t *testing.T) {
+ dl := New(discardHandler{})
+ defer func(d *Logger) { SetDefault(d) }(Default())
+ SetDefault(dl)
+
+ t.Run("Info", func(t *testing.T) {
+ wantAllocs(t, 0, func() { Info("hello") })
+ })
+ t.Run("Error", func(t *testing.T) {
+ wantAllocs(t, 0, func() { Error("hello") })
+ })
+ t.Run("logger.Info", func(t *testing.T) {
+ wantAllocs(t, 0, func() { dl.Info("hello") })
+ })
+ t.Run("logger.Log", func(t *testing.T) {
+ wantAllocs(t, 0, func() { dl.Log(nil, LevelDebug, "hello") })
+ })
+ t.Run("2 pairs", func(t *testing.T) {
+ s := "abc"
+ i := 2000
+ wantAllocs(t, 2, func() {
+ dl.Info("hello",
+ "n", i,
+ "s", s,
+ )
+ })
+ })
+ t.Run("2 pairs disabled inline", func(t *testing.T) {
+ l := New(discardHandler{disabled: true})
+ s := "abc"
+ i := 2000
+ wantAllocs(t, 2, func() {
+ l.Log(nil, LevelInfo, "hello",
+ "n", i,
+ "s", s,
+ )
+ })
+ })
+ t.Run("2 pairs disabled", func(t *testing.T) {
+ l := New(discardHandler{disabled: true})
+ s := "abc"
+ i := 2000
+ wantAllocs(t, 0, func() {
+ if l.Enabled(nil, LevelInfo) {
+ l.Log(nil, LevelInfo, "hello",
+ "n", i,
+ "s", s,
+ )
+ }
+ })
+ })
+ t.Run("9 kvs", func(t *testing.T) {
+ s := "abc"
+ i := 2000
+ d := time.Second
+ wantAllocs(t, 11, func() {
+ dl.Info("hello",
+ "n", i, "s", s, "d", d,
+ "n", i, "s", s, "d", d,
+ "n", i, "s", s, "d", d)
+ })
+ })
+ t.Run("pairs", func(t *testing.T) {
+ wantAllocs(t, 0, func() { dl.Info("", "error", io.EOF) })
+ })
+ t.Run("attrs1", func(t *testing.T) {
+ wantAllocs(t, 0, func() { dl.LogAttrs(nil, LevelInfo, "", Int("a", 1)) })
+ wantAllocs(t, 0, func() { dl.LogAttrs(nil, LevelInfo, "", Any("error", io.EOF)) })
+ })
+ t.Run("attrs3", func(t *testing.T) {
+ wantAllocs(t, 0, func() {
+ dl.LogAttrs(nil, LevelInfo, "hello", Int("a", 1), String("b", "two"), Duration("c", time.Second))
+ })
+ })
+ t.Run("attrs3 disabled", func(t *testing.T) {
+ logger := New(discardHandler{disabled: true})
+ wantAllocs(t, 0, func() {
+ logger.LogAttrs(nil, LevelInfo, "hello", Int("a", 1), String("b", "two"), Duration("c", time.Second))
+ })
+ })
+ t.Run("attrs6", func(t *testing.T) {
+ wantAllocs(t, 1, func() {
+ dl.LogAttrs(nil, LevelInfo, "hello",
+ Int("a", 1), String("b", "two"), Duration("c", time.Second),
+ Int("d", 1), String("e", "two"), Duration("f", time.Second))
+ })
+ })
+ t.Run("attrs9", func(t *testing.T) {
+ wantAllocs(t, 1, func() {
+ dl.LogAttrs(nil, LevelInfo, "hello",
+ Int("a", 1), String("b", "two"), Duration("c", time.Second),
+ Int("d", 1), String("e", "two"), Duration("f", time.Second),
+ Int("d", 1), String("e", "two"), Duration("f", time.Second))
+ })
+ })
+}
+
+func TestSetAttrs(t *testing.T) {
+ for _, test := range []struct {
+ args []any
+ want []Attr
+ }{
+ {nil, nil},
+ {[]any{"a", 1}, []Attr{Int("a", 1)}},
+ {[]any{"a", 1, "b", "two"}, []Attr{Int("a", 1), String("b", "two")}},
+ {[]any{"a"}, []Attr{String(badKey, "a")}},
+ {[]any{"a", 1, "b"}, []Attr{Int("a", 1), String(badKey, "b")}},
+ {[]any{"a", 1, 2, 3}, []Attr{Int("a", 1), Int(badKey, 2), Int(badKey, 3)}},
+ } {
+ r := NewRecord(time.Time{}, 0, "", 0)
+ r.Add(test.args...)
+ got := attrsSlice(r)
+ if !attrsEqual(got, test.want) {
+ t.Errorf("%v:\ngot %v\nwant %v", test.args, got, test.want)
+ }
+ }
+}
+
+func TestSetDefault(t *testing.T) {
+ // Verify that setting the default to itself does not result in deadlock.
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+ defer func(w io.Writer) { log.SetOutput(w) }(log.Writer())
+ log.SetOutput(io.Discard)
+ go func() {
+ Info("A")
+ SetDefault(Default())
+ Info("B")
+ cancel()
+ }()
+ <-ctx.Done()
+ if err := ctx.Err(); err != context.Canceled {
+ t.Errorf("wanted canceled, got %v", err)
+ }
+}
+
+func TestLoggerError(t *testing.T) {
+ var buf bytes.Buffer
+
+ removeTime := func(_ []string, a Attr) Attr {
+ if a.Key == TimeKey {
+ return Attr{}
+ }
+ return a
+ }
+ l := New(NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeTime}))
+ l.Error("msg", "err", io.EOF, "a", 1)
+ checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF a=1`)
+ buf.Reset()
+ l.Error("msg", "err", io.EOF, "a")
+ checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF !BADKEY=a`)
+}
+
+func TestNewLogLogger(t *testing.T) {
+ var buf bytes.Buffer
+ h := NewTextHandler(&buf, nil)
+ ll := NewLogLogger(h, LevelWarn)
+ ll.Print("hello")
+ checkLogOutput(t, buf.String(), "time="+timeRE+` level=WARN msg=hello`)
+}
+
+func checkLogOutput(t *testing.T, got, wantRegexp string) {
+ t.Helper()
+ got = clean(got)
+ wantRegexp = "^" + wantRegexp + "$"
+ matched, err := regexp.MatchString(wantRegexp, got)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !matched {
+ t.Errorf("\ngot %s\nwant %s", got, wantRegexp)
+ }
+}
+
+// clean prepares log output for comparison.
+func clean(s string) string {
+ if len(s) > 0 && s[len(s)-1] == '\n' {
+ s = s[:len(s)-1]
+ }
+ return strings.ReplaceAll(s, "\n", "~")
+}
+
+type captureHandler struct {
+ mu sync.Mutex
+ r Record
+ attrs []Attr
+ groups []string
+}
+
+func (h *captureHandler) Handle(ctx context.Context, r Record) error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.r = r
+ return nil
+}
+
+func (*captureHandler) Enabled(context.Context, Level) bool { return true }
+
+func (c *captureHandler) WithAttrs(as []Attr) Handler {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ var c2 captureHandler
+ c2.r = c.r
+ c2.groups = c.groups
+ c2.attrs = concat(c.attrs, as)
+ return &c2
+}
+
+func (c *captureHandler) WithGroup(name string) Handler {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ var c2 captureHandler
+ c2.r = c.r
+ c2.attrs = c.attrs
+ c2.groups = append(slices.Clip(c.groups), name)
+ return &c2
+}
+
+type discardHandler struct {
+ disabled bool
+ attrs []Attr
+}
+
+func (d discardHandler) Enabled(context.Context, Level) bool { return !d.disabled }
+func (discardHandler) Handle(context.Context, Record) error { return nil }
+func (d discardHandler) WithAttrs(as []Attr) Handler {
+ d.attrs = concat(d.attrs, as)
+ return d
+}
+func (h discardHandler) WithGroup(name string) Handler {
+ return h
+}
+
+// concat returns a new slice with the elements of s1 followed
+// by those of s2. The slice has no additional capacity.
+func concat[T any](s1, s2 []T) []T {
+ s := make([]T, len(s1)+len(s2))
+ copy(s, s1)
+ copy(s[len(s1):], s2)
+ return s
+}
+
+// This is a simple benchmark. See the benchmarks subdirectory for more extensive ones.
+func BenchmarkNopLog(b *testing.B) {
+ ctx := context.Background()
+ l := New(&captureHandler{})
+ b.Run("no attrs", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ l.LogAttrs(nil, LevelInfo, "msg")
+ }
+ })
+ b.Run("attrs", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ l.LogAttrs(nil, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
+ }
+ })
+ b.Run("attrs-parallel", func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ l.LogAttrs(nil, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
+ }
+ })
+ })
+ b.Run("keys-values", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ l.Log(nil, LevelInfo, "msg", "a", 1, "b", "two", "c", true)
+ }
+ })
+ b.Run("WithContext", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ l.LogAttrs(ctx, LevelInfo, "msg2", Int("a", 1), String("b", "two"), Bool("c", true))
+ }
+ })
+ b.Run("WithContext-parallel", func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
+ }
+ })
+ })
+}
+
+// callerPC returns the program counter at the given stack depth.
+func callerPC(depth int) uintptr {
+ var pcs [1]uintptr
+ runtime.Callers(depth, pcs[:])
+ return pcs[0]
+}
diff --git a/slog/noplog.bench b/slog/noplog.bench
new file mode 100644
index 0000000..ed9296f
--- /dev/null
+++ b/slog/noplog.bench
@@ -0,0 +1,36 @@
+goos: linux
+goarch: amd64
+pkg: golang.org/x/exp/slog
+cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
+BenchmarkNopLog/attrs-8 1000000 1090 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-8 1000000 1097 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-8 1000000 1078 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-8 1000000 1095 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-8 1000000 1096 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-parallel-8 4007268 308.2 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-parallel-8 4016138 299.7 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-parallel-8 4020529 305.9 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-parallel-8 3977829 303.4 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/attrs-parallel-8 3225438 318.5 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/keys-values-8 1179256 994.2 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/keys-values-8 1000000 1002 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/keys-values-8 1216710 993.2 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/keys-values-8 1000000 1013 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/keys-values-8 1000000 1016 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-8 989066 1163 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-8 994116 1163 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-8 1000000 1152 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-8 991675 1165 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-8 965268 1166 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-parallel-8 3955503 303.3 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-parallel-8 3861188 307.8 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-parallel-8 3967752 303.9 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-parallel-8 3955203 302.7 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/WithContext-parallel-8 3948278 301.1 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/Ctx-8 940622 1247 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/Ctx-8 936381 1257 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/Ctx-8 959730 1266 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/Ctx-8 943473 1290 ns/op 0 B/op 0 allocs/op
+BenchmarkNopLog/Ctx-8 919414 1259 ns/op 0 B/op 0 allocs/op
+PASS
+ok golang.org/x/exp/slog 40.566s
diff --git a/slog/norace_test.go b/slog/norace_test.go
new file mode 100644
index 0000000..69a2866
--- /dev/null
+++ b/slog/norace_test.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !race
+
+package slog
+
+import "testing"
+
+func wantAllocs(t *testing.T, want int, f func()) {
+ t.Helper()
+ got := int(testing.AllocsPerRun(5, f))
+ if got != want {
+ t.Errorf("got %d allocs, want %d", got, want)
+ }
+}
diff --git a/slog/race_test.go b/slog/race_test.go
new file mode 100644
index 0000000..be69816
--- /dev/null
+++ b/slog/race_test.go
@@ -0,0 +1,13 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build race
+
+package slog
+
+import "testing"
+
+func wantAllocs(t *testing.T, want int, f func()) {
+ t.Log("skipping allocation tests with race detector")
+}
diff --git a/slog/record.go b/slog/record.go
new file mode 100644
index 0000000..38b3440
--- /dev/null
+++ b/slog/record.go
@@ -0,0 +1,207 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "runtime"
+ "time"
+
+ "golang.org/x/exp/slices"
+)
+
+const nAttrsInline = 5
+
+// A Record holds information about a log event.
+// Copies of a Record share state.
+// Do not modify a Record after handing out a copy to it.
+// Use [Record.Clone] to create a copy with no shared state.
+type Record struct {
+ // The time at which the output method (Log, Info, etc.) was called.
+ Time time.Time
+
+ // The log message.
+ Message string
+
+ // The level of the event.
+ Level Level
+
+ // The program counter at the time the record was constructed, as determined
+ // by runtime.Callers. If zero, no program counter is available.
+ //
+ // The only valid use for this value is as an argument to
+ // [runtime.CallersFrames]. In particular, it must not be passed to
+ // [runtime.FuncForPC].
+ PC uintptr
+
+ // Allocation optimization: an inline array sized to hold
+ // the majority of log calls (based on examination of open-source
+ // code). It holds the start of the list of Attrs.
+ front [nAttrsInline]Attr
+
+ // The number of Attrs in front.
+ nFront int
+
+ // The list of Attrs except for those in front.
+ // Invariants:
+ // - len(back) > 0 iff nFront == len(front)
+ // - Unused array elements are zero. Used to detect mistakes.
+ back []Attr
+}
+
+// NewRecord creates a Record from the given arguments.
+// Use [Record.AddAttrs] to add attributes to the Record.
+//
+// NewRecord is intended for logging APIs that want to support a [Handler] as
+// a backend.
+func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
+ return Record{
+ Time: t,
+ Message: msg,
+ Level: level,
+ PC: pc,
+ }
+}
+
+// Clone returns a copy of the record with no shared state.
+// The original record and the clone can both be modified
+// without interfering with each other.
+func (r Record) Clone() Record {
+ r.back = slices.Clip(r.back) // prevent append from mutating shared array
+ return r
+}
+
+// NumAttrs returns the number of attributes in the Record.
+func (r Record) NumAttrs() int {
+ return r.nFront + len(r.back)
+}
+
+// Attrs calls f on each Attr in the Record.
+// Iteration stops if f returns false.
+func (r Record) Attrs(f func(Attr) bool) {
+ for i := 0; i < r.nFront; i++ {
+ if !f(r.front[i]) {
+ return
+ }
+ }
+ for _, a := range r.back {
+ if !f(a) {
+ return
+ }
+ }
+}
+
+// AddAttrs appends the given Attrs to the Record's list of Attrs.
+func (r *Record) AddAttrs(attrs ...Attr) {
+ n := copy(r.front[r.nFront:], attrs)
+ r.nFront += n
+ // Check if a copy was modified by slicing past the end
+ // and seeing if the Attr there is non-zero.
+ if cap(r.back) > len(r.back) {
+ end := r.back[:len(r.back)+1][len(r.back)]
+ if !end.isEmpty() {
+ panic("copies of a slog.Record were both modified")
+ }
+ }
+ r.back = append(r.back, attrs[n:]...)
+}
+
+// Add converts the args to Attrs as described in [Logger.Log],
+// then appends the Attrs to the Record's list of Attrs.
+func (r *Record) Add(args ...any) {
+ var a Attr
+ for len(args) > 0 {
+ a, args = argsToAttr(args)
+ if r.nFront < len(r.front) {
+ r.front[r.nFront] = a
+ r.nFront++
+ } else {
+ if r.back == nil {
+ r.back = make([]Attr, 0, countAttrs(args))
+ }
+ r.back = append(r.back, a)
+ }
+ }
+
+}
+
+// countAttrs returns the number of Attrs that would be created from args.
+func countAttrs(args []any) int {
+ n := 0
+ for i := 0; i < len(args); i++ {
+ n++
+ if _, ok := args[i].(string); ok {
+ i++
+ }
+ }
+ return n
+}
+
+const badKey = "!BADKEY"
+
+// argsToAttr turns a prefix of the nonempty args slice into an Attr
+// and returns the unconsumed portion of the slice.
+// If args[0] is an Attr, it returns it.
+// If args[0] is a string, it treats the first two elements as
+// a key-value pair.
+// Otherwise, it treats args[0] as a value with a missing key.
+func argsToAttr(args []any) (Attr, []any) {
+ switch x := args[0].(type) {
+ case string:
+ if len(args) == 1 {
+ return String(badKey, x), nil
+ }
+ return Any(x, args[1]), args[2:]
+
+ case Attr:
+ return x, args[1:]
+
+ default:
+ return Any(badKey, x), args[1:]
+ }
+}
+
+// Source describes the location of a line of source code.
+type Source struct {
+ // Function is the package path-qualified function name containing the
+ // source line. If non-empty, this string uniquely identifies a single
+ // function in the program. This may be the empty string if not known.
+ Function string `json:"function"`
+ // File and Line are the file name and line number (1-based) of the source
+ // line. These may be the empty string and zero, respectively, if not known.
+ File string `json:"file"`
+ Line int `json:"line"`
+}
+
+// attrs returns the non-zero fields of s as a slice of attrs.
+// It is similar to a LogValue method, but we don't want Source
+// to implement LogValuer because it would be resolved before
+// the ReplaceAttr function was called.
+func (s *Source) group() Value {
+ var as []Attr
+ if s.Function != "" {
+ as = append(as, String("function", s.Function))
+ }
+ if s.File != "" {
+ as = append(as, String("file", s.File))
+ }
+ if s.Line != 0 {
+ as = append(as, Int("line", s.Line))
+ }
+ return GroupValue(as...)
+}
+
+// source returns a Source for the log event.
+// If the Record was created without the necessary information,
+// or if the location is unavailable, it returns a non-nil *Source
+// with zero fields.
+func (r Record) source() *Source {
+ fs := runtime.CallersFrames([]uintptr{r.PC})
+ f, _ := fs.Next()
+ return &Source{
+ Function: f.Function,
+ File: f.File,
+ Line: f.Line,
+ }
+}
diff --git a/slog/record_test.go b/slog/record_test.go
new file mode 100644
index 0000000..f68af75
--- /dev/null
+++ b/slog/record_test.go
@@ -0,0 +1,154 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/exp/slices"
+)
+
+func TestRecordAttrs(t *testing.T) {
+ as := []Attr{Int("k1", 1), String("k2", "foo"), Int("k3", 3),
+ Int64("k4", -1), Float64("f", 3.1), Uint64("u", 999)}
+ r := newRecordWithAttrs(as)
+ if g, w := r.NumAttrs(), len(as); g != w {
+ t.Errorf("NumAttrs: got %d, want %d", g, w)
+ }
+ if got := attrsSlice(r); !attrsEqual(got, as) {
+ t.Errorf("got %v, want %v", got, as)
+ }
+
+ // Early return.
+ var got []Attr
+ r.Attrs(func(a Attr) bool {
+ got = append(got, a)
+ return len(got) < 2
+ })
+ want := as[:2]
+ if !attrsEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+}
+
+func TestRecordSource(t *testing.T) {
+ // Zero call depth => empty *Source.
+ for _, test := range []struct {
+ depth int
+ wantFunction string
+ wantFile string
+ wantLinePositive bool
+ }{
+ {0, "", "", false},
+ {-16, "", "", false},
+ {1, "golang.org/x/exp/slog.TestRecordSource", "record_test.go", true}, // 1: caller of NewRecord
+ {2, "testing.tRunner", "testing.go", true},
+ } {
+ var pc uintptr
+ if test.depth > 0 {
+ pc = callerPC(test.depth + 1)
+ }
+ r := NewRecord(time.Time{}, 0, "", pc)
+ got := r.source()
+ if i := strings.LastIndexByte(got.File, '/'); i >= 0 {
+ got.File = got.File[i+1:]
+ }
+ if got.Function != test.wantFunction || got.File != test.wantFile || (got.Line > 0) != test.wantLinePositive {
+ t.Errorf("depth %d: got (%q, %q, %d), want (%q, %q, %t)",
+ test.depth,
+ got.Function, got.File, got.Line,
+ test.wantFunction, test.wantFile, test.wantLinePositive)
+ }
+ }
+}
+
+func TestAliasingAndClone(t *testing.T) {
+ intAttrs := func(from, to int) []Attr {
+ var as []Attr
+ for i := from; i < to; i++ {
+ as = append(as, Int("k", i))
+ }
+ return as
+ }
+
+ check := func(r Record, want []Attr) {
+ t.Helper()
+ got := attrsSlice(r)
+ if !attrsEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ }
+
+ // Create a record whose Attrs overflow the inline array,
+ // creating a slice in r.back.
+ r1 := NewRecord(time.Time{}, 0, "", 0)
+ r1.AddAttrs(intAttrs(0, nAttrsInline+1)...)
+ // Ensure that r1.back's capacity exceeds its length.
+ b := make([]Attr, len(r1.back), len(r1.back)+1)
+ copy(b, r1.back)
+ r1.back = b
+ // Make a copy that shares state.
+ r2 := r1
+ // Adding to both should panic.
+ r1.AddAttrs(Int("p", 0))
+ if !panics(func() { r2.AddAttrs(Int("p", 1)) }) {
+ t.Error("expected panic")
+ }
+ r1Attrs := attrsSlice(r1)
+ // Adding to a clone is fine.
+ r2 = r1.Clone()
+ check(r2, r1Attrs)
+ r2.AddAttrs(Int("p", 2))
+ check(r1, r1Attrs) // r1 is unchanged
+ check(r2, append(slices.Clip(r1Attrs), Int("p", 2)))
+}
+
+func newRecordWithAttrs(as []Attr) Record {
+ r := NewRecord(time.Now(), LevelInfo, "", 0)
+ r.AddAttrs(as...)
+ return r
+}
+
+func attrsSlice(r Record) []Attr {
+ s := make([]Attr, 0, r.NumAttrs())
+ r.Attrs(func(a Attr) bool { s = append(s, a); return true })
+ return s
+}
+
+func attrsEqual(as1, as2 []Attr) bool {
+ return slices.EqualFunc(as1, as2, Attr.Equal)
+}
+
+// Currently, pc(2) takes over 400ns, which is too expensive
+// to call it for every log message.
+func BenchmarkPC(b *testing.B) {
+ for depth := 0; depth < 5; depth++ {
+ b.Run(strconv.Itoa(depth), func(b *testing.B) {
+ b.ReportAllocs()
+ var x uintptr
+ for i := 0; i < b.N; i++ {
+ x = callerPC(depth)
+ }
+ _ = x
+ })
+ }
+}
+
+func BenchmarkRecord(b *testing.B) {
+ const nAttrs = nAttrsInline * 10
+ var a Attr
+
+ for i := 0; i < b.N; i++ {
+ r := NewRecord(time.Time{}, LevelInfo, "", 0)
+ for j := 0; j < nAttrs; j++ {
+ r.AddAttrs(Int("k", j))
+ }
+ r.Attrs(func(b Attr) bool { a = b; return true })
+ }
+ _ = a
+}
diff --git a/slog/slogtest/example_test.go b/slog/slogtest/example_test.go
new file mode 100644
index 0000000..b56e167
--- /dev/null
+++ b/slog/slogtest/example_test.go
@@ -0,0 +1,40 @@
+package slogtest_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/slogtest"
+)
+
+// This example demonstrates one technique for testing a handler with this
+// package. The handler is given a [bytes.Buffer] to write to, and each line
+// of the resulting output is parsed.
+// For JSON output, [encoding/json.Unmarshal] produces a result in the desired
+// format when given a pointer to a map[string]any.
+func Example_parsing() {
+ var buf bytes.Buffer
+ h := slog.NewJSONHandler(&buf, nil)
+
+ results := func() []map[string]any {
+ var ms []map[string]any
+ for _, line := range bytes.Split(buf.Bytes(), []byte{'\n'}) {
+ if len(line) == 0 {
+ continue
+ }
+ var m map[string]any
+ if err := json.Unmarshal(line, &m); err != nil {
+ panic(err) // In a real test, use t.Fatal.
+ }
+ ms = append(ms, m)
+ }
+ return ms
+ }
+ err := slogtest.TestHandler(h, results)
+ fmt.Println(err)
+
+ // Output:
+ // <nil>
+}
diff --git a/slog/slogtest/slogtest.go b/slog/slogtest/slogtest.go
new file mode 100644
index 0000000..24b3513
--- /dev/null
+++ b/slog/slogtest/slogtest.go
@@ -0,0 +1,303 @@
+package slogtest
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "runtime"
+ "time"
+
+ "golang.org/x/exp/slog"
+)
+
+type testCase struct {
+ // If non-empty, explanation explains the violated constraint.
+ explanation string
+ // f executes a single log event using its argument logger.
+ // So that mkdescs.sh can generate the right description,
+ // the body of f must appear on a single line whose first
+ // non-whitespace characters are "l.".
+ f func(*slog.Logger)
+ // If mod is not nil, it is called to modify the Record
+ // generated by the Logger before it is passed to the Handler.
+ mod func(*slog.Record)
+ // checks is a list of checks to run on the result.
+ checks []check
+}
+
+// TestHandler tests a [slog.Handler].
+// If TestHandler finds any misbehaviors, it returns an error for each,
+// combined into a single error with errors.Join.
+//
+// TestHandler installs the given Handler in a [slog.Logger] and
+// makes several calls to the Logger's output methods.
+//
+// The results function is invoked after all such calls.
+// It should return a slice of map[string]any, one for each call to a Logger output method.
+// The keys and values of the map should correspond to the keys and values of the Handler's
+// output. Each group in the output should be represented as its own nested map[string]any.
+// The standard keys slog.TimeKey, slog.LevelKey and slog.MessageKey should be used.
+//
+// If the Handler outputs JSON, then calling [encoding/json.Unmarshal] with a `map[string]any`
+// will create the right data structure.
+//
+// If a Handler intentionally drops an attribute that is checked by a test,
+// then the results function should check for its absence and add it to the map it returns.
+func TestHandler(h slog.Handler, results func() []map[string]any) error {
+ cases := []testCase{
+ {
+ explanation: withSource("this test expects slog.TimeKey, slog.LevelKey and slog.MessageKey"),
+ f: func(l *slog.Logger) {
+ l.Info("message")
+ },
+ checks: []check{
+ hasKey(slog.TimeKey),
+ hasKey(slog.LevelKey),
+ hasAttr(slog.MessageKey, "message"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should output attributes passed to the logging function"),
+ f: func(l *slog.Logger) {
+ l.Info("message", "k", "v")
+ },
+ checks: []check{
+ hasAttr("k", "v"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should ignore an empty Attr"),
+ f: func(l *slog.Logger) {
+ l.Info("msg", "a", "b", "", nil, "c", "d")
+ },
+ checks: []check{
+ hasAttr("a", "b"),
+ missingKey(""),
+ hasAttr("c", "d"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should ignore a zero Record.Time"),
+ f: func(l *slog.Logger) {
+ l.Info("msg", "k", "v")
+ },
+ mod: func(r *slog.Record) { r.Time = time.Time{} },
+ checks: []check{
+ missingKey(slog.TimeKey),
+ },
+ },
+ {
+ explanation: withSource("a Handler should include the attributes from the WithAttrs method"),
+ f: func(l *slog.Logger) {
+ l.With("a", "b").Info("msg", "k", "v")
+ },
+ checks: []check{
+ hasAttr("a", "b"),
+ hasAttr("k", "v"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should handle Group attributes"),
+ f: func(l *slog.Logger) {
+ l.Info("msg", "a", "b", slog.Group("G", slog.String("c", "d")), "e", "f")
+ },
+ checks: []check{
+ hasAttr("a", "b"),
+ inGroup("G", hasAttr("c", "d")),
+ hasAttr("e", "f"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should ignore an empty group"),
+ f: func(l *slog.Logger) {
+ l.Info("msg", "a", "b", slog.Group("G"), "e", "f")
+ },
+ checks: []check{
+ hasAttr("a", "b"),
+ missingKey("G"),
+ hasAttr("e", "f"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should inline the Attrs of a group with an empty key"),
+ f: func(l *slog.Logger) {
+ l.Info("msg", "a", "b", slog.Group("", slog.String("c", "d")), "e", "f")
+
+ },
+ checks: []check{
+ hasAttr("a", "b"),
+ hasAttr("c", "d"),
+ hasAttr("e", "f"),
+ },
+ },
+ {
+ explanation: withSource("a Handler should handle the WithGroup method"),
+ f: func(l *slog.Logger) {
+ l.WithGroup("G").Info("msg", "a", "b")
+ },
+ checks: []check{
+ hasKey(slog.TimeKey),
+ hasKey(slog.LevelKey),
+ hasAttr(slog.MessageKey, "msg"),
+ missingKey("a"),
+ inGroup("G", hasAttr("a", "b")),
+ },
+ },
+ {
+ explanation: withSource("a Handler should handle multiple WithGroup and WithAttr calls"),
+ f: func(l *slog.Logger) {
+ l.With("a", "b").WithGroup("G").With("c", "d").WithGroup("H").Info("msg", "e", "f")
+ },
+ checks: []check{
+ hasKey(slog.TimeKey),
+ hasKey(slog.LevelKey),
+ hasAttr(slog.MessageKey, "msg"),
+ hasAttr("a", "b"),
+ inGroup("G", hasAttr("c", "d")),
+ inGroup("G", inGroup("H", hasAttr("e", "f"))),
+ },
+ },
+ {
+ explanation: withSource("a Handler should call Resolve on attribute values"),
+ f: func(l *slog.Logger) {
+ l.Info("msg", "k", &replace{"replaced"})
+ },
+ checks: []check{hasAttr("k", "replaced")},
+ },
+ {
+ explanation: withSource("a Handler should call Resolve on attribute values in groups"),
+ f: func(l *slog.Logger) {
+ l.Info("msg",
+ slog.Group("G",
+ slog.String("a", "v1"),
+ slog.Any("b", &replace{"v2"})))
+ },
+ checks: []check{
+ inGroup("G", hasAttr("a", "v1")),
+ inGroup("G", hasAttr("b", "v2")),
+ },
+ },
+ {
+ explanation: withSource("a Handler should call Resolve on attribute values from WithAttrs"),
+ f: func(l *slog.Logger) {
+ l = l.With("k", &replace{"replaced"})
+ l.Info("msg")
+ },
+ checks: []check{hasAttr("k", "replaced")},
+ },
+ {
+ explanation: withSource("a Handler should call Resolve on attribute values in groups from WithAttrs"),
+ f: func(l *slog.Logger) {
+ l = l.With(slog.Group("G",
+ slog.String("a", "v1"),
+ slog.Any("b", &replace{"v2"})))
+ l.Info("msg")
+ },
+ checks: []check{
+ inGroup("G", hasAttr("a", "v1")),
+ inGroup("G", hasAttr("b", "v2")),
+ },
+ },
+ }
+
+ // Run the handler on the test cases.
+ for _, c := range cases {
+ ht := h
+ if c.mod != nil {
+ ht = &wrapper{h, c.mod}
+ }
+ l := slog.New(ht)
+ c.f(l)
+ }
+
+ // Collect and check the results.
+ var errs []error
+ res := results()
+ if g, w := len(res), len(cases); g != w {
+ return fmt.Errorf("got %d results, want %d", g, w)
+ }
+ for i, got := range results() {
+ c := cases[i]
+ for _, check := range c.checks {
+ if p := check(got); p != "" {
+ errs = append(errs, fmt.Errorf("%s: %s", p, c.explanation))
+ }
+ }
+ }
+ return errorsJoin(errs...)
+}
+
+type check func(map[string]any) string
+
+func hasKey(key string) check {
+ return func(m map[string]any) string {
+ if _, ok := m[key]; !ok {
+ return fmt.Sprintf("missing key %q", key)
+ }
+ return ""
+ }
+}
+
+func missingKey(key string) check {
+ return func(m map[string]any) string {
+ if _, ok := m[key]; ok {
+ return fmt.Sprintf("unexpected key %q", key)
+ }
+ return ""
+ }
+}
+
+func hasAttr(key string, wantVal any) check {
+ return func(m map[string]any) string {
+ if s := hasKey(key)(m); s != "" {
+ return s
+ }
+ gotVal := m[key]
+ if !reflect.DeepEqual(gotVal, wantVal) {
+ return fmt.Sprintf("%q: got %#v, want %#v", key, gotVal, wantVal)
+ }
+ return ""
+ }
+}
+
+func inGroup(name string, c check) check {
+ return func(m map[string]any) string {
+ v, ok := m[name]
+ if !ok {
+ return fmt.Sprintf("missing group %q", name)
+ }
+ g, ok := v.(map[string]any)
+ if !ok {
+ return fmt.Sprintf("value for group %q is not map[string]any", name)
+ }
+ return c(g)
+ }
+}
+
+type wrapper struct {
+ slog.Handler
+ mod func(*slog.Record)
+}
+
+func (h *wrapper) Handle(ctx context.Context, r slog.Record) error {
+ h.mod(&r)
+ return h.Handler.Handle(ctx, r)
+}
+
+func withSource(s string) string {
+ _, file, line, ok := runtime.Caller(1)
+ if !ok {
+ panic("runtime.Caller failed")
+ }
+ return fmt.Sprintf("%s (%s:%d)", s, file, line)
+}
+
+type replace struct {
+ v any
+}
+
+func (r *replace) LogValue() slog.Value { return slog.AnyValue(r.v) }
+
+func (r *replace) String() string {
+ return fmt.Sprintf("<replace(%v)>", r.v)
+}
diff --git a/slog/slogtest/slogtest_119.go b/slog/slogtest/slogtest_119.go
new file mode 100644
index 0000000..21cd8b9
--- /dev/null
+++ b/slog/slogtest/slogtest_119.go
@@ -0,0 +1,27 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.19 && !go1.20
+
+package slogtest
+
+import (
+ "errors"
+ "strings"
+)
+
+func errorsJoin(errs ...error) error {
+ var b strings.Builder
+ for _, err := range errs {
+ if err != nil {
+ b.WriteString(err.Error())
+ b.WriteByte('\n')
+ }
+ }
+ s := b.String()
+ if len(s) == 0 {
+ return nil
+ }
+ return errors.New(s)
+}
diff --git a/slog/slogtest/slogtest_120.go b/slog/slogtest/slogtest_120.go
new file mode 100644
index 0000000..dfb3e41
--- /dev/null
+++ b/slog/slogtest/slogtest_120.go
@@ -0,0 +1,11 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.20
+
+package slogtest
+
+import "errors"
+
+var errorsJoin = errors.Join
diff --git a/slog/slogtest_test.go b/slog/slogtest_test.go
new file mode 100644
index 0000000..2eaf980
--- /dev/null
+++ b/slog/slogtest_test.go
@@ -0,0 +1,105 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "strings"
+ "testing"
+
+ "golang.org/x/exp/slog"
+ "golang.org/x/exp/slog/slogtest"
+)
+
+func TestSlogtest(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ new func(io.Writer) slog.Handler
+ parse func([]byte) (map[string]any, error)
+ }{
+ {"JSON", func(w io.Writer) slog.Handler { return slog.NewJSONHandler(w, nil) }, parseJSON},
+ {"Text", func(w io.Writer) slog.Handler { return slog.NewTextHandler(w, nil) }, parseText},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ var buf bytes.Buffer
+ h := test.new(&buf)
+ results := func() []map[string]any {
+ ms, err := parseLines(buf.Bytes(), test.parse)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return ms
+ }
+ if err := slogtest.TestHandler(h, results); err != nil {
+ t.Fatal(err)
+ }
+ })
+ }
+}
+
+func parseLines(src []byte, parse func([]byte) (map[string]any, error)) ([]map[string]any, error) {
+ var records []map[string]any
+ for _, line := range bytes.Split(src, []byte{'\n'}) {
+ if len(line) == 0 {
+ continue
+ }
+ m, err := parse(line)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", string(line), err)
+ }
+ records = append(records, m)
+ }
+ return records, nil
+}
+
+func parseJSON(bs []byte) (map[string]any, error) {
+ var m map[string]any
+ if err := json.Unmarshal(bs, &m); err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+// parseText parses the output of a single call to TextHandler.Handle.
+// It can parse the output of the tests in this package,
+// but it doesn't handle quoted keys or values.
+// It doesn't need to handle all cases, because slogtest deliberately
+// uses simple inputs so handler writers can focus on testing
+// handler behavior, not parsing.
+func parseText(bs []byte) (map[string]any, error) {
+ top := map[string]any{}
+ s := string(bytes.TrimSpace(bs))
+ for len(s) > 0 {
+ kv, rest, _ := strings.Cut(s, " ") // assumes exactly one space between attrs
+ k, value, found := strings.Cut(kv, "=")
+ if !found {
+ return nil, fmt.Errorf("no '=' in %q", kv)
+ }
+ keys := strings.Split(k, ".")
+ // Populate a tree of maps for a dotted path such as "a.b.c=x".
+ m := top
+ for _, key := range keys[:len(keys)-1] {
+ x, ok := m[key]
+ var m2 map[string]any
+ if !ok {
+ m2 = map[string]any{}
+ m[key] = m2
+ } else {
+ m2, ok = x.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("value for %q in composite key %q is not map[string]any", key, k)
+
+ }
+ }
+ m = m2
+ }
+ m[keys[len(keys)-1]] = value
+ s = rest
+ }
+ return top, nil
+}
diff --git a/slog/text_handler.go b/slog/text_handler.go
new file mode 100644
index 0000000..75b66b7
--- /dev/null
+++ b/slog/text_handler.go
@@ -0,0 +1,161 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "context"
+ "encoding"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+ "unicode"
+ "unicode/utf8"
+)
+
+// TextHandler is a Handler that writes Records to an io.Writer as a
+// sequence of key=value pairs separated by spaces and followed by a newline.
+type TextHandler struct {
+ *commonHandler
+}
+
+// NewTextHandler creates a TextHandler that writes to w,
+// using the given options.
+// If opts is nil, the default options are used.
+func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
+ if opts == nil {
+ opts = &HandlerOptions{}
+ }
+ return &TextHandler{
+ &commonHandler{
+ json: false,
+ w: w,
+ opts: *opts,
+ },
+ }
+}
+
+// Enabled reports whether the handler handles records at the given level.
+// The handler ignores records whose level is lower.
+func (h *TextHandler) Enabled(_ context.Context, level Level) bool {
+ return h.commonHandler.enabled(level)
+}
+
+// WithAttrs returns a new TextHandler whose attributes consists
+// of h's attributes followed by attrs.
+func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
+ return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
+}
+
+func (h *TextHandler) WithGroup(name string) Handler {
+ return &TextHandler{commonHandler: h.commonHandler.withGroup(name)}
+}
+
+// Handle formats its argument Record as a single line of space-separated
+// key=value items.
+//
+// If the Record's time is zero, the time is omitted.
+// Otherwise, the key is "time"
+// and the value is output in RFC3339 format with millisecond precision.
+//
+// If the Record's level is zero, the level is omitted.
+// Otherwise, the key is "level"
+// and the value of [Level.String] is output.
+//
+// If the AddSource option is set and source information is available,
+// the key is "source" and the value is output as FILE:LINE.
+//
+// The message's key is "msg".
+//
+// To modify these or other attributes, or remove them from the output, use
+// [HandlerOptions.ReplaceAttr].
+//
+// If a value implements [encoding.TextMarshaler], the result of MarshalText is
+// written. Otherwise, the result of fmt.Sprint is written.
+//
+// Keys and values are quoted with [strconv.Quote] if they contain Unicode space
+// characters, non-printing characters, '"' or '='.
+//
+// Keys inside groups consist of components (keys or group names) separated by
+// dots. No further escaping is performed.
+// Thus there is no way to determine from the key "a.b.c" whether there
+// are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c",
+// or single group "a" and a key "b.c".
+// If it is necessary to reconstruct the group structure of a key
+// even in the presence of dots inside components, use
+// [HandlerOptions.ReplaceAttr] to encode that information in the key.
+//
+// Each call to Handle results in a single serialized call to
+// io.Writer.Write.
+func (h *TextHandler) Handle(_ context.Context, r Record) error {
+ return h.commonHandler.handle(r)
+}
+
+func appendTextValue(s *handleState, v Value) error {
+ switch v.Kind() {
+ case KindString:
+ s.appendString(v.str())
+ case KindTime:
+ s.appendTime(v.time())
+ case KindAny:
+ if tm, ok := v.any.(encoding.TextMarshaler); ok {
+ data, err := tm.MarshalText()
+ if err != nil {
+ return err
+ }
+ // TODO: avoid the conversion to string.
+ s.appendString(string(data))
+ return nil
+ }
+ if bs, ok := byteSlice(v.any); ok {
+ // As of Go 1.19, this only allocates for strings longer than 32 bytes.
+ s.buf.WriteString(strconv.Quote(string(bs)))
+ return nil
+ }
+ s.appendString(fmt.Sprintf("%+v", v.Any()))
+ default:
+ *s.buf = v.append(*s.buf)
+ }
+ return nil
+}
+
+// byteSlice returns its argument as a []byte if the argument's
+// underlying type is []byte, along with a second return value of true.
+// Otherwise it returns nil, false.
+func byteSlice(a any) ([]byte, bool) {
+ if bs, ok := a.([]byte); ok {
+ return bs, true
+ }
+ // Like Printf's %s, we allow both the slice type and the byte element type to be named.
+ t := reflect.TypeOf(a)
+ if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
+ return reflect.ValueOf(a).Bytes(), true
+ }
+ return nil, false
+}
+
+func needsQuoting(s string) bool {
+ if len(s) == 0 {
+ return true
+ }
+ for i := 0; i < len(s); {
+ b := s[i]
+ if b < utf8.RuneSelf {
+ // Quote anything except a backslash that would need quoting in a
+ // JSON string, as well as space and '='
+ if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
+ return true
+ }
+ i++
+ continue
+ }
+ r, size := utf8.DecodeRuneInString(s[i:])
+ if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
+ return true
+ }
+ i += size
+ }
+ return false
+}
diff --git a/slog/text_handler_test.go b/slog/text_handler_test.go
new file mode 100644
index 0000000..2d674bb
--- /dev/null
+++ b/slog/text_handler_test.go
@@ -0,0 +1,172 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+ "testing"
+ "time"
+)
+
+var testTime = time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC)
+
+func TestTextHandler(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ attr Attr
+ wantKey, wantVal string
+ }{
+ {
+ "unquoted",
+ Int("a", 1),
+ "a", "1",
+ },
+ {
+ "quoted",
+ String("x = y", `qu"o`),
+ `"x = y"`, `"qu\"o"`,
+ },
+ {
+ "String method",
+ Any("name", name{"Ren", "Hoek"}),
+ `name`, `"Hoek, Ren"`,
+ },
+ {
+ "struct",
+ Any("x", &struct{ A, b int }{A: 1, b: 2}),
+ `x`, `"&{A:1 b:2}"`,
+ },
+ {
+ "TextMarshaler",
+ Any("t", text{"abc"}),
+ `t`, `"text{\"abc\"}"`,
+ },
+ {
+ "TextMarshaler error",
+ Any("t", text{""}),
+ `t`, `"!ERROR:text: empty string"`,
+ },
+ {
+ "nil value",
+ Any("a", nil),
+ `a`, `<nil>`,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ for _, opts := range []struct {
+ name string
+ opts HandlerOptions
+ wantPrefix string
+ modKey func(string) string
+ }{
+ {
+ "none",
+ HandlerOptions{},
+ `time=2000-01-02T03:04:05.000Z level=INFO msg="a message"`,
+ func(s string) string { return s },
+ },
+ {
+ "replace",
+ HandlerOptions{ReplaceAttr: upperCaseKey},
+ `TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG="a message"`,
+ strings.ToUpper,
+ },
+ } {
+ t.Run(opts.name, func(t *testing.T) {
+ var buf bytes.Buffer
+ h := NewTextHandler(&buf, &opts.opts)
+ r := NewRecord(testTime, LevelInfo, "a message", 0)
+ r.AddAttrs(test.attr)
+ if err := h.Handle(context.Background(), r); err != nil {
+ t.Fatal(err)
+ }
+ got := buf.String()
+ // Remove final newline.
+ got = got[:len(got)-1]
+ want := opts.wantPrefix + " " + opts.modKey(test.wantKey) + "=" + test.wantVal
+ if got != want {
+ t.Errorf("\ngot %s\nwant %s", got, want)
+ }
+ })
+ }
+ })
+ }
+}
+
+// for testing fmt.Sprint
+type name struct {
+ First, Last string
+}
+
+func (n name) String() string { return n.Last + ", " + n.First }
+
+// for testing TextMarshaler
+type text struct {
+ s string
+}
+
+func (t text) String() string { return t.s } // should be ignored
+
+func (t text) MarshalText() ([]byte, error) {
+ if t.s == "" {
+ return nil, errors.New("text: empty string")
+ }
+ return []byte(fmt.Sprintf("text{%q}", t.s)), nil
+}
+
+func TestTextHandlerPreformatted(t *testing.T) {
+ var buf bytes.Buffer
+ var h Handler = NewTextHandler(&buf, nil)
+ h = h.WithAttrs([]Attr{Duration("dur", time.Minute), Bool("b", true)})
+ // Also test omitting time.
+ r := NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "m", 0)
+ r.AddAttrs(Int("a", 1))
+ if err := h.Handle(context.Background(), r); err != nil {
+ t.Fatal(err)
+ }
+ got := strings.TrimSuffix(buf.String(), "\n")
+ want := `level=INFO msg=m dur=1m0s b=true a=1`
+ if got != want {
+ t.Errorf("got %s, want %s", got, want)
+ }
+}
+
+func TestTextHandlerAlloc(t *testing.T) {
+ r := NewRecord(time.Now(), LevelInfo, "msg", 0)
+ for i := 0; i < 10; i++ {
+ r.AddAttrs(Int("x = y", i))
+ }
+ var h Handler = NewTextHandler(io.Discard, nil)
+ wantAllocs(t, 0, func() { h.Handle(context.Background(), r) })
+
+ h = h.WithGroup("s")
+ r.AddAttrs(Group("g", Int("a", 1)))
+ wantAllocs(t, 0, func() { h.Handle(context.Background(), r) })
+}
+
+func TestNeedsQuoting(t *testing.T) {
+ for _, test := range []struct {
+ in string
+ want bool
+ }{
+ {"", true},
+ {"ab", false},
+ {"a=b", true},
+ {`"ab"`, true},
+ {"\a\b", true},
+ {"a\tb", true},
+ {"Β΅Γ₯Ο€", false},
+ } {
+ got := needsQuoting(test.in)
+ if got != test.want {
+ t.Errorf("%q: got %t, want %t", test.in, got, test.want)
+ }
+ }
+}
diff --git a/slog/value.go b/slog/value.go
new file mode 100644
index 0000000..3550c46
--- /dev/null
+++ b/slog/value.go
@@ -0,0 +1,456 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "fmt"
+ "math"
+ "runtime"
+ "strconv"
+ "strings"
+ "time"
+ "unsafe"
+
+ "golang.org/x/exp/slices"
+)
+
+// A Value can represent any Go value, but unlike type any,
+// it can represent most small values without an allocation.
+// The zero Value corresponds to nil.
+type Value struct {
+ _ [0]func() // disallow ==
+ // num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
+ // the string length for KindString, and nanoseconds since the epoch for KindTime.
+ num uint64
+ // If any is of type Kind, then the value is in num as described above.
+ // If any is of type *time.Location, then the Kind is Time and time.Time value
+ // can be constructed from the Unix nanos in num and the location (monotonic time
+ // is not preserved).
+ // If any is of type stringptr, then the Kind is String and the string value
+ // consists of the length in num and the pointer in any.
+ // Otherwise, the Kind is Any and any is the value.
+ // (This implies that Attrs cannot store values of type Kind, *time.Location
+ // or stringptr.)
+ any any
+}
+
+// Kind is the kind of a Value.
+type Kind int
+
+// The following list is sorted alphabetically, but it's also important that
+// KindAny is 0 so that a zero Value represents nil.
+
+const (
+ KindAny Kind = iota
+ KindBool
+ KindDuration
+ KindFloat64
+ KindInt64
+ KindString
+ KindTime
+ KindUint64
+ KindGroup
+ KindLogValuer
+)
+
+var kindStrings = []string{
+ "Any",
+ "Bool",
+ "Duration",
+ "Float64",
+ "Int64",
+ "String",
+ "Time",
+ "Uint64",
+ "Group",
+ "LogValuer",
+}
+
+func (k Kind) String() string {
+ if k >= 0 && int(k) < len(kindStrings) {
+ return kindStrings[k]
+ }
+ return "<unknown slog.Kind>"
+}
+
+// Unexported version of Kind, just so we can store Kinds in Values.
+// (No user-provided value has this type.)
+type kind Kind
+
+// Kind returns v's Kind.
+func (v Value) Kind() Kind {
+ switch x := v.any.(type) {
+ case Kind:
+ return x
+ case stringptr:
+ return KindString
+ case timeLocation:
+ return KindTime
+ case groupptr:
+ return KindGroup
+ case LogValuer:
+ return KindLogValuer
+ case kind: // a kind is just a wrapper for a Kind
+ return KindAny
+ default:
+ return KindAny
+ }
+}
+
+//////////////// Constructors
+
+// IntValue returns a Value for an int.
+func IntValue(v int) Value {
+ return Int64Value(int64(v))
+}
+
+// Int64Value returns a Value for an int64.
+func Int64Value(v int64) Value {
+ return Value{num: uint64(v), any: KindInt64}
+}
+
+// Uint64Value returns a Value for a uint64.
+func Uint64Value(v uint64) Value {
+ return Value{num: v, any: KindUint64}
+}
+
+// Float64Value returns a Value for a floating-point number.
+func Float64Value(v float64) Value {
+ return Value{num: math.Float64bits(v), any: KindFloat64}
+}
+
+// BoolValue returns a Value for a bool.
+func BoolValue(v bool) Value {
+ u := uint64(0)
+ if v {
+ u = 1
+ }
+ return Value{num: u, any: KindBool}
+}
+
+// Unexported version of *time.Location, just so we can store *time.Locations in
+// Values. (No user-provided value has this type.)
+type timeLocation *time.Location
+
+// TimeValue returns a Value for a time.Time.
+// It discards the monotonic portion.
+func TimeValue(v time.Time) Value {
+ if v.IsZero() {
+ // UnixNano on the zero time is undefined, so represent the zero time
+ // with a nil *time.Location instead. time.Time.Location method never
+ // returns nil, so a Value with any == timeLocation(nil) cannot be
+ // mistaken for any other Value, time.Time or otherwise.
+ return Value{any: timeLocation(nil)}
+ }
+ return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
+}
+
+// DurationValue returns a Value for a time.Duration.
+func DurationValue(v time.Duration) Value {
+ return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
+}
+
+// AnyValue returns a Value for the supplied value.
+//
+// If the supplied value is of type Value, it is returned
+// unmodified.
+//
+// Given a value of one of Go's predeclared string, bool, or
+// (non-complex) numeric types, AnyValue returns a Value of kind
+// String, Bool, Uint64, Int64, or Float64. The width of the
+// original numeric type is not preserved.
+//
+// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
+// KindTime or KindDuration. The monotonic time is not preserved.
+//
+// For nil, or values of all other types, including named types whose
+// underlying type is numeric, AnyValue returns a value of kind KindAny.
+func AnyValue(v any) Value {
+ switch v := v.(type) {
+ case string:
+ return StringValue(v)
+ case int:
+ return Int64Value(int64(v))
+ case uint:
+ return Uint64Value(uint64(v))
+ case int64:
+ return Int64Value(v)
+ case uint64:
+ return Uint64Value(v)
+ case bool:
+ return BoolValue(v)
+ case time.Duration:
+ return DurationValue(v)
+ case time.Time:
+ return TimeValue(v)
+ case uint8:
+ return Uint64Value(uint64(v))
+ case uint16:
+ return Uint64Value(uint64(v))
+ case uint32:
+ return Uint64Value(uint64(v))
+ case uintptr:
+ return Uint64Value(uint64(v))
+ case int8:
+ return Int64Value(int64(v))
+ case int16:
+ return Int64Value(int64(v))
+ case int32:
+ return Int64Value(int64(v))
+ case float64:
+ return Float64Value(v)
+ case float32:
+ return Float64Value(float64(v))
+ case []Attr:
+ return GroupValue(v...)
+ case Kind:
+ return Value{any: kind(v)}
+ case Value:
+ return v
+ default:
+ return Value{any: v}
+ }
+}
+
+//////////////// Accessors
+
+// Any returns v's value as an any.
+func (v Value) Any() any {
+ switch v.Kind() {
+ case KindAny:
+ if k, ok := v.any.(kind); ok {
+ return Kind(k)
+ }
+ return v.any
+ case KindLogValuer:
+ return v.any
+ case KindGroup:
+ return v.group()
+ case KindInt64:
+ return int64(v.num)
+ case KindUint64:
+ return v.num
+ case KindFloat64:
+ return v.float()
+ case KindString:
+ return v.str()
+ case KindBool:
+ return v.bool()
+ case KindDuration:
+ return v.duration()
+ case KindTime:
+ return v.time()
+ default:
+ panic(fmt.Sprintf("bad kind: %s", v.Kind()))
+ }
+}
+
+// Int64 returns v's value as an int64. It panics
+// if v is not a signed integer.
+func (v Value) Int64() int64 {
+ if g, w := v.Kind(), KindInt64; g != w {
+ panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
+ }
+ return int64(v.num)
+}
+
+// Uint64 returns v's value as a uint64. It panics
+// if v is not an unsigned integer.
+func (v Value) Uint64() uint64 {
+ if g, w := v.Kind(), KindUint64; g != w {
+ panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
+ }
+ return v.num
+}
+
+// Bool returns v's value as a bool. It panics
+// if v is not a bool.
+func (v Value) Bool() bool {
+ if g, w := v.Kind(), KindBool; g != w {
+ panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
+ }
+ return v.bool()
+}
+
+func (v Value) bool() bool {
+ return v.num == 1
+}
+
+// Duration returns v's value as a time.Duration. It panics
+// if v is not a time.Duration.
+func (v Value) Duration() time.Duration {
+ if g, w := v.Kind(), KindDuration; g != w {
+ panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
+ }
+
+ return v.duration()
+}
+
+func (v Value) duration() time.Duration {
+ return time.Duration(int64(v.num))
+}
+
+// Float64 returns v's value as a float64. It panics
+// if v is not a float64.
+func (v Value) Float64() float64 {
+ if g, w := v.Kind(), KindFloat64; g != w {
+ panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
+ }
+
+ return v.float()
+}
+
+func (v Value) float() float64 {
+ return math.Float64frombits(v.num)
+}
+
+// Time returns v's value as a time.Time. It panics
+// if v is not a time.Time.
+func (v Value) Time() time.Time {
+ if g, w := v.Kind(), KindTime; g != w {
+ panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
+ }
+ return v.time()
+}
+
+func (v Value) time() time.Time {
+ loc := v.any.(timeLocation)
+ if loc == nil {
+ return time.Time{}
+ }
+ return time.Unix(0, int64(v.num)).In(loc)
+}
+
+// LogValuer returns v's value as a LogValuer. It panics
+// if v is not a LogValuer.
+func (v Value) LogValuer() LogValuer {
+ return v.any.(LogValuer)
+}
+
+// Group returns v's value as a []Attr.
+// It panics if v's Kind is not KindGroup.
+func (v Value) Group() []Attr {
+ if sp, ok := v.any.(groupptr); ok {
+ return unsafe.Slice((*Attr)(sp), v.num)
+ }
+ panic("Group: bad kind")
+}
+
+func (v Value) group() []Attr {
+ return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
+}
+
+//////////////// Other
+
+// Equal reports whether v and w represent the same Go value.
+func (v Value) Equal(w Value) bool {
+ k1 := v.Kind()
+ k2 := w.Kind()
+ if k1 != k2 {
+ return false
+ }
+ switch k1 {
+ case KindInt64, KindUint64, KindBool, KindDuration:
+ return v.num == w.num
+ case KindString:
+ return v.str() == w.str()
+ case KindFloat64:
+ return v.float() == w.float()
+ case KindTime:
+ return v.time().Equal(w.time())
+ case KindAny, KindLogValuer:
+ return v.any == w.any // may panic if non-comparable
+ case KindGroup:
+ return slices.EqualFunc(v.group(), w.group(), Attr.Equal)
+ default:
+ panic(fmt.Sprintf("bad kind: %s", k1))
+ }
+}
+
+// append appends a text representation of v to dst.
+// v is formatted as with fmt.Sprint.
+func (v Value) append(dst []byte) []byte {
+ switch v.Kind() {
+ case KindString:
+ return append(dst, v.str()...)
+ case KindInt64:
+ return strconv.AppendInt(dst, int64(v.num), 10)
+ case KindUint64:
+ return strconv.AppendUint(dst, v.num, 10)
+ case KindFloat64:
+ return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
+ case KindBool:
+ return strconv.AppendBool(dst, v.bool())
+ case KindDuration:
+ return append(dst, v.duration().String()...)
+ case KindTime:
+ return append(dst, v.time().String()...)
+ case KindGroup:
+ return fmt.Append(dst, v.group())
+ case KindAny, KindLogValuer:
+ return fmt.Append(dst, v.any)
+ default:
+ panic(fmt.Sprintf("bad kind: %s", v.Kind()))
+ }
+}
+
+// A LogValuer is any Go value that can convert itself into a Value for logging.
+//
+// This mechanism may be used to defer expensive operations until they are
+// needed, or to expand a single value into a sequence of components.
+type LogValuer interface {
+ LogValue() Value
+}
+
+const maxLogValues = 100
+
+// Resolve repeatedly calls LogValue on v while it implements LogValuer,
+// and returns the result.
+// If v resolves to a group, the group's attributes' values are not recursively
+// resolved.
+// If the number of LogValue calls exceeds a threshold, a Value containing an
+// error is returned.
+// Resolve's return value is guaranteed not to be of Kind KindLogValuer.
+func (v Value) Resolve() (rv Value) {
+ orig := v
+ defer func() {
+ if r := recover(); r != nil {
+ rv = AnyValue(fmt.Errorf("LogValue panicked\n%s", stack(3, 5)))
+ }
+ }()
+
+ for i := 0; i < maxLogValues; i++ {
+ if v.Kind() != KindLogValuer {
+ return v
+ }
+ v = v.LogValuer().LogValue()
+ }
+ err := fmt.Errorf("LogValue called too many times on Value of type %T", orig.Any())
+ return AnyValue(err)
+}
+
+func stack(skip, nFrames int) string {
+ pcs := make([]uintptr, nFrames+1)
+ n := runtime.Callers(skip+1, pcs)
+ if n == 0 {
+ return "(no stack)"
+ }
+ frames := runtime.CallersFrames(pcs[:n])
+ var b strings.Builder
+ i := 0
+ for {
+ frame, more := frames.Next()
+ fmt.Fprintf(&b, "called from %s (%s:%d)\n", frame.Function, frame.File, frame.Line)
+ if !more {
+ break
+ }
+ i++
+ if i >= nFrames {
+ fmt.Fprintf(&b, "(rest of stack elided)\n")
+ break
+ }
+ }
+ return b.String()
+}
diff --git a/slog/value_119.go b/slog/value_119.go
new file mode 100644
index 0000000..29b0d73
--- /dev/null
+++ b/slog/value_119.go
@@ -0,0 +1,53 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.19 && !go1.20
+
+package slog
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type (
+ stringptr unsafe.Pointer // used in Value.any when the Value is a string
+ groupptr unsafe.Pointer // used in Value.any when the Value is a []Attr
+)
+
+// StringValue returns a new Value for a string.
+func StringValue(value string) Value {
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&value))
+ return Value{num: uint64(hdr.Len), any: stringptr(hdr.Data)}
+}
+
+func (v Value) str() string {
+ var s string
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ hdr.Data = uintptr(v.any.(stringptr))
+ hdr.Len = int(v.num)
+ return s
+}
+
+// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
+// the methods Int64, Float64, and so on, which panic if v is of the
+// wrong kind, String never panics.
+func (v Value) String() string {
+ if sp, ok := v.any.(stringptr); ok {
+ // Inlining this code makes a huge difference.
+ var s string
+ hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ hdr.Data = uintptr(sp)
+ hdr.Len = int(v.num)
+ return s
+ }
+ return string(v.append(nil))
+}
+
+// GroupValue returns a new Value for a list of Attrs.
+// The caller must not subsequently mutate the argument slice.
+func GroupValue(as ...Attr) Value {
+ hdr := (*reflect.SliceHeader)(unsafe.Pointer(&as))
+ return Value{num: uint64(hdr.Len), any: groupptr(hdr.Data)}
+}
diff --git a/slog/value_120.go b/slog/value_120.go
new file mode 100644
index 0000000..f7d4c09
--- /dev/null
+++ b/slog/value_120.go
@@ -0,0 +1,39 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.20
+
+package slog
+
+import "unsafe"
+
+type (
+ stringptr *byte // used in Value.any when the Value is a string
+ groupptr *Attr // used in Value.any when the Value is a []Attr
+)
+
+// StringValue returns a new Value for a string.
+func StringValue(value string) Value {
+ return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
+}
+
+// GroupValue returns a new Value for a list of Attrs.
+// The caller must not subsequently mutate the argument slice.
+func GroupValue(as ...Attr) Value {
+ return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
+}
+
+// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
+// the methods Int64, Float64, and so on, which panic if v is of the
+// wrong kind, String never panics.
+func (v Value) String() string {
+ if sp, ok := v.any.(stringptr); ok {
+ return unsafe.String(sp, v.num)
+ }
+ return string(v.append(nil))
+}
+
+func (v Value) str() string {
+ return unsafe.String(v.any.(stringptr), v.num)
+}
diff --git a/slog/value_access_benchmark_test.go b/slog/value_access_benchmark_test.go
new file mode 100644
index 0000000..3bd7071
--- /dev/null
+++ b/slog/value_access_benchmark_test.go
@@ -0,0 +1,215 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Benchmark for accessing Value values.
+
+package slog
+
+import (
+ "testing"
+ "time"
+)
+
+// The "As" form is the slowest.
+// The switch-panic and visitor times are almost the same.
+// BenchmarkDispatch/switch-checked-8 8669427 137.7 ns/op
+// BenchmarkDispatch/As-8 8212087 145.3 ns/op
+// BenchmarkDispatch/Visit-8 8926146 135.3 ns/op
+func BenchmarkDispatch(b *testing.B) {
+ vs := []Value{
+ Int64Value(32768),
+ Uint64Value(0xfacecafe),
+ StringValue("anything"),
+ BoolValue(true),
+ Float64Value(1.2345),
+ DurationValue(time.Second),
+ AnyValue(b),
+ }
+ var (
+ ii int64
+ s string
+ bb bool
+ u uint64
+ d time.Duration
+ f float64
+ a any
+ )
+ b.Run("switch-checked", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, v := range vs {
+ switch v.Kind() {
+ case KindString:
+ s = v.String()
+ case KindInt64:
+ ii = v.Int64()
+ case KindUint64:
+ u = v.Uint64()
+ case KindFloat64:
+ f = v.Float64()
+ case KindBool:
+ bb = v.Bool()
+ case KindDuration:
+ d = v.Duration()
+ case KindAny:
+ a = v.Any()
+ default:
+ panic("bad kind")
+ }
+ }
+ }
+ _ = ii
+ _ = s
+ _ = bb
+ _ = u
+ _ = d
+ _ = f
+ _ = a
+
+ })
+ b.Run("As", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, kv := range vs {
+ if v, ok := kv.AsString(); ok {
+ s = v
+ } else if v, ok := kv.AsInt64(); ok {
+ ii = v
+ } else if v, ok := kv.AsUint64(); ok {
+ u = v
+ } else if v, ok := kv.AsFloat64(); ok {
+ f = v
+ } else if v, ok := kv.AsBool(); ok {
+ bb = v
+ } else if v, ok := kv.AsDuration(); ok {
+ d = v
+ } else if v, ok := kv.AsAny(); ok {
+ a = v
+ } else {
+ panic("bad kind")
+ }
+ }
+ }
+ _ = ii
+ _ = s
+ _ = bb
+ _ = u
+ _ = d
+ _ = f
+ _ = a
+ })
+
+ b.Run("Visit", func(b *testing.B) {
+ v := &setVisitor{}
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, kv := range vs {
+ kv.Visit(v)
+ }
+ }
+ })
+}
+
+type setVisitor struct {
+ i int64
+ s string
+ b bool
+ u uint64
+ d time.Duration
+ f float64
+ a any
+}
+
+func (v *setVisitor) String(s string) { v.s = s }
+func (v *setVisitor) Int64(i int64) { v.i = i }
+func (v *setVisitor) Uint64(x uint64) { v.u = x }
+func (v *setVisitor) Float64(x float64) { v.f = x }
+func (v *setVisitor) Bool(x bool) { v.b = x }
+func (v *setVisitor) Duration(x time.Duration) { v.d = x }
+func (v *setVisitor) Any(x any) { v.a = x }
+
+// When dispatching on all types, the "As" functions are slightly slower
+// than switching on the kind and then calling a function that checks
+// the kind again. See BenchmarkDispatch above.
+
+func (a Value) AsString() (string, bool) {
+ if a.Kind() == KindString {
+ return a.str(), true
+ }
+ return "", false
+}
+
+func (a Value) AsInt64() (int64, bool) {
+ if a.Kind() == KindInt64 {
+ return int64(a.num), true
+ }
+ return 0, false
+}
+
+func (a Value) AsUint64() (uint64, bool) {
+ if a.Kind() == KindUint64 {
+ return a.num, true
+ }
+ return 0, false
+}
+
+func (a Value) AsFloat64() (float64, bool) {
+ if a.Kind() == KindFloat64 {
+ return a.float(), true
+ }
+ return 0, false
+}
+
+func (a Value) AsBool() (bool, bool) {
+ if a.Kind() == KindBool {
+ return a.bool(), true
+ }
+ return false, false
+}
+
+func (a Value) AsDuration() (time.Duration, bool) {
+ if a.Kind() == KindDuration {
+ return a.duration(), true
+ }
+ return 0, false
+}
+
+func (a Value) AsAny() (any, bool) {
+ if a.Kind() == KindAny {
+ return a.any, true
+ }
+ return nil, false
+}
+
+// Problem: adding a type means adding a method, which is a breaking change.
+// Using an unexported method to force embedding will make programs compile,
+// But they will panic at runtime when we call the new method.
+type Visitor interface {
+ String(string)
+ Int64(int64)
+ Uint64(uint64)
+ Float64(float64)
+ Bool(bool)
+ Duration(time.Duration)
+ Any(any)
+}
+
+func (a Value) Visit(v Visitor) {
+ switch a.Kind() {
+ case KindString:
+ v.String(a.str())
+ case KindInt64:
+ v.Int64(int64(a.num))
+ case KindUint64:
+ v.Uint64(a.num)
+ case KindBool:
+ v.Bool(a.bool())
+ case KindFloat64:
+ v.Float64(a.float())
+ case KindDuration:
+ v.Duration(a.duration())
+ case KindAny:
+ v.Any(a.any)
+ default:
+ panic("bad kind")
+ }
+}
diff --git a/slog/value_test.go b/slog/value_test.go
new file mode 100644
index 0000000..1196e75
--- /dev/null
+++ b/slog/value_test.go
@@ -0,0 +1,241 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package slog
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+ "unsafe"
+)
+
+func TestValueEqual(t *testing.T) {
+ var x, y int
+ vals := []Value{
+ {},
+ Int64Value(1),
+ Int64Value(2),
+ Float64Value(3.5),
+ Float64Value(3.7),
+ BoolValue(true),
+ BoolValue(false),
+ TimeValue(testTime),
+ AnyValue(&x),
+ AnyValue(&y),
+ GroupValue(Bool("b", true), Int("i", 3)),
+ }
+ for i, v1 := range vals {
+ for j, v2 := range vals {
+ got := v1.Equal(v2)
+ want := i == j
+ if got != want {
+ t.Errorf("%v.Equal(%v): got %t, want %t", v1, v2, got, want)
+ }
+ }
+ }
+}
+
+func panics(f func()) (b bool) {
+ defer func() {
+ if x := recover(); x != nil {
+ b = true
+ }
+ }()
+ f()
+ return false
+}
+
+func TestValueString(t *testing.T) {
+ for _, test := range []struct {
+ v Value
+ want string
+ }{
+ {Int64Value(-3), "-3"},
+ {Float64Value(.15), "0.15"},
+ {BoolValue(true), "true"},
+ {StringValue("foo"), "foo"},
+ {TimeValue(testTime), "2000-01-02 03:04:05 +0000 UTC"},
+ {AnyValue(time.Duration(3 * time.Second)), "3s"},
+ {GroupValue(Int("a", 1), Bool("b", true)), "[a=1 b=true]"},
+ } {
+ if got := test.v.String(); got != test.want {
+ t.Errorf("%#v:\ngot %q\nwant %q", test.v, got, test.want)
+ }
+ }
+}
+
+func TestValueNoAlloc(t *testing.T) {
+ // Assign values just to make sure the compiler doesn't optimize away the statements.
+ var (
+ i int64
+ u uint64
+ f float64
+ b bool
+ s string
+ x any
+ p = &i
+ d time.Duration
+ tm time.Time
+ )
+ a := int(testing.AllocsPerRun(5, func() {
+ i = Int64Value(1).Int64()
+ u = Uint64Value(1).Uint64()
+ f = Float64Value(1).Float64()
+ b = BoolValue(true).Bool()
+ s = StringValue("foo").String()
+ d = DurationValue(d).Duration()
+ tm = TimeValue(testTime).Time()
+ x = AnyValue(p).Any()
+ }))
+ if a != 0 {
+ t.Errorf("got %d allocs, want zero", a)
+ }
+ _ = u
+ _ = f
+ _ = b
+ _ = s
+ _ = x
+ _ = tm
+}
+
+func TestAnyLevelAlloc(t *testing.T) {
+ // Because typical Levels are small integers,
+ // they are zero-alloc.
+ var a Value
+ x := LevelDebug + 100
+ wantAllocs(t, 0, func() { a = AnyValue(x) })
+ _ = a
+}
+
+func TestAnyValue(t *testing.T) {
+ for _, test := range []struct {
+ in any
+ want Value
+ }{
+ {1, IntValue(1)},
+ {1.5, Float64Value(1.5)},
+ {"s", StringValue("s")},
+ {uint(2), Uint64Value(2)},
+ {true, BoolValue(true)},
+ {testTime, TimeValue(testTime)},
+ {time.Hour, DurationValue(time.Hour)},
+ {[]Attr{Int("i", 3)}, GroupValue(Int("i", 3))},
+ {IntValue(4), IntValue(4)},
+ } {
+ got := AnyValue(test.in)
+ if !got.Equal(test.want) {
+ t.Errorf("%v (%[1]T): got %v (kind %s), want %v (kind %s)",
+ test.in, got, got.Kind(), test.want, test.want.Kind())
+ }
+ }
+}
+
+func TestValueAny(t *testing.T) {
+ for _, want := range []any{
+ nil,
+ LevelDebug + 100,
+ time.UTC, // time.Locations treated specially...
+ KindBool, // ...as are Kinds
+ []Attr{Int("a", 1)},
+ } {
+ v := AnyValue(want)
+ got := v.Any()
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %v, want %v", got, want)
+ }
+ }
+}
+
+func TestLogValue(t *testing.T) {
+ want := "replaced"
+ r := &replace{StringValue(want)}
+ v := AnyValue(r)
+ if g, w := v.Kind(), KindLogValuer; g != w {
+ t.Errorf("got %s, want %s", g, w)
+ }
+ got := v.LogValuer().LogValue().Any()
+ if got != want {
+ t.Errorf("got %#v, want %#v", got, want)
+ }
+
+ // Test Resolve.
+ got = v.Resolve().Any()
+ if got != want {
+ t.Errorf("got %#v, want %#v", got, want)
+ }
+
+ // Test Resolve max iteration.
+ r.v = AnyValue(r) // create a cycle
+ got = AnyValue(r).Resolve().Any()
+ if _, ok := got.(error); !ok {
+ t.Errorf("expected error, got %T", got)
+ }
+
+ // Groups are not recursively resolved.
+ c := Any("c", &replace{StringValue("d")})
+ v = AnyValue(&replace{GroupValue(Int("a", 1), Group("b", c))})
+ got2 := v.Resolve().Any().([]Attr)
+ want2 := []Attr{Int("a", 1), Group("b", c)}
+ if !attrsEqual(got2, want2) {
+ t.Errorf("got %v, want %v", got2, want2)
+ }
+
+ // Verify that panics in Resolve are caught and turn into errors.
+ v = AnyValue(panickingLogValue{})
+ got = v.Resolve().Any()
+ gotErr, ok := got.(error)
+ if !ok {
+ t.Errorf("expected error, got %T", got)
+ }
+ // The error should provide some context information.
+ // We'll just check that this function name appears in it.
+ if got, want := gotErr.Error(), "TestLogValue"; !strings.Contains(got, want) {
+ t.Errorf("got %q, want substring %q", got, want)
+ }
+}
+
+func TestZeroTime(t *testing.T) {
+ z := time.Time{}
+ got := TimeValue(z).Time()
+ if !got.IsZero() {
+ t.Errorf("got %s (%#[1]v), not zero time (%#v)", got, z)
+ }
+}
+
+type replace struct {
+ v Value
+}
+
+func (r *replace) LogValue() Value { return r.v }
+
+type panickingLogValue struct{}
+
+func (panickingLogValue) LogValue() Value { panic("bad") }
+
+// A Value with "unsafe" strings is significantly faster:
+// safe: 1785 ns/op, 0 allocs
+// unsafe: 690 ns/op, 0 allocs
+
+// Run this with and without -tags unsafe_kvs to compare.
+func BenchmarkUnsafeStrings(b *testing.B) {
+ b.ReportAllocs()
+ dst := make([]Value, 100)
+ src := make([]Value, len(dst))
+ b.Logf("Value size = %d", unsafe.Sizeof(Value{}))
+ for i := range src {
+ src[i] = StringValue(fmt.Sprintf("string#%d", i))
+ }
+ b.ResetTimer()
+ var d string
+ for i := 0; i < b.N; i++ {
+ copy(dst, src)
+ for _, a := range dst {
+ d = a.String()
+ }
+ }
+ _ = d
+}
diff --git a/sumdb/go.mod b/sumdb/go.mod
new file mode 100644
index 0000000..83a9134
--- /dev/null
+++ b/sumdb/go.mod
@@ -0,0 +1,5 @@
+module golang.org/x/exp/sumdb
+
+go 1.12
+
+require golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
diff --git a/sumdb/go.sum b/sumdb/go.sum
new file mode 100644
index 0000000..6dea69e
--- /dev/null
+++ b/sumdb/go.sum
@@ -0,0 +1,3 @@
+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/sumdb/gosumcheck/main.go b/sumdb/gosumcheck/main.go
new file mode 100644
index 0000000..ddd7400
--- /dev/null
+++ b/sumdb/gosumcheck/main.go
@@ -0,0 +1,211 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Gosumcheck checks a go.sum file against a go.sum database server.
+//
+// Usage:
+//
+// gosumcheck [-h H] [-k key] [-u url] [-v] go.sum
+//
+// The -h flag changes the tile height (default 8).
+//
+// The -k flag changes the go.sum database server key.
+//
+// The -u flag overrides the URL of the server (usually set from the key name).
+//
+// The -v flag enables verbose output.
+// In particular, it causes gosumcheck to report
+// the URL and elapsed time for each server request.
+//
+// WARNING! WARNING! WARNING!
+//
+// Gosumcheck is meant as a proof of concept demo and should not be
+// used in production scripts or continuous integration testing.
+// It does not cache any downloaded information from run to run,
+// making it expensive and also keeping it from detecting server
+// misbehavior or successful HTTPS man-in-the-middle timeline forks.
+//
+// To discourage misuse in automated settings, gosumcheck does not
+// set any exit status to report whether any problems were found.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/exp/sumdb/internal/sumweb"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: gosumcheck [-h H] [-k key] [-u url] [-v] go.sum...\n")
+ os.Exit(2)
+}
+
+var (
+ height = flag.Int("h", 8, "tile height")
+ vkey = flag.String("k", "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8", "key")
+ url = flag.String("u", "", "url to server (overriding name)")
+ vflag = flag.Bool("v", false, "enable verbose output")
+)
+
+func main() {
+ log.SetPrefix("notecheck: ")
+ log.SetFlags(0)
+
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() < 1 {
+ usage()
+ }
+
+ conn := sumweb.NewConn(new(client))
+
+ // Look in environment explicitly, so that if 'go env' is old and
+ // doesn't know about GONOSUMDB, we at least get anything
+ // set in the environment.
+ env := os.Getenv("GONOSUMDB")
+ if env == "" {
+ out, err := exec.Command("go", "env", "GONOSUMDB").CombinedOutput()
+ if err != nil {
+ log.Fatalf("go env GONOSUMDB: %v\n%s", err, out)
+ }
+ env = strings.TrimSpace(string(out))
+ }
+ conn.SetGONOSUMDB(env)
+
+ for _, arg := range flag.Args() {
+ data, err := os.ReadFile(arg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ checkGoSum(conn, arg, data)
+ }
+}
+
+func checkGoSum(conn *sumweb.Conn, name string, data []byte) {
+ lines := strings.Split(string(data), "\n")
+ if lines[len(lines)-1] != "" {
+ log.Printf("error: final line missing newline")
+ return
+ }
+ lines = lines[:len(lines)-1]
+
+ errs := make([]string, len(lines))
+ var wg sync.WaitGroup
+ for i, line := range lines {
+ wg.Add(1)
+ go func(i int, line string) {
+ defer wg.Done()
+ f := strings.Fields(line)
+ if len(f) != 3 {
+ errs[i] = "invalid number of fields"
+ return
+ }
+
+ dbLines, err := conn.Lookup(f[0], f[1])
+ if err != nil {
+ if err == sumweb.ErrGONOSUMDB {
+ errs[i] = fmt.Sprintf("%s@%s: %v", f[0], f[1], err)
+ } else {
+ // Otherwise Lookup properly adds the prefix itself.
+ errs[i] = err.Error()
+ }
+ return
+ }
+ hashAlgPrefix := f[0] + " " + f[1] + " " + f[2][:strings.Index(f[2], ":")+1]
+ for _, dbLine := range dbLines {
+ if dbLine == line {
+ return
+ }
+ if strings.HasPrefix(dbLine, hashAlgPrefix) {
+ errs[i] = fmt.Sprintf("%s@%s hash mismatch: have %s, want %s", f[0], f[1], line, dbLine)
+ return
+ }
+ }
+ errs[i] = fmt.Sprintf("%s@%s hash algorithm mismatch: have %s, want one of:\n\t%s", f[0], f[1], line, strings.Join(dbLines, "\n\t"))
+ }(i, line)
+ }
+ wg.Wait()
+
+ for i, err := range errs {
+ if err != "" {
+ fmt.Printf("%s:%d: %s\n", name, i+1, err)
+ }
+ }
+}
+
+type client struct{}
+
+func (*client) ReadConfig(file string) ([]byte, error) {
+ if file == "key" {
+ return []byte(*vkey), nil
+ }
+ if strings.HasSuffix(file, "/latest") {
+ // Looking for cached latest tree head.
+ // Empty result means empty tree.
+ return []byte{}, nil
+ }
+ return nil, fmt.Errorf("unknown config %s", file)
+}
+
+func (*client) WriteConfig(file string, old, new []byte) error {
+ // Ignore writes.
+ return nil
+}
+
+func (*client) ReadCache(file string) ([]byte, error) {
+ return nil, fmt.Errorf("no cache")
+}
+
+func (*client) WriteCache(file string, data []byte) {
+ // Ignore writes.
+}
+
+func (*client) Log(msg string) {
+ log.Print(msg)
+}
+
+func (*client) SecurityError(msg string) {
+ log.Fatal(msg)
+}
+
+func init() {
+ http.DefaultClient.Timeout = 1 * time.Minute
+}
+
+func (*client) ReadRemote(path string) ([]byte, error) {
+ name := *vkey
+ if i := strings.Index(name, "+"); i >= 0 {
+ name = name[:i]
+ }
+ start := time.Now()
+ target := "https://" + name + path
+ if *url != "" {
+ target = *url + path
+ }
+ resp, err := http.Get(target)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("GET %v: %v", target, resp.Status)
+ }
+ data, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
+ if err != nil {
+ return nil, err
+ }
+ if *vflag {
+ fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), target)
+ }
+ return data, nil
+}
diff --git a/sumdb/gosumcheck/test.bash b/sumdb/gosumcheck/test.bash
new file mode 100755
index 0000000..f77e01b
--- /dev/null
+++ b/sumdb/gosumcheck/test.bash
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+go build -o gosumcheck.exe
+export GONOSUMDB=*/text # rsc.io/text but not golang.org/x/text
+./gosumcheck.exe "$@" -v test.sum
+rm -f ./gosumcheck.exe
diff --git a/sumdb/gosumcheck/test.sum b/sumdb/gosumcheck/test.sum
new file mode 100644
index 0000000..1b252ff
--- /dev/null
+++ b/sumdb/gosumcheck/test.sum
@@ -0,0 +1,6 @@
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/text v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/sumdb/internal/note/example_test.go b/sumdb/internal/note/example_test.go
new file mode 100644
index 0000000..bb4b5cb
--- /dev/null
+++ b/sumdb/internal/note/example_test.go
@@ -0,0 +1,128 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package note_test
+
+import (
+ "fmt"
+ "io"
+ "os"
+
+ "golang.org/x/exp/sumdb/internal/note"
+)
+
+func ExampleSign() {
+ skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz"
+ text := "If you think cryptography is the answer to your problem,\n" +
+ "then you don't know what your problem is.\n"
+
+ signer, err := note.NewSigner(skey)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ msg, err := note.Sign(&note.Note{Text: text}, signer)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ os.Stdout.Write(msg)
+
+ // Output:
+ // If you think cryptography is the answer to your problem,
+ // then you don't know what your problem is.
+ //
+ // β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
+}
+
+func ExampleOpen() {
+ vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+ msg := []byte("If you think cryptography is the answer to your problem,\n" +
+ "then you don't know what your problem is.\n" +
+ "\n" +
+ "β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n")
+
+ verifier, err := note.NewVerifier(vkey)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ verifiers := note.VerifierList(verifier)
+
+ n, err := note.Open(msg, verifiers)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Printf("%s (%08x):\n%s", n.Sigs[0].Name, n.Sigs[0].Hash, n.Text)
+
+ // Output:
+ // PeterNeumann (c74f20a3):
+ // If you think cryptography is the answer to your problem,
+ // then you don't know what your problem is.
+}
+
+var rand = struct {
+ Reader io.Reader
+}{
+ zeroReader{},
+}
+
+type zeroReader struct{}
+
+func (zeroReader) Read(buf []byte) (int, error) {
+ for i := range buf {
+ buf[i] = 0
+ }
+ return len(buf), nil
+}
+
+func ExampleSign_add_signatures() {
+ vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+ msg := []byte("If you think cryptography is the answer to your problem,\n" +
+ "then you don't know what your problem is.\n" +
+ "\n" +
+ "β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n")
+
+ verifier, err := note.NewVerifier(vkey)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ verifiers := note.VerifierList(verifier)
+
+ n, err := note.Open([]byte(msg), verifiers)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ skey, vkey, err := note.GenerateKey(rand.Reader, "EnochRoot")
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ _ = vkey // give to verifiers
+
+ me, err := note.NewSigner(skey)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ msg, err = note.Sign(n, me)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ os.Stdout.Write(msg)
+
+ // Output:
+ // If you think cryptography is the answer to your problem,
+ // then you don't know what your problem is.
+ //
+ // β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
+ // β€” EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ=
+}
diff --git a/sumdb/internal/note/note.go b/sumdb/internal/note/note.go
new file mode 100644
index 0000000..5fe2481
--- /dev/null
+++ b/sumdb/internal/note/note.go
@@ -0,0 +1,683 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package note defines the notes signed by the Go module database server.
+//
+// This package is part of a DRAFT of what the Go module database server will look like.
+// Do not assume the details here are final!
+//
+// A note is text signed by one or more server keys.
+// The text should be ignored unless the note is signed by
+// a trusted server key and the signature has been verified
+// using the server's public key.
+//
+// A server's public key is identified by a name, typically the "host[/path]"
+// giving the base URL of the server's transparency log.
+// The syntactic restrictions on a name are that it be non-empty,
+// well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B).
+//
+// A Go module database server signs texts using public key cryptography.
+// A given server may have multiple public keys, each
+// identified by the first 32 bits of the SHA-256 hash of
+// the concatenation of the server name, a newline, and
+// the encoded public key.
+//
+// # Verifying Notes
+//
+// A Verifier allows verification of signatures by one server public key.
+// It can report the name of the server and the uint32 hash of the key,
+// and it can verify a purported signature by that key.
+//
+// The standard implementation of a Verifier is constructed
+// by NewVerifier starting from a verifier key, which is a
+// plain text string of the form "<name>+<hash>+<keydata>".
+//
+// A Verifiers allows looking up a Verifier by the combination
+// of server name and key hash.
+//
+// The standard implementation of a Verifiers is constructed
+// by VerifierList from a list of known verifiers.
+//
+// A Note represents a text with one or more signatures.
+// An implementation can reject a note with too many signatures
+// (for example, more than 100 signatures).
+//
+// A Signature represents a signature on a note, verified or not.
+//
+// The Open function takes as input a signed message
+// and a set of known verifiers. It decodes and verifies
+// the message signatures and returns a Note structure
+// containing the message text and (verified or unverified) signatures.
+//
+// # Signing Notes
+//
+// A Signer allows signing a text with a given key.
+// It can report the name of the server and the hash of the key
+// and can sign a raw text using that key.
+//
+// The standard implementation of a Signer is constructed
+// by NewSigner starting from an encoded signer key, which is a
+// plain text string of the form "PRIVATE+KEY+<name>+<hash>+<keydata>".
+// Anyone with an encoded signer key can sign messages using that key,
+// so it must be kept secret. The encoding begins with the literal text
+// "PRIVATE+KEY" to avoid confusion with the public server key.
+//
+// The Sign function takes as input a Note and a list of Signers
+// and returns an encoded, signed message.
+//
+// # Signed Note Format
+//
+// A signed note consists of a text ending in newline (U+000A),
+// followed by a blank line (only a newline),
+// followed by one or more signature lines of this form:
+// em dash (U+2014), space (U+0020),
+// server name, space, base64-encoded signature, newline.
+//
+// Signed notes must be valid UTF-8 and must not contain any
+// ASCII control characters (those below U+0020) other than newline.
+//
+// A signature is a base64 encoding of 4+n bytes.
+//
+// The first four bytes in the signature are the uint32 key hash
+// stored in big-endian order, which is to say they are the first
+// four bytes of the truncated SHA-256 used to derive the key hash
+// in the first place.
+//
+// The remaining n bytes are the result of using the specified key
+// to sign the note text (including the final newline but not the
+// separating blank line).
+//
+// # Generating Keys
+//
+// There is only one key type, Ed25519 with algorithm identifier 1.
+// New key types may be introduced in the future as needed,
+// although doing so will require deploying the new algorithms to all clients
+// before starting to depend on them for signatures.
+//
+// The GenerateKey function generates and returns a new signer
+// and corresponding verifier.
+//
+// # Example
+//
+// Here is a well-formed signed note:
+//
+// If you think cryptography is the answer to your problem,
+// then you don't know what your problem is.
+//
+// β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
+//
+// It can be constructed and displayed using:
+//
+// skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz"
+// text := "If you think cryptography is the answer to your problem,\n" +
+// "then you don't know what your problem is.\n"
+//
+// signer, err := note.NewSigner(skey)
+// if err != nil {
+// log.Fatal(err)
+// }
+//
+// msg, err := note.Sign(&note.Note{Text: text}, signer)
+// if err != nil {
+// log.Fatal(err)
+// }
+// os.Stdout.Write(msg)
+//
+// The note's text is two lines, including the final newline,
+// and the text is purportedly signed by a server named
+// "PeterNeumann". (Although server names are canonically
+// base URLs, the only syntactic requirement is that they
+// not contain spaces or newlines).
+//
+// If Open is given access to a Verifiers including the
+// Verifier for this key, then it will succeed at verifiying
+// the encoded message and returning the parsed Note:
+//
+// vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+// msg := []byte("If you think cryptography is the answer to your problem,\n" +
+// "then you don't know what your problem is.\n" +
+// "\n" +
+// "β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n")
+//
+// verifier, err := note.NewVerifier(vkey)
+// if err != nil {
+// log.Fatal(err)
+// }
+// verifiers := note.VerifierList(verifier)
+//
+// n, err := note.Open([]byte(msg), verifiers)
+// if err != nil {
+// log.Fatal(err)
+// }
+// fmt.Printf("%s (%08x):\n%s", n.Sigs[0].Name, n.Sigs[0].Hash, n.Text)
+//
+// You can add your own signature to this message by re-signing the note:
+//
+// skey, vkey, err := note.GenerateKey(rand.Reader, "EnochRoot")
+// if err != nil {
+// log.Fatal(err)
+// }
+// _ = vkey // give to verifiers
+//
+// me, err := note.NewSigner(skey)
+// if err != nil {
+// log.Fatal(err)
+// }
+//
+// msg, err := note.Sign(n, me)
+// if err != nil {
+// log.Fatal(err)
+// }
+// os.Stdout.Write(msg)
+//
+// This will print a doubly-signed message, like:
+//
+// If you think cryptography is the answer to your problem,
+// then you don't know what your problem is.
+//
+// β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
+// β€” EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ=
+package note
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "golang.org/x/crypto/ed25519"
+)
+
+// A Verifier verifies messages signed with a specific key.
+type Verifier interface {
+ // Name returns the server name associated with the key.
+ Name() string
+
+ // KeyHash returns the key hash.
+ KeyHash() uint32
+
+ // Verify reports whether sig is a valid signature of msg.
+ Verify(msg, sig []byte) bool
+}
+
+// A Signer signs messages using a specific key.
+type Signer interface {
+ // Name returns the server name associated with the key.
+ Name() string
+
+ // KeyHash returns the key hash.
+ KeyHash() uint32
+
+ // Sign returns a signature for the given message.
+ Sign(msg []byte) ([]byte, error)
+}
+
+// keyHash computes the key hash for the given server name and encoded public key.
+func keyHash(name string, key []byte) uint32 {
+ h := sha256.New()
+ h.Write([]byte(name))
+ h.Write([]byte("\n"))
+ h.Write(key)
+ sum := h.Sum(nil)
+ return binary.BigEndian.Uint32(sum)
+}
+
+var (
+ errVerifierID = errors.New("malformed verifier id")
+ errVerifierAlg = errors.New("unknown verifier algorithm")
+ errVerifierHash = errors.New("invalid verifier hash")
+)
+
+const (
+ algEd25519 = 1
+)
+
+// isValidName reports whether name is valid.
+// It must be non-empty and not have any Unicode spaces or pluses.
+func isValidName(name string) bool {
+ return name != "" && utf8.ValidString(name) && strings.IndexFunc(name, unicode.IsSpace) < 0 && !strings.Contains(name, "+")
+}
+
+// NewVerifier construct a new Verifier from an encoded verifier key.
+func NewVerifier(vkey string) (Verifier, error) {
+ name, vkey := chop(vkey, "+")
+ hash16, key64 := chop(vkey, "+")
+ hash, err1 := strconv.ParseUint(hash16, 16, 32)
+ key, err2 := base64.StdEncoding.DecodeString(key64)
+ if len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
+ return nil, errVerifierID
+ }
+ if uint32(hash) != keyHash(name, key) {
+ return nil, errVerifierHash
+ }
+
+ v := &verifier{
+ name: name,
+ hash: uint32(hash),
+ }
+
+ alg, key := key[0], key[1:]
+ switch alg {
+ default:
+ return nil, errVerifierAlg
+
+ case algEd25519:
+ if len(key) != 32 {
+ return nil, errVerifierID
+ }
+ v.verify = func(msg, sig []byte) bool {
+ return ed25519.Verify(key, msg, sig)
+ }
+ }
+
+ return v, nil
+}
+
+// chop chops s at the first instance of sep, if any,
+// and returns the text before and after sep.
+// If sep is not present, chop returns before is s and after is empty.
+func chop(s, sep string) (before, after string) {
+ i := strings.Index(s, sep)
+ if i < 0 {
+ return s, ""
+ }
+ return s[:i], s[i+len(sep):]
+}
+
+// verifier is a trivial Verifier implementation.
+type verifier struct {
+ name string
+ hash uint32
+ verify func([]byte, []byte) bool
+}
+
+func (v *verifier) Name() string { return v.name }
+func (v *verifier) KeyHash() uint32 { return v.hash }
+func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) }
+
+// NewSigner constructs a new Signer from an encoded signer key.
+func NewSigner(skey string) (Signer, error) {
+ priv1, skey := chop(skey, "+")
+ priv2, skey := chop(skey, "+")
+ name, skey := chop(skey, "+")
+ hash16, key64 := chop(skey, "+")
+ hash, err1 := strconv.ParseUint(hash16, 16, 32)
+ key, err2 := base64.StdEncoding.DecodeString(key64)
+ if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
+ return nil, errSignerID
+ }
+
+ // Note: hash is the hash of the public key and we have the private key.
+ // Must verify hash after deriving public key.
+
+ s := &signer{
+ name: name,
+ hash: uint32(hash),
+ }
+
+ var pubkey []byte
+
+ alg, key := key[0], key[1:]
+ switch alg {
+ default:
+ return nil, errSignerAlg
+
+ case algEd25519:
+ if len(key) != 32 {
+ return nil, errSignerID
+ }
+ key = ed25519.NewKeyFromSeed(key)
+ pubkey = append([]byte{algEd25519}, key[32:]...)
+ s.sign = func(msg []byte) ([]byte, error) {
+ return ed25519.Sign(key, msg), nil
+ }
+ }
+
+ if uint32(hash) != keyHash(name, pubkey) {
+ return nil, errSignerHash
+ }
+
+ return s, nil
+}
+
+var (
+ errSignerID = errors.New("malformed verifier id")
+ errSignerAlg = errors.New("unknown verifier algorithm")
+ errSignerHash = errors.New("invalid verifier hash")
+)
+
+// signer is a trivial Signer implementation.
+type signer struct {
+ name string
+ hash uint32
+ sign func([]byte) ([]byte, error)
+}
+
+func (s *signer) Name() string { return s.name }
+func (s *signer) KeyHash() uint32 { return s.hash }
+func (s *signer) Sign(msg []byte) ([]byte, error) { return s.sign(msg) }
+
+// GenerateKey generates a signer and verifier key pair for a named server.
+// The signer key skey is private and must be kept secret.
+func GenerateKey(rand io.Reader, name string) (skey, vkey string, err error) {
+ pub, priv, err := ed25519.GenerateKey(rand)
+ if err != nil {
+ return "", "", err
+ }
+ pubkey := append([]byte{algEd25519}, pub...)
+ privkey := append([]byte{algEd25519}, priv.Seed()...)
+ h := keyHash(name, pubkey)
+
+ skey = fmt.Sprintf("PRIVATE+KEY+%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(privkey))
+ vkey = fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubkey))
+ return skey, vkey, nil
+}
+
+// NewEd25519VerifierKey returns an encoded verifier key using the given name
+// and Ed25519 public key.
+func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error) {
+ if len(key) != ed25519.PublicKeySize {
+ return "", fmt.Errorf("invalid public key size %d, expected %d", len(key), ed25519.PublicKeySize)
+ }
+
+ pubkey := append([]byte{algEd25519}, key...)
+ hash := keyHash(name, pubkey)
+
+ b64Key := base64.StdEncoding.EncodeToString(pubkey)
+ return fmt.Sprintf("%s+%08x+%s", name, hash, b64Key), nil
+}
+
+// A Verifiers is a collection of known verifier keys.
+type Verifiers interface {
+ // Verifier returns the Verifier associated with the key
+ // identified by the name and hash.
+ // If the name, hash pair is unknown, Verifier should return
+ // an UnknownVerifierError.
+ Verifier(name string, hash uint32) (Verifier, error)
+}
+
+// An UnknownVerifierError indicates that the given key is not known.
+// The Open function records signatures without associated verifiers as
+// unverified signatures.
+type UnknownVerifierError struct {
+ Name string
+ KeyHash uint32
+}
+
+func (e *UnknownVerifierError) Error() string {
+ return fmt.Sprintf("unknown key %s+%08x", e.Name, e.KeyHash)
+}
+
+// An ambiguousVerifierError indicates that the given name and hash
+// match multiple keys passed to VerifierList.
+// (If this happens, some malicious actor has taken control of the
+// verifier list, at which point we may as well give up entirely,
+// but we diagnose the problem instead.)
+type ambiguousVerifierError struct {
+ name string
+ hash uint32
+}
+
+func (e *ambiguousVerifierError) Error() string {
+ return fmt.Sprintf("ambiguous key %s+%08x", e.name, e.hash)
+}
+
+// VerifierList returns a Verifiers implementation that uses the given list of verifiers.
+func VerifierList(list ...Verifier) Verifiers {
+ m := make(verifierMap)
+ for _, v := range list {
+ k := nameHash{v.Name(), v.KeyHash()}
+ m[k] = append(m[k], v)
+ }
+ return m
+}
+
+type nameHash struct {
+ name string
+ hash uint32
+}
+
+type verifierMap map[nameHash][]Verifier
+
+func (m verifierMap) Verifier(name string, hash uint32) (Verifier, error) {
+ v, ok := m[nameHash{name, hash}]
+ if !ok {
+ return nil, &UnknownVerifierError{name, hash}
+ }
+ if len(v) > 1 {
+ return nil, &ambiguousVerifierError{name, hash}
+ }
+ return v[0], nil
+}
+
+// A Note is a text and signatures.
+type Note struct {
+ Text string // text of note
+ Sigs []Signature // verified signatures
+ UnverifiedSigs []Signature // unverified signatures
+}
+
+// A Signature is a single signature found in a note.
+type Signature struct {
+ // Name and Hash give the name and key hash
+ // for the key that generated the signature.
+ Name string
+ Hash uint32
+
+ // Base64 records the base64-encoded signature bytes.
+ Base64 string
+}
+
+// An UnverifiedNoteError indicates that the note
+// successfully parsed but had no verifiable signatures.
+type UnverifiedNoteError struct {
+ Note *Note
+}
+
+func (e *UnverifiedNoteError) Error() string {
+ return "note has no verifiable signatures"
+}
+
+// An InvalidSignatureError indicates that the given key was known
+// and the associated Verifier rejected the signature.
+type InvalidSignatureError struct {
+ Name string
+ Hash uint32
+}
+
+func (e *InvalidSignatureError) Error() string {
+ return fmt.Sprintf("invalid signature for key %s+%08x", e.Name, e.Hash)
+}
+
+var (
+ errMalformedNote = errors.New("malformed note")
+ errInvalidSigner = errors.New("invalid signer")
+
+ sigSplit = []byte("\n\n")
+ sigPrefix = []byte("β€” ")
+)
+
+// Open opens and parses the message msg, checking signatures from the known verifiers.
+//
+// For each signature in the message, Open calls known.Verifier to find a verifier.
+// If known.Verifier returns a verifier and the verifier accepts the signature,
+// Open records the signature in the returned note's Sigs field.
+// If known.Verifier returns a verifier but the verifier rejects the signature,
+// Open returns an InvalidSignatureError.
+// If known.Verifier returns an UnknownVerifierError,
+// Open records the signature in the returned note's UnverifiedSigs field.
+// If known.Verifier returns any other error, Open returns that error.
+//
+// If no known verifier has signed an otherwise valid note,
+// Open returns an UnverifiedNoteError.
+// In this case, the unverified note can be fetched from inside the error.
+func Open(msg []byte, known Verifiers) (*Note, error) {
+ if known == nil {
+ // Treat nil Verifiers as empty list, to produce useful error instead of crash.
+ known = VerifierList()
+ }
+
+ // Must have valid UTF-8 with no non-newline ASCII control characters.
+ for i := 0; i < len(msg); {
+ r, size := utf8.DecodeRune(msg[i:])
+ if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 {
+ return nil, errMalformedNote
+ }
+ i += size
+ }
+
+ // Must end with signature block preceded by blank line.
+ split := bytes.LastIndex(msg, sigSplit)
+ if split < 0 {
+ return nil, errMalformedNote
+ }
+ text, sigs := msg[:split+1], msg[split+2:]
+ if len(sigs) == 0 || sigs[len(sigs)-1] != '\n' {
+ return nil, errMalformedNote
+ }
+
+ n := &Note{
+ Text: string(text),
+ }
+
+ var buf bytes.Buffer
+ buf.Write(text)
+
+ // Parse and verify signatures.
+ // Ignore duplicate signatures.
+ seen := make(map[nameHash]bool)
+ seenUnverified := make(map[string]bool)
+ numSig := 0
+ for len(sigs) > 0 {
+ // Pull out next signature line.
+ // We know sigs[len(sigs)-1] == '\n', so IndexByte always finds one.
+ i := bytes.IndexByte(sigs, '\n')
+ line := sigs[:i]
+ sigs = sigs[i+1:]
+
+ if !bytes.HasPrefix(line, sigPrefix) {
+ return nil, errMalformedNote
+ }
+ line = line[len(sigPrefix):]
+ name, b64 := chop(string(line), " ")
+ sig, err := base64.StdEncoding.DecodeString(b64)
+ if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 {
+ return nil, errMalformedNote
+ }
+ hash := binary.BigEndian.Uint32(sig[0:4])
+ sig = sig[4:]
+
+ if numSig++; numSig > 100 {
+ // Avoid spending forever parsing a note with many signatures.
+ return nil, errMalformedNote
+ }
+
+ v, err := known.Verifier(name, hash)
+ if _, ok := err.(*UnknownVerifierError); ok {
+ // Drop repeated identical unverified signatures.
+ if seenUnverified[string(line)] {
+ continue
+ }
+ seenUnverified[string(line)] = true
+ n.UnverifiedSigs = append(n.UnverifiedSigs, Signature{Name: name, Hash: hash, Base64: b64})
+ continue
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Drop repeated signatures by a single verifier.
+ if seen[nameHash{name, hash}] {
+ continue
+ }
+ seen[nameHash{name, hash}] = true
+
+ ok := v.Verify(text, sig)
+ if !ok {
+ return nil, &InvalidSignatureError{name, hash}
+ }
+
+ n.Sigs = append(n.Sigs, Signature{Name: name, Hash: hash, Base64: b64})
+ }
+
+ // Parsed and verified all the signatures.
+ if len(n.Sigs) == 0 {
+ return nil, &UnverifiedNoteError{n}
+ }
+ return n, nil
+}
+
+// Sign signs the note with the given signers and returns the encoded message.
+// The new signatures from signers are listed in the encoded message after
+// the existing signatures already present in n.Sigs.
+// If any signer uses the same key as an existing signature,
+// the existing signature is elided from the output.
+func Sign(n *Note, signers ...Signer) ([]byte, error) {
+ var buf bytes.Buffer
+ if !strings.HasSuffix(n.Text, "\n") {
+ return nil, errMalformedNote
+ }
+ buf.WriteString(n.Text)
+
+ // Prepare signatures.
+ var sigs bytes.Buffer
+ have := make(map[nameHash]bool)
+ for _, s := range signers {
+ name := s.Name()
+ hash := s.KeyHash()
+ have[nameHash{name, hash}] = true
+ if !isValidName(name) {
+ return nil, errInvalidSigner
+ }
+
+ sig, err := s.Sign(buf.Bytes()) // buf holds n.Text
+ if err != nil {
+ return nil, err
+ }
+
+ var hbuf [4]byte
+ binary.BigEndian.PutUint32(hbuf[:], hash)
+ b64 := base64.StdEncoding.EncodeToString(append(hbuf[:], sig...))
+ sigs.WriteString("β€” ")
+ sigs.WriteString(name)
+ sigs.WriteString(" ")
+ sigs.WriteString(b64)
+ sigs.WriteString("\n")
+ }
+
+ buf.WriteString("\n")
+
+ // Emit existing signatures not replaced by new ones.
+ for _, list := range [][]Signature{n.Sigs, n.UnverifiedSigs} {
+ for _, sig := range list {
+ name, hash := sig.Name, sig.Hash
+ if !isValidName(name) {
+ return nil, errMalformedNote
+ }
+ if have[nameHash{name, hash}] {
+ continue
+ }
+ // Double-check hash against base64.
+ raw, err := base64.StdEncoding.DecodeString(sig.Base64)
+ if err != nil || len(raw) < 4 || binary.BigEndian.Uint32(raw) != hash {
+ return nil, errMalformedNote
+ }
+ buf.WriteString("β€” ")
+ buf.WriteString(sig.Name)
+ buf.WriteString(" ")
+ buf.WriteString(sig.Base64)
+ buf.WriteString("\n")
+ }
+ }
+ buf.Write(sigs.Bytes())
+
+ return buf.Bytes(), nil
+}
diff --git a/sumdb/internal/note/note_test.go b/sumdb/internal/note/note_test.go
new file mode 100644
index 0000000..96c8c91
--- /dev/null
+++ b/sumdb/internal/note/note_test.go
@@ -0,0 +1,473 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package note
+
+import (
+ "crypto/rand"
+ "errors"
+ "strings"
+ "testing"
+ "testing/iotest"
+
+ "golang.org/x/crypto/ed25519"
+)
+
+func TestNewVerifier(t *testing.T) {
+ vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+ _, err := NewVerifier(vkey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check various manglings are not accepted.
+ badKey := func(k string) {
+ _, err := NewVerifier(k)
+ if err == nil {
+ t.Errorf("NewVerifier(%q) succeeded, should have failed", k)
+ }
+ }
+
+ b := []byte(vkey)
+ for i := 0; i <= len(b); i++ {
+ for j := i + 1; j <= len(b); j++ {
+ if i != 0 || j != len(b) {
+ badKey(string(b[i:j]))
+ }
+ }
+ }
+ for i := 0; i < len(b); i++ {
+ b[i]++
+ badKey(string(b))
+ b[i]--
+ }
+
+ badKey("PeterNeumann+cc469956+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TWBADKEY==") // wrong length key, with adjusted key hash
+ badKey("PeterNeumann+173116ae+ZRpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW") // unknown algorithm, with adjusted key hash
+}
+
+func TestNewSigner(t *testing.T) {
+ skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz"
+ _, err := NewSigner(skey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check various manglings are not accepted.
+ b := []byte(skey)
+ for i := 0; i <= len(b); i++ {
+ for j := i + 1; j <= len(b); j++ {
+ if i == 0 && j == len(b) {
+ continue
+ }
+ _, err := NewSigner(string(b[i:j]))
+ if err == nil {
+ t.Errorf("NewSigner(%q) succeeded, should have failed", b[i:j])
+ }
+ }
+ }
+ for i := 0; i < len(b); i++ {
+ b[i]++
+ _, err := NewSigner(string(b))
+ if err == nil {
+ t.Errorf("NewSigner(%q) succeeded, should have failed", b)
+ }
+ b[i]--
+ }
+}
+
+func testSignerAndVerifier(t *testing.T, Name string, signer Signer, verifier Verifier) {
+ if name := signer.Name(); name != Name {
+ t.Errorf("signer.Name() = %q, want %q", name, Name)
+ }
+ if name := verifier.Name(); name != Name {
+ t.Errorf("verifier.Name() = %q, want %q", name, Name)
+ }
+ shash := signer.KeyHash()
+ vhash := verifier.KeyHash()
+ if shash != vhash {
+ t.Errorf("signer.KeyHash() = %#08x != verifier.KeyHash() = %#08x", shash, vhash)
+ }
+
+ msg := []byte("hi")
+ sig, err := signer.Sign(msg)
+ if err != nil {
+ t.Fatalf("signer.Sign: %v", err)
+ }
+ if !verifier.Verify(msg, sig) {
+ t.Fatalf("verifier.Verify failed on signature returned by signer.Sign")
+ }
+ sig[0]++
+ if verifier.Verify(msg, sig) {
+ t.Fatalf("verifier.Verify succceeded on corrupt signature")
+ }
+ sig[0]--
+ msg[0]++
+ if verifier.Verify(msg, sig) {
+ t.Fatalf("verifier.Verify succceeded on corrupt message")
+ }
+}
+
+func TestGenerateKey(t *testing.T) {
+ // Generate key pair, make sure it is all self-consistent.
+ const Name = "EnochRoot"
+
+ skey, vkey, err := GenerateKey(rand.Reader, Name)
+ if err != nil {
+ t.Fatalf("GenerateKey: %v", err)
+ }
+ signer, err := NewSigner(skey)
+ if err != nil {
+ t.Fatalf("NewSigner: %v", err)
+ }
+ verifier, err := NewVerifier(vkey)
+ if err != nil {
+ t.Fatalf("NewVerifier: %v", err)
+ }
+
+ testSignerAndVerifier(t, Name, signer, verifier)
+
+ // Check that GenerateKey returns error from rand reader.
+ _, _, err = GenerateKey(iotest.TimeoutReader(iotest.OneByteReader(rand.Reader)), Name)
+ if err == nil {
+ t.Fatalf("GenerateKey succeeded with error-returning rand reader")
+ }
+}
+
+func TestFromEd25519(t *testing.T) {
+ const Name = "EnochRoot"
+
+ pub, priv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("GenerateKey: %v", err)
+ }
+ signer, err := newSignerFromEd25519Seed(Name, priv.Seed())
+ if err != nil {
+ t.Fatalf("newSignerFromEd25519Seed: %v", err)
+ }
+ vkey, err := NewEd25519VerifierKey(Name, pub)
+ if err != nil {
+ t.Fatalf("NewEd25519VerifierKey: %v", err)
+ }
+ verifier, err := NewVerifier(vkey)
+ if err != nil {
+ t.Fatalf("NewVerifier: %v", err)
+ }
+
+ testSignerAndVerifier(t, Name, signer, verifier)
+
+ // Check that wrong key sizes return errors.
+ _, err = NewEd25519VerifierKey(Name, pub[:len(pub)-1])
+ if err == nil {
+ t.Errorf("NewEd25519VerifierKey succeeded with a seed of the wrong size")
+ }
+}
+
+// newSignerFromEd25519Seed constructs a new signer from a verifier name and a
+// golang.org/x/crypto/ed25519 private key seed.
+func newSignerFromEd25519Seed(name string, seed []byte) (Signer, error) {
+ if len(seed) != ed25519.SeedSize {
+ return nil, errors.New("invalid seed size")
+ }
+ priv := ed25519.NewKeyFromSeed(seed)
+ pub := priv[32:]
+
+ pubkey := append([]byte{algEd25519}, pub...)
+ hash := keyHash(name, pubkey)
+
+ s := &signer{
+ name: name,
+ hash: uint32(hash),
+ sign: func(msg []byte) ([]byte, error) {
+ return ed25519.Sign(priv, msg), nil
+ },
+ }
+ return s, nil
+}
+
+func TestSign(t *testing.T) {
+ skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz"
+ text := "If you think cryptography is the answer to your problem,\n" +
+ "then you don't know what your problem is.\n"
+
+ signer, err := NewSigner(skey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ msg, err := Sign(&Note{Text: text}, signer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ want := `If you think cryptography is the answer to your problem,
+then you don't know what your problem is.
+
+β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
+`
+ if string(msg) != want {
+ t.Errorf("Sign: wrong output\nhave:\n%s\nwant:\n%s", msg, want)
+ }
+
+ // Check that existing signature is replaced by new one.
+ msg, err = Sign(&Note{Text: text, Sigs: []Signature{{Name: "PeterNeumann", Hash: 0xc74f20a3, Base64: "BADSIGN="}}}, signer)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(msg) != want {
+ t.Errorf("Sign replacing signature: wrong output\nhave:\n%s\nwant:\n%s", msg, want)
+ }
+
+ // Check various bad inputs.
+ _, err = Sign(&Note{Text: "abc"}, signer)
+ if err == nil || err.Error() != "malformed note" {
+ t.Fatalf("Sign with short text: %v, want malformed note error", err)
+ }
+
+ _, err = Sign(&Note{Text: text, Sigs: []Signature{{Name: "a+b", Base64: "ABCD"}}})
+ if err == nil || err.Error() != "malformed note" {
+ t.Fatalf("Sign with bad name: %v, want malformed note error", err)
+ }
+
+ _, err = Sign(&Note{Text: text, Sigs: []Signature{{Name: "PeterNeumann", Hash: 0xc74f20a3, Base64: "BADHASH="}}})
+ if err == nil || err.Error() != "malformed note" {
+ t.Fatalf("Sign with bad pre-filled signature: %v, want malformed note error", err)
+ }
+
+ _, err = Sign(&Note{Text: text}, &badSigner{signer})
+ if err == nil || err.Error() != "invalid signer" {
+ t.Fatalf("Sign with bad signer: %v, want invalid signer error", err)
+ }
+
+ _, err = Sign(&Note{Text: text}, &errSigner{signer})
+ if err != errSurprise {
+ t.Fatalf("Sign with failing signer: %v, want errSurprise", err)
+ }
+}
+
+func TestVerifierList(t *testing.T) {
+ peterKey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+ peterVerifier, err := NewVerifier(peterKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ enochKey := "EnochRoot+af0cfe78+ATtqJ7zOtqQtYqOo0CpvDXNlMhV3HeJDpjrASKGLWdop"
+ enochVerifier, err := NewVerifier(enochKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ list := VerifierList(peterVerifier, enochVerifier, enochVerifier)
+ v, err := list.Verifier("PeterNeumann", 0xc74f20a3)
+ if v != peterVerifier || err != nil {
+ t.Fatalf("list.Verifier(peter) = %v, %v, want %v, nil", v, err, peterVerifier)
+ }
+ v, err = list.Verifier("PeterNeumann", 0xc74f20a4)
+ if v != nil || err == nil || err.Error() != "unknown key PeterNeumann+c74f20a4" {
+ t.Fatalf("list.Verifier(peter bad hash) = %v, %v, want nil, unknown key error", v, err)
+ }
+
+ v, err = list.Verifier("PeterNeuman", 0xc74f20a3)
+ if v != nil || err == nil || err.Error() != "unknown key PeterNeuman+c74f20a3" {
+ t.Fatalf("list.Verifier(peter bad name) = %v, %v, want nil, unknown key error", v, err)
+ }
+ v, err = list.Verifier("EnochRoot", 0xaf0cfe78)
+ if v != nil || err == nil || err.Error() != "ambiguous key EnochRoot+af0cfe78" {
+ t.Fatalf("list.Verifier(enoch) = %v, %v, want nil, ambiguous key error", v, err)
+ }
+}
+
+type badSigner struct {
+ Signer
+}
+
+func (b *badSigner) Name() string {
+ return "bad name"
+}
+
+var errSurprise = errors.New("surprise!")
+
+type errSigner struct {
+ Signer
+}
+
+func (e *errSigner) Sign([]byte) ([]byte, error) {
+ return nil, errSurprise
+}
+
+func TestOpen(t *testing.T) {
+ peterKey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+ peterVerifier, err := NewVerifier(peterKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ enochKey := "EnochRoot+af0cfe78+ATtqJ7zOtqQtYqOo0CpvDXNlMhV3HeJDpjrASKGLWdop"
+ enochVerifier, err := NewVerifier(enochKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ text := `If you think cryptography is the answer to your problem,
+then you don't know what your problem is.
+`
+ peterSig := "β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n"
+ enochSig := "β€” EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ=\n"
+
+ peter := Signature{"PeterNeumann", 0xc74f20a3, "x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM="}
+ enoch := Signature{"EnochRoot", 0xaf0cfe78, "rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ="}
+
+ // Check one signature verified, one not.
+ n, err := Open([]byte(text+"\n"+peterSig+enochSig), VerifierList(peterVerifier))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n.Text != text {
+ t.Errorf("n.Text = %q, want %q", n.Text, text)
+ }
+ if len(n.Sigs) != 1 || n.Sigs[0] != peter {
+ t.Errorf("n.Sigs:\nhave %v\nwant %v", n.Sigs, []Signature{peter})
+ }
+ if len(n.UnverifiedSigs) != 1 || n.UnverifiedSigs[0] != enoch {
+ t.Errorf("n.UnverifiedSigs:\nhave %v\nwant %v", n.Sigs, []Signature{peter})
+ }
+
+ // Check both verified.
+ n, err = Open([]byte(text+"\n"+peterSig+enochSig), VerifierList(peterVerifier, enochVerifier))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(n.Sigs) != 2 || n.Sigs[0] != peter || n.Sigs[1] != enoch {
+ t.Errorf("n.Sigs:\nhave %v\nwant %v", n.Sigs, []Signature{peter, enoch})
+ }
+ if len(n.UnverifiedSigs) != 0 {
+ t.Errorf("n.UnverifiedSigs:\nhave %v\nwant %v", n.Sigs, []Signature{})
+ }
+
+ // Check both unverified.
+ n, err = Open([]byte(text+"\n"+peterSig+enochSig), VerifierList())
+ if n != nil || err == nil {
+ t.Fatalf("Open unverified = %v, %v, want nil, error", n, err)
+ }
+ e, ok := err.(*UnverifiedNoteError)
+ if !ok {
+ t.Fatalf("Open unverified: err is %T, want *UnverifiedNoteError", err)
+ }
+ if err.Error() != "note has no verifiable signatures" {
+ t.Fatalf("Open unverified: err.Error() = %q, want %q", err.Error(), "note has no verifiable signatures")
+ }
+
+ n = e.Note
+ if n == nil {
+ t.Fatalf("Open unverified: missing note in UnverifiedNoteError")
+ }
+ if len(n.Sigs) != 0 {
+ t.Errorf("n.Sigs:\nhave %v\nwant %v", n.Sigs, []Signature{})
+ }
+ if len(n.UnverifiedSigs) != 2 || n.UnverifiedSigs[0] != peter || n.UnverifiedSigs[1] != enoch {
+ t.Errorf("n.UnverifiedSigs:\nhave %v\nwant %v", n.Sigs, []Signature{peter, enoch})
+ }
+
+ // Check duplicated verifier.
+ _, err = Open([]byte(text+"\n"+enochSig), VerifierList(enochVerifier, peterVerifier, enochVerifier))
+ if err == nil || err.Error() != "ambiguous key EnochRoot+af0cfe78" {
+ t.Fatalf("Open with duplicated verifier: err=%v, want ambiguous key", err)
+ }
+
+ // Check unused duplicated verifier.
+ _, err = Open([]byte(text+"\n"+peterSig), VerifierList(enochVerifier, peterVerifier, enochVerifier))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check too many signatures.
+ n, err = Open([]byte(text+"\n"+strings.Repeat(peterSig, 101)), VerifierList(peterVerifier))
+ if n != nil || err == nil || err.Error() != "malformed note" {
+ t.Fatalf("Open too many verified signatures = %v, %v, want nil, malformed note error", n, err)
+ }
+ n, err = Open([]byte(text+"\n"+strings.Repeat(peterSig, 101)), VerifierList())
+ if n != nil || err == nil || err.Error() != "malformed note" {
+ t.Fatalf("Open too many verified signatures = %v, %v, want nil, malformed note error", n, err)
+ }
+
+ // Invalid signature.
+ n, err = Open([]byte(text+"\n"+peterSig[:60]+"ABCD"+peterSig[60:]), VerifierList(peterVerifier))
+ if n != nil || err == nil || err.Error() != "invalid signature for key PeterNeumann+c74f20a3" {
+ t.Fatalf("Open too many verified signatures = %v, %v, want nil, invalid signature error", n, err)
+ }
+
+ // Duplicated verified and unverified signatures.
+ enochABCD := Signature{"EnochRoot", 0xaf0cfe78, "rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n" + "ABCD" + "2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ="}
+ n, err = Open([]byte(text+"\n"+peterSig+peterSig+enochSig+enochSig+enochSig[:60]+"ABCD"+enochSig[60:]), VerifierList(peterVerifier))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(n.Sigs) != 1 || n.Sigs[0] != peter {
+ t.Errorf("n.Sigs:\nhave %v\nwant %v", n.Sigs, []Signature{peter})
+ }
+ if len(n.UnverifiedSigs) != 2 || n.UnverifiedSigs[0] != enoch || n.UnverifiedSigs[1] != enochABCD {
+ t.Errorf("n.UnverifiedSigs:\nhave %v\nwant %v", n.UnverifiedSigs, []Signature{enoch, enochABCD})
+ }
+
+ // Invalid encoded message syntax.
+ badMsgs := []string{
+ text,
+ text + "\n",
+ text + "\n" + peterSig[:len(peterSig)-1],
+ "\x01" + text + "\n" + peterSig,
+ "\xff" + text + "\n" + peterSig,
+ text + "\n" + "β€” Bad Name x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=",
+ text + "\n" + peterSig + "Unexpected line.\n",
+ }
+ for _, msg := range badMsgs {
+ n, err := Open([]byte(msg), VerifierList(peterVerifier))
+ if n != nil || err == nil || err.Error() != "malformed note" {
+ t.Fatalf("Open bad msg = %v, %v, want nil, malformed note error\nmsg:\n%s", n, err, msg)
+ }
+ }
+}
+
+func BenchmarkOpen(b *testing.B) {
+ vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
+ msg := []byte("If you think cryptography is the answer to your problem,\n" +
+ "then you don't know what your problem is.\n" +
+ "\n" +
+ "β€” PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n")
+
+ verifier, err := NewVerifier(vkey)
+ if err != nil {
+ b.Fatal(err)
+ }
+ verifiers := VerifierList(verifier)
+ verifiers0 := VerifierList()
+
+ // Try with 0 signatures and 1 signature so we can tell how much each signature adds.
+
+ b.Run("Sig0", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ _, err := Open(msg, verifiers0)
+ e, ok := err.(*UnverifiedNoteError)
+ if !ok {
+ b.Fatal("expected UnverifiedNoteError")
+ }
+ n := e.Note
+ if len(n.Sigs) != 0 || len(n.UnverifiedSigs) != 1 {
+ b.Fatal("wrong signature count")
+ }
+ }
+ })
+
+ b.Run("Sig1", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ n, err := Open(msg, verifiers)
+ if err != nil {
+ b.Fatal(err)
+ }
+ if len(n.Sigs) != 1 || len(n.UnverifiedSigs) != 0 {
+ b.Fatal("wrong signature count")
+ }
+ }
+ })
+}
diff --git a/sumdb/internal/sumweb/cache.go b/sumdb/internal/sumweb/cache.go
new file mode 100644
index 0000000..a8117a7
--- /dev/null
+++ b/sumdb/internal/sumweb/cache.go
@@ -0,0 +1,59 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Parallel cache.
+// This file is copied from cmd/go/internal/par.
+
+package sumweb
+
+import (
+ "sync"
+ "sync/atomic"
+)
+
+// parCache runs an action once per key and caches the result.
+type parCache struct {
+ m sync.Map
+}
+
+type cacheEntry struct {
+ done uint32
+ mu sync.Mutex
+ result interface{}
+}
+
+// Do calls the function f if and only if Do is being called for the first time with this key.
+// No call to Do with a given key returns until the one call to f returns.
+// Do returns the value returned by the one call to f.
+func (c *parCache) Do(key interface{}, f func() interface{}) interface{} {
+ entryIface, ok := c.m.Load(key)
+ if !ok {
+ entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
+ }
+ e := entryIface.(*cacheEntry)
+ if atomic.LoadUint32(&e.done) == 0 {
+ e.mu.Lock()
+ if atomic.LoadUint32(&e.done) == 0 {
+ e.result = f()
+ atomic.StoreUint32(&e.done, 1)
+ }
+ e.mu.Unlock()
+ }
+ return e.result
+}
+
+// Get returns the cached result associated with key.
+// It returns nil if there is no such result.
+// If the result for key is being computed, Get does not wait for the computation to finish.
+func (c *parCache) Get(key interface{}) interface{} {
+ entryIface, ok := c.m.Load(key)
+ if !ok {
+ return nil
+ }
+ e := entryIface.(*cacheEntry)
+ if atomic.LoadUint32(&e.done) == 0 {
+ return nil
+ }
+ return e.result
+}
diff --git a/sumdb/internal/sumweb/client.go b/sumdb/internal/sumweb/client.go
new file mode 100644
index 0000000..364f48d
--- /dev/null
+++ b/sumdb/internal/sumweb/client.go
@@ -0,0 +1,663 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sumweb
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "path"
+ "strings"
+ "sync"
+ "sync/atomic"
+
+ "golang.org/x/exp/sumdb/internal/note"
+ "golang.org/x/exp/sumdb/internal/tlog"
+)
+
+// A Client provides the external operations
+// (file caching, HTTP fetches, and so on)
+// needed to implement the HTTP client Conn.
+// The methods must be safe for concurrent use by multiple goroutines.
+type Client interface {
+ // ReadRemote reads and returns the content served at the given path
+ // on the remote database server. The path begins with "/lookup" or "/tile/".
+ // It is the implementation's responsibility to turn that path into a full URL
+ // and make the HTTP request. ReadRemote should return an error for
+ // any non-200 HTTP response status.
+ ReadRemote(path string) ([]byte, error)
+
+ // ReadConfig reads and returns the content of the named configuration file.
+ // There are only a fixed set of configuration files.
+ //
+ // "key" returns a file containing the verifier key for the server.
+ //
+ // serverName + "/latest" returns a file containing the latest known
+ // signed tree from the server. It is read and written (using WriteConfig).
+ // To signal that the client wishes to start with an "empty" signed tree,
+ // ReadConfig can return a successful empty result (0 bytes of data).
+ ReadConfig(file string) ([]byte, error)
+
+ // WriteConfig updates the content of the named configuration file,
+ // changing it from the old []byte to the new []byte.
+ // If the old []byte does not match the stored configuration,
+ // WriteConfig must return ErrWriteConflict.
+ // Otherwise, WriteConfig should atomically replace old with new.
+ WriteConfig(file string, old, new []byte) error
+
+ // ReadCache reads and returns the content of the named cache file.
+ // Any returned error will be treated as equivalent to the file not existing.
+ // There can be arbitrarily many cache files, such as:
+ // serverName/lookup/pkg@version
+ // serverName/tile/8/1/x123/456
+ ReadCache(file string) ([]byte, error)
+
+ // WriteCache writes the named cache file.
+ WriteCache(file string, data []byte)
+
+ // Log prints the given log message (such as with log.Print)
+ Log(msg string)
+
+ // SecurityError prints the given security error log message.
+ // The Conn returns ErrSecurity from any operation that invokes SecurityError,
+ // but the return value is mainly for testing. In a real program,
+ // SecurityError should typically print the message and call log.Fatal or os.Exit.
+ SecurityError(msg string)
+}
+
+// ErrWriteConflict signals a write conflict during Client.WriteConfig.
+var ErrWriteConflict = errors.New("write conflict")
+
+// ErrSecurity is returned by Conn operations that invoke Client.SecurityError.
+var ErrSecurity = errors.New("security error: misbehaving server")
+
+// A Conn is a client connection to a go.sum database.
+// All the methods are safe for simultaneous use by multiple goroutines.
+type Conn struct {
+ client Client // client-provided external world
+
+ didLookup uint32
+
+ // one-time initialized data
+ initOnce sync.Once
+ initErr error // init error, if any
+ name string // name of accepted verifier
+ verifiers note.Verifiers // accepted verifiers (just one, but Verifiers for note.Open)
+ tileReader tileReader
+ tileHeight int
+ nosumdb string
+
+ record parCache // cache of record lookup, keyed by path@vers
+ tileCache parCache // cache of c.readTile, keyed by tile
+
+ latestMu sync.Mutex
+ latest tlog.Tree // latest known tree head
+ latestMsg []byte // encoded signed note for latest
+
+ tileSavedMu sync.Mutex
+ tileSaved map[tlog.Tile]bool // which tiles have been saved using c.client.WriteCache already
+}
+
+// NewConn returns a new Conn using the given Client.
+func NewConn(client Client) *Conn {
+ return &Conn{
+ client: client,
+ }
+}
+
+// init initiailzes the conn (if not already initialized)
+// and returns any initialization error.
+func (c *Conn) init() error {
+ c.initOnce.Do(c.initWork)
+ return c.initErr
+}
+
+// initWork does the actual initialization work.
+func (c *Conn) initWork() {
+ defer func() {
+ if c.initErr != nil {
+ c.initErr = fmt.Errorf("initializing sumweb.Conn: %v", c.initErr)
+ }
+ }()
+
+ c.tileReader.c = c
+ if c.tileHeight == 0 {
+ c.tileHeight = 8
+ }
+ c.tileSaved = make(map[tlog.Tile]bool)
+
+ vkey, err := c.client.ReadConfig("key")
+ if err != nil {
+ c.initErr = err
+ return
+ }
+ verifier, err := note.NewVerifier(strings.TrimSpace(string(vkey)))
+ if err != nil {
+ c.initErr = err
+ return
+ }
+ c.verifiers = note.VerifierList(verifier)
+ c.name = verifier.Name()
+
+ data, err := c.client.ReadConfig(c.name + "/latest")
+ if err != nil {
+ c.initErr = err
+ return
+ }
+ if err := c.mergeLatest(data); err != nil {
+ c.initErr = err
+ return
+ }
+}
+
+// SetTileHeight sets the tile height for the Conn.
+// Any call to SetTileHeight must happen before the first call to Lookup.
+// If SetTileHeight is not called, the Conn defaults to tile height 8.
+func (c *Conn) SetTileHeight(height int) {
+ if atomic.LoadUint32(&c.didLookup) != 0 {
+ panic("SetTileHeight used after Lookup")
+ }
+ if c.tileHeight != 0 {
+ panic("multiple calls to SetTileHeight")
+ }
+ c.tileHeight = height
+}
+
+// SetGONOSUMDB sets the list of comma-separated GONOSUMDB patterns for the Conn.
+// For any module path matching one of the patterns,
+// Lookup will return ErrGONOSUMDB.
+// Any call to SetGONOSUMDB must happen before the first call to Lookup.
+func (c *Conn) SetGONOSUMDB(list string) {
+ if atomic.LoadUint32(&c.didLookup) != 0 {
+ panic("SetGONOSUMDB used after Lookup")
+ }
+ if c.nosumdb != "" {
+ panic("multiple calls to SetGONOSUMDB")
+ }
+ c.nosumdb = list
+}
+
+// ErrGONOSUMDB is returned by Lookup for paths that match
+// a pattern listed in the GONOSUMDB list (set by SetGONOSUMDB,
+// usually from the environment variable).
+var ErrGONOSUMDB = errors.New("skipped (listed in GONOSUMDB)")
+
+func (c *Conn) skip(target string) bool {
+ return globsMatchPath(c.nosumdb, target)
+}
+
+// globsMatchPath reports whether any path prefix of target
+// matches one of the glob patterns (as defined by path.Match)
+// in the comma-separated globs list.
+// It ignores any empty or malformed patterns in the list.
+func globsMatchPath(globs, target string) bool {
+ for globs != "" {
+ // Extract next non-empty glob in comma-separated list.
+ var glob string
+ if i := strings.Index(globs, ","); i >= 0 {
+ glob, globs = globs[:i], globs[i+1:]
+ } else {
+ glob, globs = globs, ""
+ }
+ if glob == "" {
+ continue
+ }
+
+ // A glob with N+1 path elements (N slashes) needs to be matched
+ // against the first N+1 path elements of target,
+ // which end just before the N+1'th slash.
+ n := strings.Count(glob, "/")
+ prefix := target
+ // Walk target, counting slashes, truncating at the N+1'th slash.
+ for i := 0; i < len(target); i++ {
+ if target[i] == '/' {
+ if n == 0 {
+ prefix = target[:i]
+ break
+ }
+ n--
+ }
+ }
+ if n > 0 {
+ // Not enough prefix elements.
+ continue
+ }
+ matched, _ := path.Match(glob, prefix)
+ if matched {
+ return true
+ }
+ }
+ return false
+}
+
+// Lookup returns the go.sum lines for the given module path and version.
+// The version may end in a /go.mod suffix, in which case Lookup returns
+// the go.sum lines for the module's go.mod-only hash.
+func (c *Conn) Lookup(path, vers string) (lines []string, err error) {
+ atomic.StoreUint32(&c.didLookup, 1)
+
+ if c.skip(path) {
+ return nil, ErrGONOSUMDB
+ }
+
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("%s@%s: %v", path, vers, err)
+ }
+ }()
+
+ if err := c.init(); err != nil {
+ return nil, err
+ }
+
+ // Prepare encoded cache filename / URL.
+ epath, err := encodePath(path)
+ if err != nil {
+ return nil, err
+ }
+ evers, err := encodeVersion(strings.TrimSuffix(vers, "/go.mod"))
+ if err != nil {
+ return nil, err
+ }
+ file := c.name + "/lookup/" + epath + "@" + evers
+ remotePath := "/lookup/" + epath + "@" + evers
+
+ // Fetch the data.
+ // The lookupCache avoids redundant ReadCache/GetURL operations
+ // (especially since go.sum lines tend to come in pairs for a given
+ // path and version) and also avoids having multiple of the same
+ // request in flight at once.
+ type cached struct {
+ data []byte
+ err error
+ }
+ result := c.record.Do(file, func() interface{} {
+ // Try the on-disk cache, or else get from web.
+ writeCache := false
+ data, err := c.client.ReadCache(file)
+ if err != nil {
+ data, err = c.client.ReadRemote(remotePath)
+ if err != nil {
+ return cached{nil, err}
+ }
+ writeCache = true
+ }
+
+ // Validate the record before using it for anything.
+ id, text, treeMsg, err := tlog.ParseRecord(data)
+ if err != nil {
+ return cached{nil, err}
+ }
+ if err := c.mergeLatest(treeMsg); err != nil {
+ return cached{nil, err}
+ }
+ if err := c.checkRecord(id, text); err != nil {
+ return cached{nil, err}
+ }
+
+ // Now that we've validated the record,
+ // save it to the on-disk cache (unless that's where it came from).
+ if writeCache {
+ c.client.WriteCache(file, data)
+ }
+
+ return cached{data, nil}
+ }).(cached)
+ if result.err != nil {
+ return nil, result.err
+ }
+
+ // Extract the lines for the specific version we want
+ // (with or without /go.mod).
+ prefix := path + " " + vers + " "
+ var hashes []string
+ for _, line := range strings.Split(string(result.data), "\n") {
+ if strings.HasPrefix(line, prefix) {
+ hashes = append(hashes, line)
+ }
+ }
+ return hashes, nil
+}
+
+// mergeLatest merges the tree head in msg
+// with the Conn's current latest tree head,
+// ensuring the result is a consistent timeline.
+// If the result is inconsistent, mergeLatest calls c.client.SecurityError
+// with a detailed security error message and then
+// (only if c.client.SecurityError does not exit the program) returns ErrSecurity.
+// If the Conn's current latest tree head moves forward,
+// mergeLatest updates the underlying configuration file as well,
+// taking care to merge any independent updates to that configuration.
+func (c *Conn) mergeLatest(msg []byte) error {
+ // Merge msg into our in-memory copy of the latest tree head.
+ when, err := c.mergeLatestMem(msg)
+ if err != nil {
+ return err
+ }
+ if when != msgFuture {
+ // msg matched our present or was in the past.
+ // No change to our present, so no update of config file.
+ return nil
+ }
+
+ // Flush our extended timeline back out to the configuration file.
+ // If the configuration file has been updated in the interim,
+ // we need to merge any updates made there as well.
+ // Note that writeConfig is an atomic compare-and-swap.
+ for {
+ msg, err := c.client.ReadConfig(c.name + "/latest")
+ if err != nil {
+ return err
+ }
+ when, err := c.mergeLatestMem(msg)
+ if err != nil {
+ return err
+ }
+ if when != msgPast {
+ // msg matched our present or was from the future,
+ // and now our in-memory copy matches.
+ return nil
+ }
+
+ // msg (== config) is in the past, so we need to update it.
+ c.latestMu.Lock()
+ latestMsg := c.latestMsg
+ c.latestMu.Unlock()
+ if err := c.client.WriteConfig(c.name+"/latest", msg, latestMsg); err != ErrWriteConflict {
+ // Success or a non-write-conflict error.
+ return err
+ }
+ }
+}
+
+const (
+ msgPast = 1 + iota
+ msgNow
+ msgFuture
+)
+
+// mergeLatestMem is like mergeLatest but is only concerned with
+// updating the in-memory copy of the latest tree head (c.latest)
+// not the configuration file.
+// The when result explains when msg happened relative to our
+// previous idea of c.latest:
+// msgPast means msg was from before c.latest,
+// msgNow means msg was exactly c.latest, and
+// msgFuture means msg was from after c.latest, which has now been updated.
+func (c *Conn) mergeLatestMem(msg []byte) (when int, err error) {
+ if len(msg) == 0 {
+ // Accept empty msg as the unsigned, empty timeline.
+ c.latestMu.Lock()
+ latest := c.latest
+ c.latestMu.Unlock()
+ if latest.N == 0 {
+ return msgNow, nil
+ }
+ return msgPast, nil
+ }
+
+ note, err := note.Open(msg, c.verifiers)
+ if err != nil {
+ return 0, fmt.Errorf("reading tree note: %v\nnote:\n%s", err, msg)
+ }
+ tree, err := tlog.ParseTree([]byte(note.Text))
+ if err != nil {
+ return 0, fmt.Errorf("reading tree: %v\ntree:\n%s", err, note.Text)
+ }
+
+ // Other lookups may be calling mergeLatest with other heads,
+ // so c.latest is changing underfoot. We don't want to hold the
+ // c.mu lock during tile fetches, so loop trying to update c.latest.
+ c.latestMu.Lock()
+ latest := c.latest
+ latestMsg := c.latestMsg
+ c.latestMu.Unlock()
+
+ for {
+ // If the tree head looks old, check that it is on our timeline.
+ if tree.N <= latest.N {
+ if err := c.checkTrees(tree, msg, latest, latestMsg); err != nil {
+ return 0, err
+ }
+ if tree.N < latest.N {
+ return msgPast, nil
+ }
+ return msgNow, nil
+ }
+
+ // The tree head looks new. Check that we are on its timeline and try to move our timeline forward.
+ if err := c.checkTrees(latest, latestMsg, tree, msg); err != nil {
+ return 0, err
+ }
+
+ // Install our msg if possible.
+ // Otherwise we will go around again.
+ c.latestMu.Lock()
+ installed := false
+ if c.latest == latest {
+ installed = true
+ c.latest = tree
+ c.latestMsg = msg
+ } else {
+ latest = c.latest
+ latestMsg = c.latestMsg
+ }
+ c.latestMu.Unlock()
+
+ if installed {
+ return msgFuture, nil
+ }
+ }
+}
+
+// checkTrees checks that older (from olderNote) is contained in newer (from newerNote).
+// If an error occurs, such as malformed data or a network problem, checkTrees returns that error.
+// If on the other hand checkTrees finds evidence of misbehavior, it prepares a detailed
+// message and calls log.Fatal.
+func (c *Conn) checkTrees(older tlog.Tree, olderNote []byte, newer tlog.Tree, newerNote []byte) error {
+ thr := tlog.TileHashReader(newer, &c.tileReader)
+ h, err := tlog.TreeHash(older.N, thr)
+ if err != nil {
+ if older.N == newer.N {
+ return fmt.Errorf("checking tree#%d: %v", older.N, err)
+ }
+ return fmt.Errorf("checking tree#%d against tree#%d: %v", older.N, newer.N, err)
+ }
+ if h == older.Hash {
+ return nil
+ }
+
+ // Detected a fork in the tree timeline.
+ // Start by reporting the inconsistent signed tree notes.
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "SECURITY ERROR\n")
+ fmt.Fprintf(&buf, "go.sum database server misbehavior detected!\n\n")
+ indent := func(b []byte) []byte {
+ return bytes.Replace(b, []byte("\n"), []byte("\n\t"), -1)
+ }
+ fmt.Fprintf(&buf, "old database:\n\t%s\n", indent(olderNote))
+ fmt.Fprintf(&buf, "new database:\n\t%s\n", indent(newerNote))
+
+ // The notes alone are not enough to prove the inconsistency.
+ // We also need to show that the newer note's tree hash for older.N
+ // does not match older.Hash. The consumer of this report could
+ // of course consult the server to try to verify the inconsistency,
+ // but we are holding all the bits we need to prove it right now,
+ // so we might as well print them and make the report not depend
+ // on the continued availability of the misbehaving server.
+ // Preparing this data only reuses the tiled hashes needed for
+ // tlog.TreeHash(older.N, thr) above, so assuming thr is caching tiles,
+ // there are no new access to the server here, and these operations cannot fail.
+ fmt.Fprintf(&buf, "proof of misbehavior:\n\t%v", h)
+ if p, err := tlog.ProveTree(newer.N, older.N, thr); err != nil {
+ fmt.Fprintf(&buf, "\tinternal error: %v\n", err)
+ } else if err := tlog.CheckTree(p, newer.N, newer.Hash, older.N, h); err != nil {
+ fmt.Fprintf(&buf, "\tinternal error: generated inconsistent proof\n")
+ } else {
+ for _, h := range p {
+ fmt.Fprintf(&buf, "\n\t%v", h)
+ }
+ }
+ c.client.SecurityError(buf.String())
+ return ErrSecurity
+}
+
+// checkRecord checks that record #id's hash matches data.
+func (c *Conn) checkRecord(id int64, data []byte) error {
+ c.latestMu.Lock()
+ latest := c.latest
+ c.latestMu.Unlock()
+
+ if id >= latest.N {
+ return fmt.Errorf("cannot validate record %d in tree of size %d", id, latest.N)
+ }
+ hashes, err := tlog.TileHashReader(latest, &c.tileReader).ReadHashes([]int64{tlog.StoredHashIndex(0, id)})
+ if err != nil {
+ return err
+ }
+ if hashes[0] == tlog.RecordHash(data) {
+ return nil
+ }
+ return fmt.Errorf("cannot authenticate record data in server response")
+}
+
+// tileReader is a *Conn wrapper that implements tlog.TileReader.
+// The separate type avoids exposing the ReadTiles and SaveTiles
+// methods on Conn itself.
+type tileReader struct {
+ c *Conn
+}
+
+func (r *tileReader) Height() int {
+ return r.c.tileHeight
+}
+
+// ReadTiles reads and returns the requested tiles,
+// either from the on-disk cache or the server.
+func (r *tileReader) ReadTiles(tiles []tlog.Tile) ([][]byte, error) {
+ // Read all the tiles in parallel.
+ data := make([][]byte, len(tiles))
+ errs := make([]error, len(tiles))
+ var wg sync.WaitGroup
+ for i, tile := range tiles {
+ wg.Add(1)
+ go func(i int, tile tlog.Tile) {
+ defer wg.Done()
+ data[i], errs[i] = r.c.readTile(tile)
+ }(i, tile)
+ }
+ wg.Wait()
+
+ for _, err := range errs {
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return data, nil
+}
+
+// tileCacheKey returns the cache key for the tile.
+func (c *Conn) tileCacheKey(tile tlog.Tile) string {
+ return c.name + "/" + tile.Path()
+}
+
+// tileRemotePath returns the remote path for the tile.
+func (c *Conn) tileRemotePath(tile tlog.Tile) string {
+ return "/" + tile.Path()
+}
+
+// readTile reads a single tile, either from the on-disk cache or the server.
+func (c *Conn) readTile(tile tlog.Tile) ([]byte, error) {
+ type cached struct {
+ data []byte
+ err error
+ }
+
+ result := c.tileCache.Do(tile, func() interface{} {
+ // Try the requested tile in on-disk cache.
+ data, err := c.client.ReadCache(c.tileCacheKey(tile))
+ if err == nil {
+ c.markTileSaved(tile)
+ return cached{data, nil}
+ }
+
+ // Try the full tile in on-disk cache (if requested tile not already full).
+ // We only save authenticated tiles to the on-disk cache,
+ // so the recreated prefix is equally authenticated.
+ full := tile
+ full.W = 1 << uint(tile.H)
+ if tile != full {
+ data, err := c.client.ReadCache(c.tileCacheKey(full))
+ if err == nil {
+ c.markTileSaved(tile) // don't save tile later; we already have full
+ return cached{data[:len(data)/full.W*tile.W], nil}
+ }
+ }
+
+ // Try requested tile from server.
+ data, err = c.client.ReadRemote(c.tileRemotePath(tile))
+ if err == nil {
+ return cached{data, nil}
+ }
+
+ // Try full tile on server.
+ // If the partial tile does not exist, it should be because
+ // the tile has been completed and only the complete one
+ // is available.
+ if tile != full {
+ data, err := c.client.ReadRemote(c.tileRemotePath(full))
+ if err == nil {
+ // Note: We could save the full tile in the on-disk cache here,
+ // but we don't know if it is valid yet, and we will only find out
+ // about the partial data, not the full data. So let SaveTiles
+ // save the partial tile, and we'll just refetch the full tile later
+ // once we can validate more (or all) of it.
+ return cached{data[:len(data)/full.W*tile.W], nil}
+ }
+ }
+
+ // Nothing worked.
+ // Return the error from the server fetch for the requested (not full) tile.
+ return cached{nil, err}
+ }).(cached)
+
+ return result.data, result.err
+}
+
+// markTileSaved records that tile is already present in the on-disk cache,
+// so that a future SaveTiles for that tile can be ignored.
+func (c *Conn) markTileSaved(tile tlog.Tile) {
+ c.tileSavedMu.Lock()
+ c.tileSaved[tile] = true
+ c.tileSavedMu.Unlock()
+}
+
+// SaveTiles saves the now validated tiles.
+func (r *tileReader) SaveTiles(tiles []tlog.Tile, data [][]byte) {
+ c := r.c
+
+ // Determine which tiles need saving.
+ // (Tiles that came from the cache need not be saved back.)
+ save := make([]bool, len(tiles))
+ c.tileSavedMu.Lock()
+ for i, tile := range tiles {
+ if !c.tileSaved[tile] {
+ save[i] = true
+ c.tileSaved[tile] = true
+ }
+ }
+ c.tileSavedMu.Unlock()
+
+ for i, tile := range tiles {
+ if save[i] {
+ // If WriteCache fails here (out of disk space? i/o error?),
+ // c.tileSaved[tile] is still true and we will not try to write it again.
+ // Next time we run maybe we'll redownload it again and be
+ // more successful.
+ c.client.WriteCache(c.name+"/"+tile.Path(), data[i])
+ }
+ }
+}
diff --git a/sumdb/internal/sumweb/client_test.go b/sumdb/internal/sumweb/client_test.go
new file mode 100644
index 0000000..f09ccb8
--- /dev/null
+++ b/sumdb/internal/sumweb/client_test.go
@@ -0,0 +1,460 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sumweb
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "sync"
+ "testing"
+
+ "golang.org/x/exp/sumdb/internal/note"
+ "golang.org/x/exp/sumdb/internal/tlog"
+)
+
+const (
+ testName = "localhost.localdev/sumdb"
+ testVerifierKey = "localhost.localdev/sumdb+00000c67+AcTrnkbUA+TU4heY3hkjiSES/DSQniBqIeQ/YppAUtK6"
+ testSignerKey = "PRIVATE+KEY+localhost.localdev/sumdb+00000c67+AXu6+oaVaOYuQOFrf1V59JK1owcFlJcHwwXHDfDGxSPk"
+)
+
+func TestConnLookup(t *testing.T) {
+ tc := newTestClient(t)
+ tc.mustHaveLatest(1)
+
+ // Basic lookup.
+ tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
+ tc.mustHaveLatest(3)
+
+ // Everything should now be cached, both for the original package and its /go.mod.
+ tc.getOK = false
+ tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
+ tc.mustLookup("rsc.io/sampler", "v1.3.0/go.mod", "rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=")
+ tc.mustHaveLatest(3)
+ tc.getOK = true
+ tc.getTileOK = false // the cache has what we need
+
+ // Lookup with multiple returned lines.
+ tc.mustLookup("rsc.io/quote", "v1.5.2", "rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=\nrsc.io/quote v1.5.2 h2:xyzzy")
+ tc.mustHaveLatest(3)
+
+ // Lookup with need for !-encoding.
+ // rsc.io/Quote is the only record written after rsc.io/samper,
+ // so it is the only one that should need more tiles.
+ tc.getTileOK = true
+ tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
+ tc.mustHaveLatest(4)
+}
+
+func TestConnBadTiles(t *testing.T) {
+ tc := newTestClient(t)
+
+ flipBits := func() {
+ for url, data := range tc.remote {
+ if strings.Contains(url, "/tile/") {
+ for i := range data {
+ data[i] ^= 0x80
+ }
+ }
+ }
+ }
+
+ // Bad tiles in initial download.
+ tc.mustHaveLatest(1)
+ flipBits()
+ _, err := tc.conn.Lookup("rsc.io/sampler", "v1.3.0")
+ tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumweb.Conn: checking tree#1: downloaded inconsistent tile")
+ flipBits()
+ tc.newConn()
+ tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
+
+ // Bad tiles after initial download.
+ flipBits()
+ _, err = tc.conn.Lookup("rsc.io/Quote", "v1.5.2")
+ tc.mustError(err, "rsc.io/Quote@v1.5.2: checking tree#3 against tree#4: downloaded inconsistent tile")
+ flipBits()
+ tc.newConn()
+ tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
+
+ // Bad starting tree hash looks like bad tiles.
+ tc.newConn()
+ text := tlog.FormatTree(tlog.Tree{N: 1, Hash: tlog.Hash{}})
+ data, err := note.Sign(&note.Note{Text: string(text)}, tc.signer)
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ tc.config[testName+"/latest"] = data
+ _, err = tc.conn.Lookup("rsc.io/sampler", "v1.3.0")
+ tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumweb.Conn: checking tree#1: downloaded inconsistent tile")
+}
+
+func TestConnFork(t *testing.T) {
+ tc := newTestClient(t)
+ tc2 := tc.fork()
+
+ tc.addRecord("rsc.io/pkg1@v1.5.2", `rsc.io/pkg1 v1.5.2 h1:hash!=
+`)
+ tc.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
+`)
+ tc.mustLookup("rsc.io/pkg1", "v1.5.2", "rsc.io/pkg1 v1.5.2 h1:hash!=")
+
+ tc2.addRecord("rsc.io/pkg1@v1.5.3", `rsc.io/pkg1 v1.5.3 h1:hash!=
+`)
+ tc2.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
+`)
+ tc2.mustLookup("rsc.io/pkg1", "v1.5.4", "rsc.io/pkg1 v1.5.4 h1:hash!=")
+
+ key := "/lookup/rsc.io/pkg1@v1.5.2"
+ tc2.remote[key] = tc.remote[key]
+ _, err := tc2.conn.Lookup("rsc.io/pkg1", "v1.5.2")
+ tc2.mustError(err, ErrSecurity.Error())
+
+ /*
+ SECURITY ERROR
+ go.sum database server misbehavior detected!
+
+ old database:
+ go.sum database tree!
+ 5
+ nWzN20+pwMt62p7jbv1/NlN95ePTlHijabv5zO/s36w=
+
+ β€” localhost.localdev/sumdb AAAMZ5/2FVAdMH58kmnz/0h299pwyskEbzDzoa2/YaPdhvLya4YWDFQQxu2TQb5GpwAH4NdWnTwuhILafisyf3CNbgg=
+
+ new database:
+ go.sum database tree
+ 6
+ wc4SkQt52o5W2nQ8To2ARs+mWuUJjss+sdleoiqxMmM=
+
+ β€” localhost.localdev/sumdb AAAMZ6oRNswlEZ6ZZhxrCvgl1MBy+nusq4JU+TG6Fe2NihWLqOzb+y2c2kzRLoCr4tvw9o36ucQEnhc20e4nA4Qc/wc=
+
+ proof of misbehavior:
+ T7i+H/8ER4nXOiw4Bj0koZOkGjkxoNvlI34GpvhHhQg=
+ Nsuejv72de9hYNM5bqFv8rv3gm3zJQwv/DT/WNbLDLA=
+ mOmqqZ1aI/lzS94oq/JSbj7pD8Rv9S+xDyi12BtVSHo=
+ /7Aw5jVSMM9sFjQhaMg+iiDYPMk6decH7QLOGrL9Lx0=
+ */
+
+ wants := []string{
+ "SECURITY ERROR",
+ "go.sum database server misbehavior detected!",
+ "old database:\n\tgo.sum database tree\n\t5\n",
+ "β€” localhost.localdev/sumdb AAAMZ5/2FVAd",
+ "new database:\n\tgo.sum database tree\n\t6\n",
+ "β€” localhost.localdev/sumdb AAAMZ6oRNswl",
+ "proof of misbehavior:\n\tT7i+H/8ER4nXOiw4Bj0k",
+ }
+ text := tc2.security.String()
+ for _, want := range wants {
+ if !strings.Contains(text, want) {
+ t.Fatalf("cannot find %q in security text:\n%s", want, text)
+ }
+ }
+}
+
+func TestConnGONOSUMDB(t *testing.T) {
+ tc := newTestClient(t)
+ tc.conn.SetGONOSUMDB("p,*/q")
+ tc.conn.Lookup("rsc.io/sampler", "v1.3.0") // initialize before we turn off network
+ tc.getOK = false
+
+ ok := []string{
+ "abc",
+ "a/p",
+ "pq",
+ "q",
+ "n/o/p/q",
+ }
+ skip := []string{
+ "p",
+ "p/x",
+ "x/q",
+ "x/q/z",
+ }
+
+ for _, path := range ok {
+ _, err := tc.conn.Lookup(path, "v1.0.0")
+ if err == ErrGONOSUMDB {
+ t.Errorf("Lookup(%q): ErrGONOSUMDB, wanted failed actual lookup", path)
+ }
+ }
+ for _, path := range skip {
+ _, err := tc.conn.Lookup(path, "v1.0.0")
+ if err != ErrGONOSUMDB {
+ t.Errorf("Lookup(%q): %v, wanted ErrGONOSUMDB", path, err)
+ }
+ }
+}
+
+// A testClient is a self-contained client-side testing environment.
+type testClient struct {
+ t *testing.T // active test
+ conn *Conn // conn being tested
+ tileHeight int // tile height to use (default 2)
+ getOK bool // should tc.GetURL succeed?
+ getTileOK bool // should tc.GetURL of tiles succeed?
+ treeSize int64
+ hashes []tlog.Hash
+ remote map[string][]byte
+ signer note.Signer
+
+ // mu protects config, cache, log, security
+ // during concurrent use of the exported methods
+ // by the conn itself (testClient is the Conn's Client,
+ // and the Client methods can both read and write these fields).
+ // Unexported methods invoked directly by the test
+ // (for example, addRecord) need not hold the mutex:
+ // for proper test execution those methods should only
+ // be called when the Conn is idle and not using its Client.
+ // Not holding the mutex in those methods ensures
+ // that if a mistake is made, go test -race will report it.
+ // (Holding the mutex would eliminate the race report but
+ // not the underlying problem.)
+ // Similarly, the get map is not protected by the mutex,
+ // because the Client methods only read it.
+ mu sync.Mutex // prot
+ config map[string][]byte
+ cache map[string][]byte
+ security bytes.Buffer
+}
+
+// newTestClient returns a new testClient that will call t.Fatal on error
+// and has a few records already available on the remote server.
+func newTestClient(t *testing.T) *testClient {
+ tc := &testClient{
+ t: t,
+ tileHeight: 2,
+ getOK: true,
+ getTileOK: true,
+ config: make(map[string][]byte),
+ cache: make(map[string][]byte),
+ remote: make(map[string][]byte),
+ }
+
+ tc.config["key"] = []byte(testVerifierKey + "\n")
+ var err error
+ tc.signer, err = note.NewSigner(testSignerKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tc.newConn()
+
+ tc.addRecord("rsc.io/quote@v1.5.2", `rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/quote v1.5.2 h2:xyzzy
+`)
+
+ tc.addRecord("golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c", `golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+`)
+ tc.addRecord("rsc.io/sampler@v1.3.0", `rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+`)
+ tc.config[testName+"/latest"] = tc.signTree(1)
+
+ tc.addRecord("rsc.io/!quote@v1.5.2", `rsc.io/Quote v1.5.2 h1:uppercase!=
+`)
+ return tc
+}
+
+// newConn resets the Conn associated with tc.
+// This clears any in-memory cache from the Conn
+// but not tc's on-disk cache.
+func (tc *testClient) newConn() {
+ tc.conn = NewConn(tc)
+ tc.conn.SetTileHeight(tc.tileHeight)
+}
+
+// mustLookup does a lookup for path@vers and checks that the lines that come back match want.
+func (tc *testClient) mustLookup(path, vers, want string) {
+ tc.t.Helper()
+ lines, err := tc.conn.Lookup(path, vers)
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ if strings.Join(lines, "\n") != want {
+ tc.t.Fatalf("Lookup(%q, %q):\n\t%s\nwant:\n\t%s", path, vers, strings.Join(lines, "\n\t"), strings.Replace(want, "\n", "\n\t", -1))
+ }
+}
+
+// mustHaveLatest checks that the on-disk configuration
+// for latest is a tree of size n.
+func (tc *testClient) mustHaveLatest(n int64) {
+ tc.t.Helper()
+
+ latest := tc.config[testName+"/latest"]
+ lines := strings.Split(string(latest), "\n")
+ if len(lines) < 2 || lines[1] != fmt.Sprint(n) {
+ tc.t.Fatalf("/latest should have tree %d, but has:\n%s", n, latest)
+ }
+}
+
+// mustError checks that err's error string contains the text.
+func (tc *testClient) mustError(err error, text string) {
+ tc.t.Helper()
+ if err == nil || !strings.Contains(err.Error(), text) {
+ tc.t.Fatalf("err = %v, want %q", err, text)
+ }
+}
+
+// fork returns a copy of tc.
+// Changes made to the new copy or to tc are not reflected in the other.
+func (tc *testClient) fork() *testClient {
+ tc2 := &testClient{
+ t: tc.t,
+ getOK: tc.getOK,
+ getTileOK: tc.getTileOK,
+ tileHeight: tc.tileHeight,
+ treeSize: tc.treeSize,
+ hashes: append([]tlog.Hash{}, tc.hashes...),
+ signer: tc.signer,
+ config: copyMap(tc.config),
+ cache: copyMap(tc.cache),
+ remote: copyMap(tc.remote),
+ }
+ tc2.newConn()
+ return tc2
+}
+
+func copyMap(m map[string][]byte) map[string][]byte {
+ m2 := make(map[string][]byte)
+ for k, v := range m {
+ m2[k] = v
+ }
+ return m2
+}
+
+// ReadHashes is tc's implementation of tlog.HashReader, for use with
+// tlog.TreeHash and so on.
+func (tc *testClient) ReadHashes(indexes []int64) ([]tlog.Hash, error) {
+ var list []tlog.Hash
+ for _, id := range indexes {
+ list = append(list, tc.hashes[id])
+ }
+ return list, nil
+}
+
+// addRecord adds a log record using the given (!-encoded) key and data.
+func (tc *testClient) addRecord(key, data string) {
+ tc.t.Helper()
+
+ // Create record, add hashes to log tree.
+ id := tc.treeSize
+ tc.treeSize++
+ rec, err := tlog.FormatRecord(id, []byte(data))
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ hashes, err := tlog.StoredHashesForRecordHash(id, tlog.RecordHash([]byte(data)), tc)
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ tc.hashes = append(tc.hashes, hashes...)
+
+ // Create lookup result.
+ tc.remote["/lookup/"+key] = append(rec, tc.signTree(tc.treeSize)...)
+
+ // Create new tiles.
+ tiles := tlog.NewTiles(tc.tileHeight, id, tc.treeSize)
+ for _, tile := range tiles {
+ data, err := tlog.ReadTileData(tile, tc)
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ tc.remote["/"+tile.Path()] = data
+ // TODO delete old partial tiles
+ }
+}
+
+// signTree returns the signed head for the tree of the given size.
+func (tc *testClient) signTree(size int64) []byte {
+ h, err := tlog.TreeHash(size, tc)
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ text := tlog.FormatTree(tlog.Tree{N: size, Hash: h})
+ data, err := note.Sign(&note.Note{Text: string(text)}, tc.signer)
+ if err != nil {
+ tc.t.Fatal(err)
+ }
+ return data
+}
+
+// ReadRemote is for tc's implementation of Client.
+func (tc *testClient) ReadRemote(path string) ([]byte, error) {
+ // No mutex here because only the Client should be running
+ // and the Client cannot change tc.get.
+ if !tc.getOK {
+ return nil, fmt.Errorf("disallowed remote read %s", path)
+ }
+ if strings.Contains(path, "/tile/") && !tc.getTileOK {
+ return nil, fmt.Errorf("disallowed remote tile read %s", path)
+ }
+
+ data, ok := tc.remote[path]
+ if !ok {
+ return nil, fmt.Errorf("no remote path %s", path)
+ }
+ return data, nil
+}
+
+// ReadConfig is for tc's implementation of Client.
+func (tc *testClient) ReadConfig(file string) ([]byte, error) {
+ tc.mu.Lock()
+ defer tc.mu.Unlock()
+
+ data, ok := tc.config[file]
+ if !ok {
+ return nil, fmt.Errorf("no config %s", file)
+ }
+ return data, nil
+}
+
+// WriteConfig is for tc's implementation of Client.
+func (tc *testClient) WriteConfig(file string, old, new []byte) error {
+ tc.mu.Lock()
+ defer tc.mu.Unlock()
+
+ data := tc.config[file]
+ if !bytes.Equal(old, data) {
+ return ErrWriteConflict
+ }
+ tc.config[file] = new
+ return nil
+}
+
+// ReadCache is for tc's implementation of Client.
+func (tc *testClient) ReadCache(file string) ([]byte, error) {
+ tc.mu.Lock()
+ defer tc.mu.Unlock()
+
+ data, ok := tc.cache[file]
+ if !ok {
+ return nil, fmt.Errorf("no cache %s", file)
+ }
+ return data, nil
+}
+
+// WriteCache is for tc's implementation of Client.
+func (tc *testClient) WriteCache(file string, data []byte) {
+ tc.mu.Lock()
+ defer tc.mu.Unlock()
+
+ tc.cache[file] = data
+}
+
+// Log is for tc's implementation of Client.
+func (tc *testClient) Log(msg string) {
+ tc.t.Log(msg)
+}
+
+// SecurityError is for tc's implementation of Client.
+func (tc *testClient) SecurityError(msg string) {
+ tc.mu.Lock()
+ defer tc.mu.Unlock()
+
+ fmt.Fprintf(&tc.security, "%s\n", strings.TrimRight(msg, "\n"))
+}
diff --git a/sumdb/internal/sumweb/encode.go b/sumdb/internal/sumweb/encode.go
new file mode 100644
index 0000000..d044a84
--- /dev/null
+++ b/sumdb/internal/sumweb/encode.go
@@ -0,0 +1,167 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// FS-safe encoding of module paths and versions.
+// Copied from cmd/go/internal/module and unexported.
+
+package sumweb
+
+import (
+ "fmt"
+ "unicode/utf8"
+)
+
+// Safe encodings
+//
+// Module paths appear as substrings of file system paths
+// (in the download cache) and of web server URLs in the proxy protocol.
+// In general we cannot rely on file systems to be case-sensitive,
+// nor can we rely on web servers, since they read from file systems.
+// That is, we cannot rely on the file system to keep rsc.io/QUOTE
+// and rsc.io/quote separate. Windows and macOS don't.
+// Instead, we must never require two different casings of a file path.
+// Because we want the download cache to match the proxy protocol,
+// and because we want the proxy protocol to be possible to serve
+// from a tree of static files (which might be stored on a case-insensitive
+// file system), the proxy protocol must never require two different casings
+// of a URL path either.
+//
+// One possibility would be to make the safe encoding be the lowercase
+// hexadecimal encoding of the actual path bytes. This would avoid ever
+// needing different casings of a file path, but it would be fairly illegible
+// to most programmers when those paths appeared in the file system
+// (including in file paths in compiler errors and stack traces)
+// in web server logs, and so on. Instead, we want a safe encoding that
+// leaves most paths unaltered.
+//
+// The safe encoding is this:
+// replace every uppercase letter with an exclamation mark
+// followed by the letter's lowercase equivalent.
+//
+// For example,
+// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go.
+// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
+// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
+//
+// Import paths that avoid upper-case letters are left unchanged.
+// Note that because import paths are ASCII-only and avoid various
+// problematic punctuation (like : < and >), the safe encoding is also ASCII-only
+// and avoids the same problematic punctuation.
+//
+// Import paths have never allowed exclamation marks, so there is no
+// need to define how to encode a literal !.
+//
+// Although paths are disallowed from using Unicode (see pathOK above),
+// the eventual plan is to allow Unicode letters as well, to assume that
+// file systems and URLs are Unicode-safe (storing UTF-8), and apply
+// the !-for-uppercase convention. Note however that not all runes that
+// are different but case-fold equivalent are an upper/lower pair.
+// For example, U+004B ('K'), U+006B ('k'), and U+212A ('β„ͺ' for Kelvin)
+// are considered to case-fold to each other. When we do add Unicode
+// letters, we must not assume that upper/lower are the only case-equivalent pairs.
+// Perhaps the Kelvin symbol would be disallowed entirely, for example.
+// Or perhaps it would encode as "!!k", or perhaps as "(212A)".
+//
+// Also, it would be nice to allow Unicode marks as well as letters,
+// but marks include combining marks, and then we must deal not
+// only with case folding but also normalization: both U+00E9 ('Γ©')
+// and U+0065 U+0301 ('e' followed by combining acute accent)
+// look the same on the page and are treated by some file systems
+// as the same path. If we do allow Unicode marks in paths, there
+// must be some kind of normalization to allow only one canonical
+// encoding of any character used in an import path.
+
+// encodePath returns the safe encoding of the given module path.
+// It fails if the module path is invalid.
+func encodePath(path string) (encoding string, err error) {
+ return encodeString(path)
+}
+
+// encodeVersion returns the safe encoding of the given module version.
+// Versions are allowed to be in non-semver form but must be valid file names
+// and not contain exclamation marks.
+func encodeVersion(v string) (encoding string, err error) {
+ return encodeString(v)
+}
+
+func encodeString(s string) (encoding string, err error) {
+ haveUpper := false
+ for _, r := range s {
+ if r == '!' || r >= utf8.RuneSelf {
+ // This should be disallowed by CheckPath, but diagnose anyway.
+ // The correctness of the encoding loop below depends on it.
+ return "", fmt.Errorf("internal error: inconsistency in EncodePath")
+ }
+ if 'A' <= r && r <= 'Z' {
+ haveUpper = true
+ }
+ }
+
+ if !haveUpper {
+ return s, nil
+ }
+
+ var buf []byte
+ for _, r := range s {
+ if 'A' <= r && r <= 'Z' {
+ buf = append(buf, '!', byte(r+'a'-'A'))
+ } else {
+ buf = append(buf, byte(r))
+ }
+ }
+ return string(buf), nil
+}
+
+// decodePath returns the module path of the given safe encoding.
+// It fails if the encoding is invalid or encodes an invalid path.
+func decodePath(encoding string) (path string, err error) {
+ path, ok := decodeString(encoding)
+ if !ok {
+ return "", fmt.Errorf("invalid module path encoding %q", encoding)
+ }
+ return path, nil
+}
+
+// decodeVersion returns the version string for the given safe encoding.
+// It fails if the encoding is invalid or encodes an invalid version.
+// Versions are allowed to be in non-semver form but must be valid file names
+// and not contain exclamation marks.
+func decodeVersion(encoding string) (v string, err error) {
+ v, ok := decodeString(encoding)
+ if !ok {
+ return "", fmt.Errorf("invalid version encoding %q", encoding)
+ }
+ return v, nil
+}
+
+func decodeString(encoding string) (string, bool) {
+ var buf []byte
+
+ bang := false
+ for _, r := range encoding {
+ if r >= utf8.RuneSelf {
+ return "", false
+ }
+ if bang {
+ bang = false
+ if r < 'a' || 'z' < r {
+ return "", false
+ }
+ buf = append(buf, byte(r+'A'-'a'))
+ continue
+ }
+ if r == '!' {
+ bang = true
+ continue
+ }
+ if 'A' <= r && r <= 'Z' {
+ return "", false
+ }
+ buf = append(buf, byte(r))
+ }
+ if bang {
+ return "", false
+ }
+ return string(buf), true
+}
diff --git a/sumdb/internal/sumweb/encode_test.go b/sumdb/internal/sumweb/encode_test.go
new file mode 100644
index 0000000..9ed5e4a
--- /dev/null
+++ b/sumdb/internal/sumweb/encode_test.go
@@ -0,0 +1,67 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sumweb
+
+import "testing"
+
+var encodeTests = []struct {
+ path string
+ enc string // empty means same as path
+}{
+ {path: "ascii.com/abcdefghijklmnopqrstuvwxyz.-+/~_0123456789"},
+ {path: "github.com/GoogleCloudPlatform/omega", enc: "github.com/!google!cloud!platform/omega"},
+}
+
+func TestEncodePath(t *testing.T) {
+ // Check encodings.
+ for _, tt := range encodeTests {
+ enc, err := encodePath(tt.path)
+ if err != nil {
+ t.Errorf("encodePath(%q): unexpected error: %v", tt.path, err)
+ continue
+ }
+ want := tt.enc
+ if want == "" {
+ want = tt.path
+ }
+ if enc != want {
+ t.Errorf("encodePath(%q) = %q, want %q", tt.path, enc, want)
+ }
+ }
+}
+
+var badDecode = []string{
+ "github.com/GoogleCloudPlatform/omega",
+ "github.com/!google!cloud!platform!/omega",
+ "github.com/!0google!cloud!platform/omega",
+ "github.com/!_google!cloud!platform/omega",
+ "github.com/!!google!cloud!platform/omega",
+}
+
+func TestDecodePath(t *testing.T) {
+ // Check invalid decodings.
+ for _, bad := range badDecode {
+ _, err := decodePath(bad)
+ if err == nil {
+ t.Errorf("DecodePath(%q): succeeded, want error (invalid decoding)", bad)
+ }
+ }
+
+ // Check encodings.
+ for _, tt := range encodeTests {
+ enc := tt.enc
+ if enc == "" {
+ enc = tt.path
+ }
+ path, err := decodePath(enc)
+ if err != nil {
+ t.Errorf("decodePath(%q): unexpected error: %v", enc, err)
+ continue
+ }
+ if path != tt.path {
+ t.Errorf("decodePath(%q) = %q, want %q", enc, path, tt.path)
+ }
+ }
+}
diff --git a/sumdb/internal/sumweb/server.go b/sumdb/internal/sumweb/server.go
new file mode 100644
index 0000000..d25f5a3
--- /dev/null
+++ b/sumdb/internal/sumweb/server.go
@@ -0,0 +1,182 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package sumweb implements the HTTP protocols for serving or accessing a go.sum database.
+package sumweb
+
+import (
+ "context"
+ "net/http"
+ "os"
+ "regexp"
+ "strings"
+
+ "golang.org/x/exp/sumdb/internal/tlog"
+)
+
+// A Server provides the external operations
+// (underlying database access and so on)
+// needed to implement the HTTP server Handler.
+type Server interface {
+ // NewContext returns the context to use for the request r.
+ NewContext(r *http.Request) (context.Context, error)
+
+ // Signed returns the signed hash of the latest tree.
+ Signed(ctx context.Context) ([]byte, error)
+
+ // ReadRecords returns the content for the n records id through id+n-1.
+ ReadRecords(ctx context.Context, id, n int64) ([][]byte, error)
+
+ // Lookup looks up a record by its associated key ("module@version"),
+ // returning the record ID.
+ Lookup(ctx context.Context, key string) (int64, error)
+
+ // ReadTileData reads the content of tile t.
+ // It is only invoked for hash tiles (t.L β‰₯ 0).
+ ReadTileData(ctx context.Context, t tlog.Tile) ([]byte, error)
+}
+
+// A Handler is the go.sum database server handler,
+// which should be invoked to serve the paths listed in Paths.
+// The calling code is responsible for initializing Server.
+type Handler struct {
+ Server Server
+}
+
+// Paths are the URL paths for which Handler should be invoked.
+//
+// Typically a server will do:
+//
+// handler := &sumweb.Handler{Server: srv}
+// for _, path := range sumweb.Paths {
+// http.HandleFunc(path, handler)
+// }
+var Paths = []string{
+ "/lookup/",
+ "/latest",
+ "/tile/",
+}
+
+var modVerRE = regexp.MustCompile(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ ctx, err := h.Server.NewContext(r)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ switch {
+ default:
+ http.NotFound(w, r)
+
+ case strings.HasPrefix(r.URL.Path, "/lookup/"):
+ mod := strings.TrimPrefix(r.URL.Path, "/lookup/")
+ if !modVerRE.MatchString(mod) {
+ http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
+ return
+ }
+ i := strings.Index(mod, "@")
+ encPath, encVers := mod[:i], mod[i+1:]
+ path, err := decodePath(encPath)
+ if err != nil {
+ reportError(w, r, err)
+ return
+ }
+ vers, err := decodeVersion(encVers)
+ if err != nil {
+ reportError(w, r, err)
+ return
+ }
+ id, err := h.Server.Lookup(ctx, path+"@"+vers)
+ if err != nil {
+ reportError(w, r, err)
+ return
+ }
+ records, err := h.Server.ReadRecords(ctx, id, 1)
+ if err != nil {
+ // This should never happen - the lookup says the record exists.
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if len(records) != 1 {
+ http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
+ return
+ }
+ msg, err := tlog.FormatRecord(id, records[0])
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ signed, err := h.Server.Signed(ctx)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
+ w.Write(msg)
+ w.Write(signed)
+
+ case r.URL.Path == "/latest":
+ data, err := h.Server.Signed(ctx)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
+ w.Write(data)
+
+ case strings.HasPrefix(r.URL.Path, "/tile/"):
+ t, err := tlog.ParseTilePath(r.URL.Path[1:])
+ if err != nil {
+ http.Error(w, "invalid tile syntax", http.StatusBadRequest)
+ return
+ }
+ if t.L == -1 {
+ // Record data.
+ start := t.N << uint(t.H)
+ records, err := h.Server.ReadRecords(ctx, start, int64(t.W))
+ if err != nil {
+ reportError(w, r, err)
+ return
+ }
+ if len(records) != t.W {
+ http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
+ return
+ }
+ var data []byte
+ for i, text := range records {
+ msg, err := tlog.FormatRecord(start+int64(i), text)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ data = append(data, msg...)
+ }
+ w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
+ w.Write(data)
+ return
+ }
+
+ data, err := h.Server.ReadTileData(ctx, t)
+ if err != nil {
+ reportError(w, r, err)
+ return
+ }
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.Write(data)
+ }
+}
+
+// reportError reports err to w.
+// If it's a not-found, the reported error is 404.
+// Otherwise it is an internal server error.
+// The caller must only call reportError in contexts where
+// a not-found err should be reported as 404.
+func reportError(w http.ResponseWriter, r *http.Request, err error) {
+ if os.IsNotExist(err) {
+ http.Error(w, err.Error(), http.StatusNotFound)
+ return
+ }
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+}
diff --git a/sumdb/internal/sumweb/test.go b/sumdb/internal/sumweb/test.go
new file mode 100644
index 0000000..fd75fc3
--- /dev/null
+++ b/sumdb/internal/sumweb/test.go
@@ -0,0 +1,133 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sumweb
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+ "sync"
+
+ "golang.org/x/exp/sumdb/internal/note"
+ "golang.org/x/exp/sumdb/internal/tlog"
+)
+
+// NewTestServer constructs a new TestServer
+// that will sign its tree with the given signer key
+// (see golang.org/x/exp/sumdb/internal/note)
+// and fetch new records as needed by calling gosum.
+func NewTestServer(signer string, gosum func(path, vers string) ([]byte, error)) *TestServer {
+ return &TestServer{signer: signer, gosum: gosum}
+}
+
+// A TestServer is an in-memory implementation of Server for testing.
+type TestServer struct {
+ signer string
+ gosum func(path, vers string) ([]byte, error)
+
+ mu sync.Mutex
+ hashes testHashes
+ records [][]byte
+ lookup map[string]int64
+}
+
+// testHashes implements tlog.HashReader, reading from a slice.
+type testHashes []tlog.Hash
+
+func (h testHashes) ReadHashes(indexes []int64) ([]tlog.Hash, error) {
+ var list []tlog.Hash
+ for _, id := range indexes {
+ list = append(list, h[id])
+ }
+ return list, nil
+}
+
+func (s *TestServer) NewContext(r *http.Request) (context.Context, error) {
+ return nil, nil
+}
+
+func (s *TestServer) Signed(ctx context.Context) ([]byte, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ size := int64(len(s.records))
+ h, err := tlog.TreeHash(size, s.hashes)
+ if err != nil {
+ return nil, err
+ }
+ text := tlog.FormatTree(tlog.Tree{N: size, Hash: h})
+ signer, err := note.NewSigner(s.signer)
+ if err != nil {
+ return nil, err
+ }
+ return note.Sign(&note.Note{Text: string(text)}, signer)
+}
+
+func (s *TestServer) ReadRecords(ctx context.Context, id, n int64) ([][]byte, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ var list [][]byte
+ for i := int64(0); i < n; i++ {
+ if id+i >= int64(len(s.records)) {
+ return nil, fmt.Errorf("missing records")
+ }
+ list = append(list, s.records[id+i])
+ }
+ return list, nil
+}
+
+func (s *TestServer) Lookup(ctx context.Context, key string) (int64, error) {
+ s.mu.Lock()
+ id, ok := s.lookup[key]
+ s.mu.Unlock()
+ if ok {
+ return id, nil
+ }
+
+ // Look up module and compute go.sum lines.
+ i := strings.Index(key, "@")
+ if i < 0 {
+ return 0, fmt.Errorf("invalid lookup key %q", key)
+ }
+ path, vers := key[:i], key[i+1:]
+ data, err := s.gosum(path, vers)
+ if err != nil {
+ return 0, err
+ }
+
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ // We ran the fetch without the lock.
+ // If another fetch happened and committed, use it instead.
+ id, ok = s.lookup[key]
+ if ok {
+ return id, nil
+ }
+
+ // Add record.
+ id = int64(len(s.records))
+ s.records = append(s.records, data)
+ if s.lookup == nil {
+ s.lookup = make(map[string]int64)
+ }
+ s.lookup[key] = id
+ hashes, err := tlog.StoredHashesForRecordHash(id, tlog.RecordHash([]byte(data)), s.hashes)
+ if err != nil {
+ panic(err)
+ }
+ s.hashes = append(s.hashes, hashes...)
+
+ return id, nil
+}
+
+func (s *TestServer) ReadTileData(ctx context.Context, t tlog.Tile) ([]byte, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ return tlog.ReadTileData(t, s.hashes)
+}
diff --git a/sumdb/internal/tkv/tkv.go b/sumdb/internal/tkv/tkv.go
new file mode 100644
index 0000000..b58bc59
--- /dev/null
+++ b/sumdb/internal/tkv/tkv.go
@@ -0,0 +1,57 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tkv defines a simple interface to a
+// transactional key-value storage system.
+package tkv
+
+import "context"
+
+// A Storage is a transaction key-value storage system.
+type Storage interface {
+ // ReadOnly runs f in a read-only transaction.
+ // It is equivalent to ReadWrite except that the
+ // transaction's BufferWrite method will fail unconditionally.
+ // (The implementation may be able to optimize the
+ // transaction if it knows at the start that no writes will happen.)
+ ReadOnly(ctx context.Context, f func(context.Context, Transaction) error) error
+
+ // ReadWrite runs f in a read-write transaction.
+ // If f returns an error, the transaction aborts and returns that error.
+ // If f returns nil, the transaction attempts to commit and then then return nil.
+ // Otherwise it tries again. Note that f may be called multiple times and that
+ // the result only describes the effect of the final call to f.
+ // The caller must take care not to use any state computed during
+ // earlier calls to f, or even the last call to f when an error is returned.
+ ReadWrite(ctx context.Context, f func(context.Context, Transaction) error) error
+}
+
+// A Transaction provides read and write operations within a transaction,
+// as executed by Storage's ReadOnly or ReadWrite methods.
+type Transaction interface {
+ // ReadValue reads the value associated with a single key.
+ // If there is no value associated with that key, ReadKey returns an empty value.
+ // An error is only returned for problems accessing the storage.
+ ReadValue(ctx context.Context, key string) (value string, err error)
+
+ // ReadValues reads the values associated with the given keys.
+ // If there is no value stored for a given key, ReadValues returns an empty value for that key.
+ // An error is only returned for problems accessing the storage.
+ ReadValues(ctx context.Context, keys []string) (values []string, err error)
+
+ // BufferWrites buffers the given writes,
+ // to be applied at the end of the transaction.
+ // BufferWrites panics if this is a ReadOnly transaction.
+ // It returns an error if it detects any other problems.
+ // The behavior of multiple writes buffered using the same key
+ // is undefined: it may return an error or not.
+ BufferWrites(writes []Write) error
+}
+
+// A Write is a single change to be applied at the end of a read-write transaction.
+// A Write with an empty value deletes the value associated with the given key.
+type Write struct {
+ Key string
+ Value string
+}
diff --git a/sumdb/internal/tkv/tkvtest/mem.go b/sumdb/internal/tkv/tkvtest/mem.go
new file mode 100644
index 0000000..780edc4
--- /dev/null
+++ b/sumdb/internal/tkv/tkvtest/mem.go
@@ -0,0 +1,116 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tkvtest
+
+import (
+ "context"
+ "errors"
+ "math/rand"
+ "sync"
+
+ "golang.org/x/exp/sumdb/internal/tkv"
+)
+
+// Mem is an in-memory implementation of Storage.
+// It is meant for tests and does not store any data to persistent storage.
+//
+// The zero value is an empty Mem ready for use.
+type Mem struct {
+ mu sync.RWMutex
+ table map[string]string
+}
+
+// A memTx is a transaction in a Mem.
+type memTx struct {
+ m *Mem
+ writes []tkv.Write
+}
+
+// errRetry is an internal sentinel indicating that the transaction should be retried.
+// It is never returned to the caller.
+var errRetry = errors.New("retry")
+
+// ReadOnly runs f in a read-only transaction.
+func (m *Mem) ReadOnly(ctx context.Context, f func(context.Context, tkv.Transaction) error) error {
+ tx := &memTx{m: m}
+ for {
+ err := func() error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ if err := f(ctx, tx); err != nil {
+ return err
+ }
+ // Spurious retry with 10% probability.
+ if rand.Intn(10) == 0 {
+ return errRetry
+ }
+ return nil
+ }()
+ if err != errRetry {
+ return err
+ }
+ }
+}
+
+// ReadWrite runs f in a read-write transaction.
+func (m *Mem) ReadWrite(ctx context.Context, f func(context.Context, tkv.Transaction) error) error {
+ tx := &memTx{m: m}
+ for {
+ err := func() error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ tx.writes = []tkv.Write{}
+ if err := f(ctx, tx); err != nil {
+ return err
+ }
+ // Spurious retry with 10% probability.
+ if rand.Intn(10) == 0 {
+ return errRetry
+ }
+ if m.table == nil {
+ m.table = make(map[string]string)
+ }
+ for _, w := range tx.writes {
+ if w.Value == "" {
+ delete(m.table, w.Key)
+ } else {
+ m.table[w.Key] = w.Value
+ }
+ }
+ return nil
+ }()
+ if err != errRetry {
+ return err
+ }
+ }
+}
+
+// ReadValues returns the values associated with the given keys.
+func (tx *memTx) ReadValues(ctx context.Context, keys []string) ([]string, error) {
+ vals := make([]string, len(keys))
+ for i, key := range keys {
+ vals[i] = tx.m.table[key]
+ }
+ return vals, nil
+}
+
+// ReadValue returns the value associated with the single key.
+func (tx *memTx) ReadValue(ctx context.Context, key string) (string, error) {
+ return tx.m.table[key], nil
+}
+
+// BufferWrites buffers a list of writes to be applied
+// to the table when the transaction commits.
+// The changes are not visible to reads within the transaction.
+// The map argument is not used after the call returns.
+func (tx *memTx) BufferWrites(list []tkv.Write) error {
+ if tx.writes == nil {
+ panic("BufferWrite on read-only transaction")
+ }
+ tx.writes = append(tx.writes, list...)
+ return nil
+}
diff --git a/sumdb/internal/tkv/tkvtest/mem_test.go b/sumdb/internal/tkv/tkvtest/mem_test.go
new file mode 100644
index 0000000..ac0c48b
--- /dev/null
+++ b/sumdb/internal/tkv/tkvtest/mem_test.go
@@ -0,0 +1,14 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tkvtest
+
+import (
+ "context"
+ "testing"
+)
+
+func TestMem(t *testing.T) {
+ TestStorage(t, context.Background(), new(Mem))
+}
diff --git a/sumdb/internal/tkv/tkvtest/test.go b/sumdb/internal/tkv/tkvtest/test.go
new file mode 100644
index 0000000..7f124b1
--- /dev/null
+++ b/sumdb/internal/tkv/tkvtest/test.go
@@ -0,0 +1,77 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tkvtest contains a test harness for testing tkv.Storage implementations.
+package tkvtest
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "testing"
+
+ "golang.org/x/exp/sumdb/internal/tkv"
+)
+
+func TestStorage(t *testing.T, ctx context.Context, storage tkv.Storage) {
+ s := storage
+
+ // Insert records.
+ err := s.ReadWrite(ctx, func(ctx context.Context, tx tkv.Transaction) error {
+ for i := 0; i < 10; i++ {
+ err := tx.BufferWrites([]tkv.Write{
+ {Key: fmt.Sprint(i), Value: fmt.Sprint(-i)},
+ {Key: fmt.Sprint(1000 + i), Value: fmt.Sprint(-1000 - i)},
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Read the records back.
+ testRead := func() {
+ err := s.ReadOnly(ctx, func(ctx context.Context, tx tkv.Transaction) error {
+ for i := int64(0); i < 1010; i++ {
+ if i == 10 {
+ i = 1000
+ }
+ val, err := tx.ReadValue(ctx, fmt.Sprint(i))
+ if err != nil {
+ t.Fatalf("reading %v: %v", i, err)
+ }
+ if val != fmt.Sprint(-i) {
+ t.Fatalf("ReadValue %v = %q, want %v", i, val, fmt.Sprint(-i))
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ testRead()
+
+ // Buffered writes in failed transaction should not be applied.
+ err = s.ReadWrite(ctx, func(ctx context.Context, tx tkv.Transaction) error {
+ tx.BufferWrites([]tkv.Write{
+ {Key: fmt.Sprint(0), Value: ""}, // delete
+ {Key: fmt.Sprint(1), Value: "overwrite"}, // overwrite
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ return io.ErrUnexpectedEOF
+ })
+ if err != io.ErrUnexpectedEOF {
+ t.Fatalf("ReadWrite returned %v, want ErrUnexpectedEOF", err)
+ }
+
+ // All same values should still be there.
+ testRead()
+}
diff --git a/sumdb/internal/tlog/ct_test.go b/sumdb/internal/tlog/ct_test.go
new file mode 100644
index 0000000..f8c364b
--- /dev/null
+++ b/sumdb/internal/tlog/ct_test.go
@@ -0,0 +1,96 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tlog
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "testing"
+)
+
+func TestCertificateTransparency(t *testing.T) {
+ // Test that we can verify actual Certificate Transparency proofs.
+ // (The other tests check that we can verify our own proofs;
+ // this is a test that the two are compatible.)
+
+ if testing.Short() {
+ t.Skip("skipping in -short mode")
+ }
+
+ var root ctTree
+ httpGET(t, "http://ct.googleapis.com/logs/argon2020/ct/v1/get-sth", &root)
+
+ var leaf ctEntries
+ httpGET(t, "http://ct.googleapis.com/logs/argon2020/ct/v1/get-entries?start=10000&end=10000", &leaf)
+ hash := RecordHash(leaf.Entries[0].Data)
+
+ var rp ctRecordProof
+ httpGET(t, "http://ct.googleapis.com/logs/argon2020/ct/v1/get-proof-by-hash?tree_size="+fmt.Sprint(root.Size)+"&hash="+url.QueryEscape(hash.String()), &rp)
+
+ err := CheckRecord(rp.Proof, root.Size, root.Hash, 10000, hash)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var tp ctTreeProof
+ httpGET(t, "http://ct.googleapis.com/logs/argon2020/ct/v1/get-sth-consistency?first=3654490&second="+fmt.Sprint(root.Size), &tp)
+
+ oh, _ := ParseHash("AuIZ5V6sDUj1vn3Y1K85oOaQ7y+FJJKtyRTl1edIKBQ=")
+ err = CheckTree(tp.Proof, root.Size, root.Hash, 3654490, oh)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+type ctTree struct {
+ Size int64 `json:"tree_size"`
+ Hash Hash `json:"sha256_root_hash"`
+}
+
+type ctEntries struct {
+ Entries []*ctEntry
+}
+
+type ctEntry struct {
+ Data []byte `json:"leaf_input"`
+}
+
+type ctRecordProof struct {
+ Index int64 `json:"leaf_index"`
+ Proof RecordProof `json:"audit_path"`
+}
+
+type ctTreeProof struct {
+ Proof TreeProof `json:"consistency"`
+}
+
+func httpGET(t *testing.T, url string, targ interface{}) {
+ if testing.Verbose() {
+ println()
+ println(url)
+ }
+ resp, err := http.Get(url)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+ data, err := io.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if testing.Verbose() {
+ os.Stdout.Write(data)
+ }
+ err = json.Unmarshal(data, targ)
+ if err != nil {
+ println(url)
+ os.Stdout.Write(data)
+ t.Fatal(err)
+ }
+}
diff --git a/sumdb/internal/tlog/note.go b/sumdb/internal/tlog/note.go
new file mode 100644
index 0000000..65c7164
--- /dev/null
+++ b/sumdb/internal/tlog/note.go
@@ -0,0 +1,135 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tlog
+
+import (
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+)
+
+// A Tree is a tree description, to be signed by a go.sum database server.
+type Tree struct {
+ N int64
+ Hash Hash
+}
+
+// FormatTree formats a tree description for inclusion in a note.
+//
+// The encoded form is three lines, each ending in a newline (U+000A):
+//
+// go.sum database tree
+// N
+// Hash
+//
+// where N is in decimal and Hash is in base64.
+//
+// A future backwards-compatible encoding may add additional lines,
+// which the parser can ignore.
+// A future backwards-incompatible encoding would use a different
+// first line (for example, "go.sum database tree v2").
+func FormatTree(tree Tree) []byte {
+ return []byte(fmt.Sprintf("go.sum database tree\n%d\n%s\n", tree.N, tree.Hash))
+}
+
+var errMalformedTree = errors.New("malformed tree note")
+var treePrefix = []byte("go.sum database tree\n")
+
+// ParseTree parses a tree root description.
+func ParseTree(text []byte) (tree Tree, err error) {
+ // The message looks like:
+ //
+ // go.sum database tree
+ // 2
+ // nND/nri/U0xuHUrYSy0HtMeal2vzD9V4k/BO79C+QeI=
+ //
+ // For forwards compatibility, extra text lines after the encoding are ignored.
+ if !bytes.HasPrefix(text, treePrefix) || bytes.Count(text, []byte("\n")) < 3 || len(text) > 1e6 {
+ return Tree{}, errMalformedTree
+ }
+
+ lines := strings.SplitN(string(text), "\n", 4)
+ n, err := strconv.ParseInt(lines[1], 10, 64)
+ if err != nil || n < 0 || lines[1] != strconv.FormatInt(n, 10) {
+ return Tree{}, errMalformedTree
+ }
+
+ h, err := base64.StdEncoding.DecodeString(lines[2])
+ if err != nil || len(h) != HashSize {
+ return Tree{}, errMalformedTree
+ }
+
+ var hash Hash
+ copy(hash[:], h)
+ return Tree{n, hash}, nil
+}
+
+var errMalformedRecord = errors.New("malformed record data")
+
+// FormatRecord formats a record for serving to a client
+// in a lookup response or data tile.
+//
+// The encoded form is the record ID as a single number,
+// then the text of the record, and then a terminating blank line.
+// Record text must be valid UTF-8 and must not contain any ASCII control
+// characters (those below U+0020) other than newline (U+000A).
+// It must end in a terminating newline and not contain any blank lines.
+func FormatRecord(id int64, text []byte) (msg []byte, err error) {
+ if !isValidRecordText(text) {
+ return nil, errMalformedRecord
+ }
+ msg = []byte(fmt.Sprintf("%d\n", id))
+ msg = append(msg, text...)
+ msg = append(msg, '\n')
+ return msg, nil
+}
+
+// isValidRecordText reports whether text is syntactically valid record text.
+func isValidRecordText(text []byte) bool {
+ var last rune
+ for i := 0; i < len(text); {
+ r, size := utf8.DecodeRune(text[i:])
+ if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 || last == '\n' && r == '\n' {
+ return false
+ }
+ i += size
+ last = r
+ }
+ if last != '\n' {
+ return false
+ }
+ return true
+}
+
+// ParseRecord parses a record description at the start of text,
+// stopping immediately after the terminating blank line.
+// It returns the record id, the record text, and the remainder of text.
+func ParseRecord(msg []byte) (id int64, text, rest []byte, err error) {
+ // Leading record id.
+ i := bytes.IndexByte(msg, '\n')
+ if i < 0 {
+ return 0, nil, nil, errMalformedRecord
+ }
+ id, err = strconv.ParseInt(string(msg[:i]), 10, 64)
+ if err != nil {
+ return 0, nil, nil, errMalformedRecord
+ }
+ msg = msg[i+1:]
+
+ // Record text.
+ i = bytes.Index(msg, []byte("\n\n"))
+ if i < 0 {
+ return 0, nil, nil, errMalformedRecord
+ }
+ text, rest = msg[:i+1], msg[i+2:]
+ if !isValidRecordText(text) {
+ return 0, nil, nil, errMalformedRecord
+ }
+ return id, text, rest, nil
+}
diff --git a/sumdb/internal/tlog/note_test.go b/sumdb/internal/tlog/note_test.go
new file mode 100644
index 0000000..a32d6d2
--- /dev/null
+++ b/sumdb/internal/tlog/note_test.go
@@ -0,0 +1,117 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tlog
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestFormatTree(t *testing.T) {
+ n := int64(123456789012)
+ h := RecordHash([]byte("hello world"))
+ golden := "go.sum database tree\n123456789012\nTszzRgjTG6xce+z2AG31kAXYKBgQVtCSCE40HmuwBb0=\n"
+ b := FormatTree(Tree{n, h})
+ if string(b) != golden {
+ t.Errorf("FormatTree(...) = %q, want %q", b, golden)
+ }
+}
+
+func TestParseTree(t *testing.T) {
+ in := "go.sum database tree\n123456789012\nTszzRgjTG6xce+z2AG31kAXYKBgQVtCSCE40HmuwBb0=\n"
+ goldH := RecordHash([]byte("hello world"))
+ goldN := int64(123456789012)
+ tree, err := ParseTree([]byte(in))
+ if tree.N != goldN || tree.Hash != goldH || err != nil {
+ t.Fatalf("ParseTree(...) = Tree{%d, %v}, %v, want Tree{%d, %v}, nil", tree.N, tree.Hash, err, goldN, goldH)
+ }
+
+ // Check invalid trees.
+ var badTrees = []string{
+ "not-" + in,
+ "go.sum database tree\n0xabcdef\nTszzRgjTG6xce+z2AG31kAXYKBgQVtCSCE40HmuwBb0=\n",
+ "go.sum database tree\n123456789012\nTszzRgjTG6xce+z2AG31kAXYKBgQVtCSCE40HmuwBTOOBIG=\n",
+ }
+ for _, bad := range badTrees {
+ _, err := ParseTree([]byte(bad))
+ if err == nil {
+ t.Fatalf("ParseTree(%q) succeeded, want failure", in)
+ }
+ }
+
+ // Check junk on end is ignored.
+ var goodTrees = []string{
+ in + "JOE",
+ in + "JOE\n",
+ in + strings.Repeat("JOE\n", 1000),
+ }
+ for _, good := range goodTrees {
+ _, err := ParseTree([]byte(good))
+ if tree.N != goldN || tree.Hash != goldH || err != nil {
+ t.Fatalf("ParseTree(...+%q) = Tree{%d, %v}, %v, want Tree{%d, %v}, nil", good[len(in):], tree.N, tree.Hash, err, goldN, goldH)
+ }
+ }
+}
+
+func TestFormatRecord(t *testing.T) {
+ id := int64(123456789012)
+ text := "hello, world\n"
+ golden := "123456789012\nhello, world\n\n"
+ msg, err := FormatRecord(id, []byte(text))
+ if err != nil {
+ t.Fatalf("FormatRecord: %v", err)
+ }
+ if string(msg) != golden {
+ t.Fatalf("FormatRecord(...) = %q, want %q", msg, golden)
+ }
+
+ var badTexts = []string{
+ "",
+ "hello\nworld",
+ "hello\n\nworld\n",
+ "hello\x01world\n",
+ }
+ for _, bad := range badTexts {
+ msg, err := FormatRecord(id, []byte(bad))
+ if err == nil {
+ t.Errorf("FormatRecord(id, %q) = %q, want error", bad, msg)
+ }
+ }
+}
+
+func TestParseRecord(t *testing.T) {
+ in := "123456789012\nhello, world\n\njunk on end\x01\xff"
+ goldID := int64(123456789012)
+ goldText := "hello, world\n"
+ goldRest := "junk on end\x01\xff"
+ id, text, rest, err := ParseRecord([]byte(in))
+ if id != goldID || string(text) != goldText || string(rest) != goldRest || err != nil {
+ t.Fatalf("ParseRecord(%q) = %d, %q, %q, %v, want %d, %q, %q, nil", in, id, text, rest, err, goldID, goldText, goldRest)
+ }
+
+ in = "123456789012\nhello, world\n\n"
+ id, text, rest, err = ParseRecord([]byte(in))
+ if id != goldID || string(text) != goldText || len(rest) != 0 || err != nil {
+ t.Fatalf("ParseRecord(%q) = %d, %q, %q, %v, want %d, %q, %q, nil", in, id, text, rest, err, goldID, goldText, "")
+ }
+ if rest == nil {
+ t.Fatalf("ParseRecord(%q): rest = []byte(nil), want []byte{}", in)
+ }
+
+ // Check invalid records.
+ var badRecords = []string{
+ "not-" + in,
+ "123\nhello\x01world\n\n",
+ "123\nhello\xffworld\n\n",
+ "123\nhello world\n",
+ "0x123\nhello world\n\n",
+ }
+ for _, bad := range badRecords {
+ id, text, rest, err := ParseRecord([]byte(bad))
+ if err == nil {
+ t.Fatalf("ParseRecord(%q) = %d, %q, %q, nil, want error", in, id, text, rest)
+ }
+ }
+}
diff --git a/sumdb/internal/tlog/tile.go b/sumdb/internal/tlog/tile.go
new file mode 100644
index 0000000..694d89c
--- /dev/null
+++ b/sumdb/internal/tlog/tile.go
@@ -0,0 +1,418 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tlog
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// A Tile is a description of a transparency log tile.
+// A tile of height H at level L offset N lists W consecutive hashes
+// at level H*L of the tree starting at offset N*(2**H).
+// A complete tile lists 2**H hashes; a partial tile lists fewer.
+// Note that a tile represents the entire subtree of height H
+// with those hashes as the leaves. The levels above H*L
+// can be reconstructed by hashing the leaves.
+//
+// Each Tile can be encoded as a β€œtile coordinate path”
+// of the form tile/H/L/NNN[.p/W].
+// The .p/W suffix is present only for partial tiles, meaning W < 2**H.
+// The NNN element is an encoding of N into 3-digit path elements.
+// All but the last path element begins with an "x".
+// For example,
+// Tile{H: 3, L: 4, N: 1234067, W: 1}'s path
+// is tile/3/4/x001/x234/067.p/1, and
+// Tile{H: 3, L: 4, N: 1234067, W: 8}'s path
+// is tile/3/4/x001/x234/067.
+// See Tile's Path method and the ParseTilePath function.
+//
+// The special level L=-1 holds raw record data instead of hashes.
+// In this case, the level encodes into a tile path as the path element
+// "data" instead of "-1".
+type Tile struct {
+ H int // height of tile (1 ≀ H ≀ 30)
+ L int // level in tiling (-1 ≀ L ≀ 63)
+ N int64 // number within level (0 ≀ N, unbounded)
+ W int // width of tile (1 ≀ W ≀ 2**H; 2**H is complete tile)
+}
+
+// TileForIndex returns the tile of height h β‰₯ 1
+// and least width storing the given hash storage index.
+func TileForIndex(h int, index int64) Tile {
+ if h < 1 {
+ panic("TileForIndex: invalid height")
+ }
+ t, _, _ := tileForIndex(h, index)
+ return t
+}
+
+// tileForIndex returns the tile of height h β‰₯ 1
+// storing the given hash index, which can be
+// reconstructed using tileHash(data[start:end]).
+func tileForIndex(h int, index int64) (t Tile, start, end int) {
+ level, n := SplitStoredHashIndex(index)
+ t.H = h
+ t.L = level / h
+ level -= t.L * h // now level within tile
+ t.N = n << uint(level) >> uint(t.H)
+ n -= t.N << uint(t.H) >> uint(level) // now n within tile at level
+ t.W = int((n + 1) << uint(level))
+ return t, int(n<<uint(level)) * HashSize, int((n+1)<<uint(level)) * HashSize
+}
+
+// HashFromTile returns the hash at the given storage index,
+// provided that t == TileForIndex(t.H, index) or a wider version,
+// and data is t's tile data (of length at least t.W*HashSize).
+func HashFromTile(t Tile, data []byte, index int64) (Hash, error) {
+ if t.H < 1 || t.H > 30 || t.L < 0 || t.L >= 64 || t.W < 1 || t.W > 1<<uint(t.H) {
+ return Hash{}, fmt.Errorf("invalid tile %v", t.Path())
+ }
+ if len(data) < t.W*HashSize {
+ return Hash{}, fmt.Errorf("data len %d too short for tile %v", len(data), t.Path())
+ }
+ t1, start, end := tileForIndex(t.H, index)
+ if t.L != t1.L || t.N != t1.N || t.W < t1.W {
+ return Hash{}, fmt.Errorf("index %v is in %v not %v", index, t1.Path(), t.Path())
+ }
+ return tileHash(data[start:end]), nil
+}
+
+// tileHash computes the subtree hash corresponding to the (2^K)-1 hashes in data.
+func tileHash(data []byte) Hash {
+ if len(data) == 0 {
+ panic("bad math in tileHash")
+ }
+ if len(data) == HashSize {
+ var h Hash
+ copy(h[:], data)
+ return h
+ }
+ n := len(data) / 2
+ return NodeHash(tileHash(data[:n]), tileHash(data[n:]))
+}
+
+// NewTiles returns the coordinates of the tiles of height h β‰₯ 1
+// that must be published when publishing from a tree of
+// size newTreeSize to replace a tree of size oldTreeSize.
+// (No tiles need to be published for a tree of size zero.)
+func NewTiles(h int, oldTreeSize, newTreeSize int64) []Tile {
+ if h < 1 {
+ panic(fmt.Sprintf("NewTiles: invalid height %d", h))
+ }
+ H := uint(h)
+ var tiles []Tile
+ for level := uint(0); newTreeSize>>(H*level) > 0; level++ {
+ oldN := oldTreeSize >> (H * level)
+ newN := newTreeSize >> (H * level)
+ for n := oldN >> H; n < newN>>H; n++ {
+ tiles = append(tiles, Tile{H: h, L: int(level), N: n, W: 1 << H})
+ }
+ n := newN >> H
+ maxW := int(newN - n<<H)
+ minW := 1
+ if oldN > n<<H {
+ minW = int(oldN - n<<H)
+ }
+ for w := minW; w <= maxW; w++ {
+ tiles = append(tiles, Tile{H: h, L: int(level), N: n, W: w})
+ }
+ }
+ return tiles
+}
+
+// ReadTileData reads the hashes for tile t from r
+// and returns the corresponding tile data.
+func ReadTileData(t Tile, r HashReader) ([]byte, error) {
+ size := t.W
+ if size == 0 {
+ size = 1 << uint(t.H)
+ }
+ start := t.N << uint(t.H)
+ indexes := make([]int64, size)
+ for i := 0; i < size; i++ {
+ indexes[i] = StoredHashIndex(t.H*t.L, start+int64(i))
+ }
+
+ hashes, err := r.ReadHashes(indexes)
+ if err != nil {
+ return nil, err
+ }
+ if len(hashes) != len(indexes) {
+ return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
+ }
+
+ tile := make([]byte, size*HashSize)
+ for i := 0; i < size; i++ {
+ copy(tile[i*HashSize:], hashes[i][:])
+ }
+ return tile, nil
+}
+
+// To limit the size of any particular directory listing,
+// we encode the (possibly very large) number N
+// by encoding three digits at a time.
+// For example, 123456789 encodes as x123/x456/789.
+// Each directory has at most 1000 each xNNN, NNN, and NNN.p children,
+// so there are at most 3000 entries in any one directory.
+const pathBase = 1000
+
+// Path returns a tile coordinate path describing t.
+func (t Tile) Path() string {
+ n := t.N
+ nStr := fmt.Sprintf("%03d", n%pathBase)
+ for n >= pathBase {
+ n /= pathBase
+ nStr = fmt.Sprintf("x%03d/%s", n%pathBase, nStr)
+ }
+ pStr := ""
+ if t.W != 1<<uint(t.H) {
+ pStr = fmt.Sprintf(".p/%d", t.W)
+ }
+ var L string
+ if t.L == -1 {
+ L = "data"
+ } else {
+ L = fmt.Sprintf("%d", t.L)
+ }
+ return fmt.Sprintf("tile/%d/%s/%s%s", t.H, L, nStr, pStr)
+}
+
+// ParseTilePath parses a tile coordinate path.
+func ParseTilePath(path string) (Tile, error) {
+ f := strings.Split(path, "/")
+ if len(f) < 4 || f[0] != "tile" {
+ return Tile{}, &badPathError{path}
+ }
+ h, err1 := strconv.Atoi(f[1])
+ isData := false
+ if f[2] == "data" {
+ isData = true
+ f[2] = "0"
+ }
+ l, err2 := strconv.Atoi(f[2])
+ if err1 != nil || err2 != nil || h < 1 || l < 0 || h > 30 {
+ return Tile{}, &badPathError{path}
+ }
+ w := 1 << uint(h)
+ if dotP := f[len(f)-2]; strings.HasSuffix(dotP, ".p") {
+ ww, err := strconv.Atoi(f[len(f)-1])
+ if err != nil || ww <= 0 || ww >= w {
+ return Tile{}, &badPathError{path}
+ }
+ w = ww
+ f[len(f)-2] = dotP[:len(dotP)-len(".p")]
+ f = f[:len(f)-1]
+ }
+ f = f[3:]
+ n := int64(0)
+ for _, s := range f {
+ nn, err := strconv.Atoi(strings.TrimPrefix(s, "x"))
+ if err != nil || nn < 0 || nn >= pathBase {
+ return Tile{}, &badPathError{path}
+ }
+ n = n*pathBase + int64(nn)
+ }
+ if isData {
+ l = -1
+ }
+ t := Tile{H: h, L: l, N: n, W: w}
+ if path != t.Path() {
+ return Tile{}, &badPathError{path}
+ }
+ return t, nil
+}
+
+type badPathError struct {
+ path string
+}
+
+func (e *badPathError) Error() string {
+ return fmt.Sprintf("malformed tile path %q", e.path)
+}
+
+// A TileReader reads tiles from a go.sum database log.
+type TileReader interface {
+ // Height returns the height of the available tiles.
+ Height() int
+
+ // ReadTiles returns the data for each requested tile.
+ // If ReadTiles returns err == nil, it must also return
+ // a data record for each tile (len(data) == len(tiles))
+ // and each data record must be the correct length
+ // (len(data[i]) == tiles[i].W*HashSize).
+ ReadTiles(tiles []Tile) (data [][]byte, err error)
+
+ // SaveTiles informs the TileReader that the tile data
+ // returned by ReadTiles has been confirmed as valid
+ // and can be saved in persistent storage (on disk).
+ SaveTiles(tiles []Tile, data [][]byte)
+}
+
+// TileHashReader returns a HashReader that satisfies requests
+// by loading tiles of the given tree.
+//
+// The returned HashReader checks that loaded tiles are
+// valid for the given tree. Therefore, any hashes returned
+// by the HashReader are already proven to be in the tree.
+func TileHashReader(tree Tree, tr TileReader) HashReader {
+ return &tileHashReader{tree: tree, tr: tr}
+}
+
+type tileHashReader struct {
+ tree Tree
+ tr TileReader
+}
+
+// tileParent returns t's k'th tile parent in the tiles for a tree of size n.
+// If there is no such parent, tileParent returns Tile{}.
+func tileParent(t Tile, k int, n int64) Tile {
+ t.L += k
+ t.N >>= uint(k * t.H)
+ t.W = 1 << uint(t.H)
+ if max := n >> uint(t.L*t.H); t.N<<uint(t.H)+int64(t.W) >= max {
+ if t.N<<uint(t.H) >= max {
+ return Tile{}
+ }
+ t.W = int(max - t.N<<uint(t.H))
+ }
+ return t
+}
+
+func (r *tileHashReader) ReadHashes(indexes []int64) ([]Hash, error) {
+ h := r.tr.Height()
+
+ tileOrder := make(map[Tile]int) // tileOrder[tileKey(tiles[i])] = i
+ var tiles []Tile
+
+ // Plan to fetch tiles necessary to recompute tree hash.
+ // If it matches, those tiles are authenticated.
+ stx := subTreeIndex(0, r.tree.N, nil)
+ stxTileOrder := make([]int, len(stx))
+ for i, x := range stx {
+ tile, _, _ := tileForIndex(h, x)
+ tile = tileParent(tile, 0, r.tree.N)
+ if j, ok := tileOrder[tile]; ok {
+ stxTileOrder[i] = j
+ continue
+ }
+ stxTileOrder[i] = len(tiles)
+ tileOrder[tile] = len(tiles)
+ tiles = append(tiles, tile)
+ }
+
+ // Plan to fetch tiles containing the indexes,
+ // along with any parent tiles needed
+ // for authentication. For most calls,
+ // the parents are being fetched anyway.
+ indexTileOrder := make([]int, len(indexes))
+ for i, x := range indexes {
+ if x >= StoredHashIndex(0, r.tree.N) {
+ return nil, fmt.Errorf("indexes not in tree")
+ }
+
+ tile, _, _ := tileForIndex(h, x)
+
+ // Walk up parent tiles until we find one we've requested.
+ // That one will be authenticated.
+ k := 0
+ for ; ; k++ {
+ p := tileParent(tile, k, r.tree.N)
+ if j, ok := tileOrder[p]; ok {
+ if k == 0 {
+ indexTileOrder[i] = j
+ }
+ break
+ }
+ }
+
+ // Walk back down recording child tiles after parents.
+ // This loop ends by revisiting the tile for this index
+ // (tileParent(tile, 0, r.tree.N)) unless k == 0, in which
+ // case the previous loop did it.
+ for k--; k >= 0; k-- {
+ p := tileParent(tile, k, r.tree.N)
+ if p.W != 1<<uint(p.H) {
+ // Only full tiles have parents.
+ // This tile has a parent, so it must be full.
+ return nil, fmt.Errorf("bad math in tileHashReader: %d %d %v", r.tree.N, x, p)
+ }
+ tileOrder[p] = len(tiles)
+ if k == 0 {
+ indexTileOrder[i] = len(tiles)
+ }
+ tiles = append(tiles, p)
+ }
+ }
+
+ // Fetch all the tile data.
+ data, err := r.tr.ReadTiles(tiles)
+ if err != nil {
+ return nil, err
+ }
+ if len(data) != len(tiles) {
+ return nil, fmt.Errorf("TileReader returned bad result slice (len=%d, want %d)", len(data), len(tiles))
+ }
+ for i, tile := range tiles {
+ if len(data[i]) != tile.W*HashSize {
+ return nil, fmt.Errorf("TileReader returned bad result slice (%v len=%d, want %d)", tile.Path(), len(data[i]), tile.W*HashSize)
+ }
+ }
+
+ // Authenticate the initial tiles against the tree hash.
+ // They are arranged so that parents are authenticated before children.
+ // First the tiles needed for the tree hash.
+ th, err := HashFromTile(tiles[stxTileOrder[len(stx)-1]], data[stxTileOrder[len(stx)-1]], stx[len(stx)-1])
+ if err != nil {
+ return nil, err
+ }
+ for i := len(stx) - 2; i >= 0; i-- {
+ h, err := HashFromTile(tiles[stxTileOrder[i]], data[stxTileOrder[i]], stx[i])
+ if err != nil {
+ return nil, err
+ }
+ th = NodeHash(h, th)
+ }
+ if th != r.tree.Hash {
+ // The tiles do not support the tree hash.
+ // We know at least one is wrong, but not which one.
+ return nil, fmt.Errorf("downloaded inconsistent tile")
+ }
+
+ // Authenticate full tiles against their parents.
+ for i := len(stx); i < len(tiles); i++ {
+ tile := tiles[i]
+ p := tileParent(tile, 1, r.tree.N)
+ j, ok := tileOrder[p]
+ if !ok {
+ return nil, fmt.Errorf("bad math in tileHashReader %d %v: lost parent of %v", r.tree.N, indexes, tile)
+ }
+ h, err := HashFromTile(p, data[j], StoredHashIndex(p.L*p.H, tile.N))
+ if err != nil {
+ return nil, fmt.Errorf("bad math in tileHashReader %d %v: lost hash of %v: %v", r.tree.N, indexes, tile, err)
+ }
+ if h != tileHash(data[i]) {
+ return nil, fmt.Errorf("downloaded inconsistent tile")
+ }
+ }
+
+ // Now we have all the tiles needed for the requested hashes,
+ // and we've authenticated the full tile set against the trusted tree hash.
+ r.tr.SaveTiles(tiles, data)
+
+ // Pull out the requested hashes.
+ hashes := make([]Hash, len(indexes))
+ for i, x := range indexes {
+ j := indexTileOrder[i]
+ h, err := HashFromTile(tiles[j], data[j], x)
+ if err != nil {
+ return nil, fmt.Errorf("bad math in tileHashReader %d %v: lost hash %v: %v", r.tree.N, indexes, x, err)
+ }
+ hashes[i] = h
+ }
+
+ return hashes, nil
+}
diff --git a/sumdb/internal/tlog/tlog.go b/sumdb/internal/tlog/tlog.go
new file mode 100644
index 0000000..732316f
--- /dev/null
+++ b/sumdb/internal/tlog/tlog.go
@@ -0,0 +1,600 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tlog implements a tamper-evident log
+// used in the Go module go.sum database server.
+//
+// This package is part of a DRAFT of what the go.sum database server will look like.
+// Do not assume the details here are final!
+//
+// This package follows the design of Certificate Transparency (RFC 6962)
+// and its proofs are compatible with that system.
+// See TestCertificateTransparency.
+package tlog
+
+import (
+ "crypto/sha256"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "math/bits"
+)
+
+// A Hash is a hash identifying a log record or tree root.
+type Hash [HashSize]byte
+
+// HashSize is the size of a Hash in bytes.
+const HashSize = 32
+
+// String returns a base64 representation of the hash for printing.
+func (h Hash) String() string {
+ return base64.StdEncoding.EncodeToString(h[:])
+}
+
+// MarshalJSON marshals the hash as a JSON string containing the base64-encoded hash.
+func (h Hash) MarshalJSON() ([]byte, error) {
+ return []byte(`"` + h.String() + `"`), nil
+}
+
+// UnmarshalJSON unmarshals a hash from JSON string containing the a base64-encoded hash.
+func (h *Hash) UnmarshalJSON(data []byte) error {
+ if len(data) != 1+44+1 || data[0] != '"' || data[len(data)-2] != '=' || data[len(data)-1] != '"' {
+ return errors.New("cannot decode hash")
+ }
+
+ // As of Go 1.12, base64.StdEncoding.Decode insists on
+ // slicing into target[33:] even when it only writes 32 bytes.
+ // Since we already checked that the hash ends in = above,
+ // we can use base64.RawStdEncoding with the = removed;
+ // RawStdEncoding does not exhibit the same bug.
+ // We decode into a temporary to avoid writing anything to *h
+ // unless the entire input is well-formed.
+ var tmp Hash
+ n, err := base64.RawStdEncoding.Decode(tmp[:], data[1:len(data)-2])
+ if err != nil || n != HashSize {
+ return errors.New("cannot decode hash")
+ }
+ *h = tmp
+ return nil
+}
+
+// ParseHash parses the base64-encoded string form of a hash.
+func ParseHash(s string) (Hash, error) {
+ data, err := base64.StdEncoding.DecodeString(s)
+ if err != nil || len(data) != HashSize {
+ return Hash{}, fmt.Errorf("malformed hash")
+ }
+ var h Hash
+ copy(h[:], data)
+ return h, nil
+}
+
+// maxpow2 returns k, the maximum power of 2 smaller than n,
+// as well as l = logβ‚‚ k (so k = 1<<l).
+func maxpow2(n int64) (k int64, l int) {
+ l = 0
+ for 1<<uint(l+1) < n {
+ l++
+ }
+ return 1 << uint(l), l
+}
+
+var zeroPrefix = []byte{0x00}
+
+// RecordHash returns the content hash for the given record data.
+func RecordHash(data []byte) Hash {
+ // SHA256(0x00 || data)
+ // https://tools.ietf.org/html/rfc6962#section-2.1
+ h := sha256.New()
+ h.Write(zeroPrefix)
+ h.Write(data)
+ var h1 Hash
+ h.Sum(h1[:0])
+ return h1
+}
+
+// NodeHash returns the hash for an interior tree node with the given left and right hashes.
+func NodeHash(left, right Hash) Hash {
+ // SHA256(0x01 || left || right)
+ // https://tools.ietf.org/html/rfc6962#section-2.1
+ // We use a stack buffer to assemble the hash input
+ // to avoid allocating a hash struct with sha256.New.
+ var buf [1 + HashSize + HashSize]byte
+ buf[0] = 0x01
+ copy(buf[1:], left[:])
+ copy(buf[1+HashSize:], right[:])
+ return sha256.Sum256(buf[:])
+}
+
+// For information about the stored hash index ordering,
+// see section 3.3 of Crosby and Wallach's paper
+// "Efficient Data Structures for Tamper-Evident Logging".
+// https://www.usenix.org/legacy/event/sec09/tech/full_papers/crosby.pdf
+
+// StoredHashIndex maps the tree coordinates (level, n)
+// to a dense linear ordering that can be used for hash storage.
+// Hash storage implementations that store hashes in sequential
+// storage can use this function to compute where to read or write
+// a given hash.
+func StoredHashIndex(level int, n int64) int64 {
+ // Level L's n'th hash is written right after level L+1's 2n+1'th hash.
+ // Work our way down to the level 0 ordering.
+ // We'll add back the orignal level count at the end.
+ for l := level; l > 0; l-- {
+ n = 2*n + 1
+ }
+
+ // Level 0's n'th hash is written at n+n/2+n/4+... (eventually n/2ⁱ hits zero).
+ i := int64(0)
+ for ; n > 0; n >>= 1 {
+ i += n
+ }
+
+ return i + int64(level)
+}
+
+// SplitStoredHashIndex is the inverse of StoredHashIndex.
+// That is, SplitStoredHashIndex(StoredHashIndex(level, n)) == level, n.
+func SplitStoredHashIndex(index int64) (level int, n int64) {
+ // Determine level 0 record before index.
+ // StoredHashIndex(0, n) < 2*n,
+ // so the n we want is in [index/2, index/2+logβ‚‚(index)].
+ n = index / 2
+ indexN := StoredHashIndex(0, n)
+ if indexN > index {
+ panic("bad math")
+ }
+ for {
+ // Each new record n adds 1 + trailingZeros(n) hashes.
+ x := indexN + 1 + int64(bits.TrailingZeros64(uint64(n+1)))
+ if x > index {
+ break
+ }
+ n++
+ indexN = x
+ }
+ // The hash we want was committed with record n,
+ // meaning it is one of (0, n), (1, n/2), (2, n/4), ...
+ level = int(index - indexN)
+ return level, n >> uint(level)
+}
+
+// StoredHashCount returns the number of stored hashes
+// that are expected for a tree with n records.
+func StoredHashCount(n int64) int64 {
+ if n == 0 {
+ return 0
+ }
+ // The tree will have the hashes up to the last leaf hash.
+ numHash := StoredHashIndex(0, n-1) + 1
+ // And it will have any hashes for subtrees completed by that leaf.
+ for i := uint64(n - 1); i&1 != 0; i >>= 1 {
+ numHash++
+ }
+ return numHash
+}
+
+// StoredHashes returns the hashes that must be stored when writing
+// record n with the given data. The hashes should be stored starting
+// at StoredHashIndex(0, n). The result will have at most 1 + logβ‚‚ n hashes,
+// but it will average just under two per call for a sequence of calls for n=1..k.
+//
+// StoredHashes may read up to log n earlier hashes from r
+// in order to compute hashes for completed subtrees.
+func StoredHashes(n int64, data []byte, r HashReader) ([]Hash, error) {
+ return StoredHashesForRecordHash(n, RecordHash(data), r)
+}
+
+// StoredHashesForRecordHash is like StoredHashes but takes
+// as its second argument RecordHash(data) instead of data itself.
+func StoredHashesForRecordHash(n int64, h Hash, r HashReader) ([]Hash, error) {
+ // Start with the record hash.
+ hashes := []Hash{h}
+
+ // Build list of indexes needed for hashes for completed subtrees.
+ // Each trailing 1 bit in the binary representation of n completes a subtree
+ // and consumes a hash from an adjacent subtree.
+ m := int(bits.TrailingZeros64(uint64(n + 1)))
+ indexes := make([]int64, m)
+ for i := 0; i < m; i++ {
+ // We arrange indexes in sorted order.
+ // Note that n>>i is always odd.
+ indexes[m-1-i] = StoredHashIndex(i, n>>uint(i)-1)
+ }
+
+ // Fetch hashes.
+ old, err := r.ReadHashes(indexes)
+ if err != nil {
+ return nil, err
+ }
+ if len(old) != len(indexes) {
+ return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(old))
+ }
+
+ // Build new hashes.
+ for i := 0; i < m; i++ {
+ h = NodeHash(old[m-1-i], h)
+ hashes = append(hashes, h)
+ }
+ return hashes, nil
+}
+
+// A HashReader can read hashes for nodes in the log's tree structure.
+type HashReader interface {
+ // ReadHashes returns the hashes with the given stored hash indexes
+ // (see StoredHashIndex and SplitStoredHashIndex).
+ // ReadHashes must return a slice of hashes the same length as indexes,
+ // or else it must return a non-nil error.
+ // ReadHashes may run faster if indexes is sorted in increasing order.
+ ReadHashes(indexes []int64) ([]Hash, error)
+}
+
+// A HashReaderFunc is a function implementing HashReader.
+type HashReaderFunc func([]int64) ([]Hash, error)
+
+func (f HashReaderFunc) ReadHashes(indexes []int64) ([]Hash, error) {
+ return f(indexes)
+}
+
+// TreeHash computes the hash for the root of the tree with n records,
+// using the HashReader to obtain previously stored hashes
+// (those returned by StoredHashes during the writes of those n records).
+// TreeHash makes a single call to ReadHash requesting at most 1 + logβ‚‚ n hashes.
+// The tree of size zero is defined to have an all-zero Hash.
+func TreeHash(n int64, r HashReader) (Hash, error) {
+ if n == 0 {
+ return Hash{}, nil
+ }
+ indexes := subTreeIndex(0, n, nil)
+ hashes, err := r.ReadHashes(indexes)
+ if err != nil {
+ return Hash{}, err
+ }
+ if len(hashes) != len(indexes) {
+ return Hash{}, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
+ }
+ hash, hashes := subTreeHash(0, n, hashes)
+ if len(hashes) != 0 {
+ panic("tlog: bad index math in TreeHash")
+ }
+ return hash, nil
+}
+
+// subTreeIndex returns the storage indexes needed to compute
+// the hash for the subtree containing records [lo, hi),
+// appending them to need and returning the result.
+// See https://tools.ietf.org/html/rfc6962#section-2.1
+func subTreeIndex(lo, hi int64, need []int64) []int64 {
+ // See subTreeHash below for commentary.
+ for lo < hi {
+ k, level := maxpow2(hi - lo + 1)
+ if lo&(k-1) != 0 {
+ panic("tlog: bad math in subTreeIndex")
+ }
+ need = append(need, StoredHashIndex(level, lo>>uint(level)))
+ lo += k
+ }
+ return need
+}
+
+// subTreeHash computes the hash for the subtree containing records [lo, hi),
+// assuming that hashes are the hashes corresponding to the indexes
+// returned by subTreeIndex(lo, hi).
+// It returns any leftover hashes.
+func subTreeHash(lo, hi int64, hashes []Hash) (Hash, []Hash) {
+ // Repeatedly partition the tree into a left side with 2^level nodes,
+ // for as large a level as possible, and a right side with the fringe.
+ // The left hash is stored directly and can be read from storage.
+ // The right side needs further computation.
+ numTree := 0
+ for lo < hi {
+ k, _ := maxpow2(hi - lo + 1)
+ if lo&(k-1) != 0 || lo >= hi {
+ panic("tlog: bad math in subTreeHash")
+ }
+ numTree++
+ lo += k
+ }
+
+ if len(hashes) < numTree {
+ panic("tlog: bad index math in subTreeHash")
+ }
+
+ // Reconstruct hash.
+ h := hashes[numTree-1]
+ for i := numTree - 2; i >= 0; i-- {
+ h = NodeHash(hashes[i], h)
+ }
+ return h, hashes[numTree:]
+}
+
+// A RecordProof is a verifiable proof that a particular log root contains a particular record.
+// RFC 6962 calls this a β€œMerkle audit path.”
+type RecordProof []Hash
+
+// ProveRecord returns the proof that the tree of size t contains the record with index n.
+func ProveRecord(t, n int64, r HashReader) (RecordProof, error) {
+ if t < 0 || n < 0 || n >= t {
+ return nil, fmt.Errorf("tlog: invalid inputs in ProveRecord")
+ }
+ indexes := leafProofIndex(0, t, n, nil)
+ if len(indexes) == 0 {
+ return RecordProof{}, nil
+ }
+ hashes, err := r.ReadHashes(indexes)
+ if err != nil {
+ return nil, err
+ }
+ if len(hashes) != len(indexes) {
+ return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
+ }
+
+ p, hashes := leafProof(0, t, n, hashes)
+ if len(hashes) != 0 {
+ panic("tlog: bad index math in ProveRecord")
+ }
+ return p, nil
+}
+
+// leafProofIndex builds the list of indexes needed to construct the proof
+// that leaf n is contained in the subtree with leaves [lo, hi).
+// It appends those indexes to need and returns the result.
+// See https://tools.ietf.org/html/rfc6962#section-2.1.1
+func leafProofIndex(lo, hi, n int64, need []int64) []int64 {
+ // See leafProof below for commentary.
+ if !(lo <= n && n < hi) {
+ panic("tlog: bad math in leafProofIndex")
+ }
+ if lo+1 == hi {
+ return need
+ }
+ if k, _ := maxpow2(hi - lo); n < lo+k {
+ need = leafProofIndex(lo, lo+k, n, need)
+ need = subTreeIndex(lo+k, hi, need)
+ } else {
+ need = subTreeIndex(lo, lo+k, need)
+ need = leafProofIndex(lo+k, hi, n, need)
+ }
+ return need
+}
+
+// leafProof constructs the proof that leaf n is contained in the subtree with leaves [lo, hi).
+// It returns any leftover hashes as well.
+// See https://tools.ietf.org/html/rfc6962#section-2.1.1
+func leafProof(lo, hi, n int64, hashes []Hash) (RecordProof, []Hash) {
+ // We must have lo <= n < hi or else the code here has a bug.
+ if !(lo <= n && n < hi) {
+ panic("tlog: bad math in leafProof")
+ }
+
+ if lo+1 == hi { // n == lo
+ // Reached the leaf node.
+ // The verifier knows what the leaf hash is, so we don't need to send it.
+ return RecordProof{}, hashes
+ }
+
+ // Walk down the tree toward n.
+ // Record the hash of the path not taken (needed for verifying the proof).
+ var p RecordProof
+ var th Hash
+ if k, _ := maxpow2(hi - lo); n < lo+k {
+ // n is on left side
+ p, hashes = leafProof(lo, lo+k, n, hashes)
+ th, hashes = subTreeHash(lo+k, hi, hashes)
+ } else {
+ // n is on right side
+ th, hashes = subTreeHash(lo, lo+k, hashes)
+ p, hashes = leafProof(lo+k, hi, n, hashes)
+ }
+ return append(p, th), hashes
+}
+
+var errProofFailed = errors.New("invalid transparency proof")
+
+// CheckRecord verifies that p is a valid proof that the tree of size t
+// with hash th has an n'th record with hash h.
+func CheckRecord(p RecordProof, t int64, th Hash, n int64, h Hash) error {
+ if t < 0 || n < 0 || n >= t {
+ return fmt.Errorf("tlog: invalid inputs in CheckRecord")
+ }
+ th2, err := runRecordProof(p, 0, t, n, h)
+ if err != nil {
+ return err
+ }
+ if th2 == th {
+ return nil
+ }
+ return errProofFailed
+}
+
+// runRecordProof runs the proof p that leaf n is contained in the subtree with leaves [lo, hi).
+// Running the proof means constructing and returning the implied hash of that
+// subtree.
+func runRecordProof(p RecordProof, lo, hi, n int64, leafHash Hash) (Hash, error) {
+ // We must have lo <= n < hi or else the code here has a bug.
+ if !(lo <= n && n < hi) {
+ panic("tlog: bad math in runRecordProof")
+ }
+
+ if lo+1 == hi { // m == lo
+ // Reached the leaf node.
+ // The proof must not have any unnecessary hashes.
+ if len(p) != 0 {
+ return Hash{}, errProofFailed
+ }
+ return leafHash, nil
+ }
+
+ if len(p) == 0 {
+ return Hash{}, errProofFailed
+ }
+
+ k, _ := maxpow2(hi - lo)
+ if n < lo+k {
+ th, err := runRecordProof(p[:len(p)-1], lo, lo+k, n, leafHash)
+ if err != nil {
+ return Hash{}, err
+ }
+ return NodeHash(th, p[len(p)-1]), nil
+ } else {
+ th, err := runRecordProof(p[:len(p)-1], lo+k, hi, n, leafHash)
+ if err != nil {
+ return Hash{}, err
+ }
+ return NodeHash(p[len(p)-1], th), nil
+ }
+}
+
+// A TreeProof is a verifiable proof that a particular log tree contains
+// as a prefix all records present in an earlier tree.
+// RFC 6962 calls this a β€œMerkle consistency proof.”
+type TreeProof []Hash
+
+// ProveTree returns the proof that the tree of size t contains
+// as a prefix all the records from the tree of smaller size n.
+func ProveTree(t, n int64, h HashReader) (TreeProof, error) {
+ if t < 1 || n < 1 || n > t {
+ return nil, fmt.Errorf("tlog: invalid inputs in ProveTree")
+ }
+ indexes := treeProofIndex(0, t, n, nil)
+ if len(indexes) == 0 {
+ return TreeProof{}, nil
+ }
+ hashes, err := h.ReadHashes(indexes)
+ if err != nil {
+ return nil, err
+ }
+ if len(hashes) != len(indexes) {
+ return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes))
+ }
+
+ p, hashes := treeProof(0, t, n, hashes)
+ if len(hashes) != 0 {
+ panic("tlog: bad index math in ProveTree")
+ }
+ return p, nil
+}
+
+// treeProofIndex builds the list of indexes needed to construct
+// the sub-proof related to the subtree containing records [lo, hi).
+// See https://tools.ietf.org/html/rfc6962#section-2.1.2.
+func treeProofIndex(lo, hi, n int64, need []int64) []int64 {
+ // See treeProof below for commentary.
+ if !(lo < n && n <= hi) {
+ panic("tlog: bad math in treeProofIndex")
+ }
+
+ if n == hi {
+ if lo == 0 {
+ return need
+ }
+ return subTreeIndex(lo, hi, need)
+ }
+
+ if k, _ := maxpow2(hi - lo); n <= lo+k {
+ need = treeProofIndex(lo, lo+k, n, need)
+ need = subTreeIndex(lo+k, hi, need)
+ } else {
+ need = subTreeIndex(lo, lo+k, need)
+ need = treeProofIndex(lo+k, hi, n, need)
+ }
+ return need
+}
+
+// treeProof constructs the sub-proof related to the subtree containing records [lo, hi).
+// It returns any leftover hashes as well.
+// See https://tools.ietf.org/html/rfc6962#section-2.1.2.
+func treeProof(lo, hi, n int64, hashes []Hash) (TreeProof, []Hash) {
+ // We must have lo < n <= hi or else the code here has a bug.
+ if !(lo < n && n <= hi) {
+ panic("tlog: bad math in treeProof")
+ }
+
+ // Reached common ground.
+ if n == hi {
+ if lo == 0 {
+ // This subtree corresponds exactly to the old tree.
+ // The verifier knows that hash, so we don't need to send it.
+ return TreeProof{}, hashes
+ }
+ th, hashes := subTreeHash(lo, hi, hashes)
+ return TreeProof{th}, hashes
+ }
+
+ // Interior node for the proof.
+ // Decide whether to walk down the left or right side.
+ var p TreeProof
+ var th Hash
+ if k, _ := maxpow2(hi - lo); n <= lo+k {
+ // m is on left side
+ p, hashes = treeProof(lo, lo+k, n, hashes)
+ th, hashes = subTreeHash(lo+k, hi, hashes)
+ } else {
+ // m is on right side
+ th, hashes = subTreeHash(lo, lo+k, hashes)
+ p, hashes = treeProof(lo+k, hi, n, hashes)
+ }
+ return append(p, th), hashes
+}
+
+// CheckTree verifies that p is a valid proof that the tree of size t with hash th
+// contains as a prefix the tree of size n with hash h.
+func CheckTree(p TreeProof, t int64, th Hash, n int64, h Hash) error {
+ if t < 1 || n < 1 || n > t {
+ return fmt.Errorf("tlog: invalid inputs in CheckTree")
+ }
+ h2, th2, err := runTreeProof(p, 0, t, n, h)
+ if err != nil {
+ return err
+ }
+ if th2 == th && h2 == h {
+ return nil
+ }
+ return errProofFailed
+}
+
+// runTreeProof runs the sub-proof p related to the subtree containing records [lo, hi),
+// where old is the hash of the old tree with n records.
+// Running the proof means constructing and returning the implied hashes of that
+// subtree in both the old and new tree.
+func runTreeProof(p TreeProof, lo, hi, n int64, old Hash) (Hash, Hash, error) {
+ // We must have lo < n <= hi or else the code here has a bug.
+ if !(lo < n && n <= hi) {
+ panic("tlog: bad math in runTreeProof")
+ }
+
+ // Reached common ground.
+ if n == hi {
+ if lo == 0 {
+ if len(p) != 0 {
+ return Hash{}, Hash{}, errProofFailed
+ }
+ return old, old, nil
+ }
+ if len(p) != 1 {
+ return Hash{}, Hash{}, errProofFailed
+ }
+ return p[0], p[0], nil
+ }
+
+ if len(p) == 0 {
+ return Hash{}, Hash{}, errProofFailed
+ }
+
+ // Interior node for the proof.
+ k, _ := maxpow2(hi - lo)
+ if n <= lo+k {
+ oh, th, err := runTreeProof(p[:len(p)-1], lo, lo+k, n, old)
+ if err != nil {
+ return Hash{}, Hash{}, err
+ }
+ return oh, NodeHash(th, p[len(p)-1]), nil
+ } else {
+ oh, th, err := runTreeProof(p[:len(p)-1], lo+k, hi, n, old)
+ if err != nil {
+ return Hash{}, Hash{}, err
+ }
+ return NodeHash(p[len(p)-1], oh), NodeHash(p[len(p)-1], th), nil
+ }
+}
diff --git a/sumdb/internal/tlog/tlog_test.go b/sumdb/internal/tlog/tlog_test.go
new file mode 100644
index 0000000..584e728
--- /dev/null
+++ b/sumdb/internal/tlog/tlog_test.go
@@ -0,0 +1,269 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tlog
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+type testHashStorage []Hash
+
+func (t testHashStorage) ReadHash(level int, n int64) (Hash, error) {
+ return t[StoredHashIndex(level, n)], nil
+}
+
+func (t testHashStorage) ReadHashes(index []int64) ([]Hash, error) {
+ // It's not required by HashReader that indexes be in increasing order,
+ // but check that the functions we are testing only ever ask for
+ // indexes in increasing order.
+ for i := 1; i < len(index); i++ {
+ if index[i-1] >= index[i] {
+ panic("indexes out of order")
+ }
+ }
+
+ out := make([]Hash, len(index))
+ for i, x := range index {
+ out[i] = t[x]
+ }
+ return out, nil
+}
+
+type testTilesStorage struct {
+ unsaved int
+ m map[Tile][]byte
+}
+
+func (t testTilesStorage) Height() int {
+ return 2
+}
+
+func (t *testTilesStorage) SaveTiles(tiles []Tile, data [][]byte) {
+ t.unsaved -= len(tiles)
+}
+
+func (t *testTilesStorage) ReadTiles(tiles []Tile) ([][]byte, error) {
+ out := make([][]byte, len(tiles))
+ for i, tile := range tiles {
+ out[i] = t.m[tile]
+ }
+ t.unsaved += len(tiles)
+ return out, nil
+}
+
+func TestTree(t *testing.T) {
+ var trees []Hash
+ var leafhashes []Hash
+ var storage testHashStorage
+ tiles := make(map[Tile][]byte)
+ const testH = 2
+ for i := int64(0); i < 100; i++ {
+ data := []byte(fmt.Sprintf("leaf %d", i))
+ hashes, err := StoredHashes(i, data, storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ leafhashes = append(leafhashes, RecordHash(data))
+ oldStorage := len(storage)
+ storage = append(storage, hashes...)
+ if count := StoredHashCount(i + 1); count != int64(len(storage)) {
+ t.Errorf("StoredHashCount(%d) = %d, have %d StoredHashes", i+1, count, len(storage))
+ }
+ th, err := TreeHash(i+1, storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, tile := range NewTiles(testH, i, i+1) {
+ data, err := ReadTileData(tile, storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ old := Tile{H: tile.H, L: tile.L, N: tile.N, W: tile.W - 1}
+ oldData := tiles[old]
+ if len(oldData) != len(data)-HashSize || !bytes.Equal(oldData, data[:len(oldData)]) {
+ t.Fatalf("tile %v not extending earlier tile %v", tile.Path(), old.Path())
+ }
+ tiles[tile] = data
+ }
+ for _, tile := range NewTiles(testH, 0, i+1) {
+ data, err := ReadTileData(tile, storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(tiles[tile], data) {
+ t.Fatalf("mismatch at %+v", tile)
+ }
+ }
+ for _, tile := range NewTiles(testH, i/2, i+1) {
+ data, err := ReadTileData(tile, storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(tiles[tile], data) {
+ t.Fatalf("mismatch at %+v", tile)
+ }
+ }
+
+ // Check that all the new hashes are readable from their tiles.
+ for j := oldStorage; j < len(storage); j++ {
+ tile := TileForIndex(testH, int64(j))
+ data, ok := tiles[tile]
+ if !ok {
+ t.Log(NewTiles(testH, 0, i+1))
+ t.Fatalf("TileForIndex(%d, %d) = %v, not yet stored (i=%d, stored %d)", testH, j, tile.Path(), i, len(storage))
+ continue
+ }
+ h, err := HashFromTile(tile, data, int64(j))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if h != storage[j] {
+ t.Errorf("HashFromTile(%v, %d) = %v, want %v", tile.Path(), int64(j), h, storage[j])
+ }
+ }
+
+ trees = append(trees, th)
+
+ // Check that leaf proofs work, for all trees and leaves so far.
+ for j := int64(0); j <= i; j++ {
+ p, err := ProveRecord(i+1, j, storage)
+ if err != nil {
+ t.Fatalf("ProveRecord(%d, %d): %v", i+1, j, err)
+ }
+ if err := CheckRecord(p, i+1, th, j, leafhashes[j]); err != nil {
+ t.Fatalf("CheckRecord(%d, %d): %v", i+1, j, err)
+ }
+ for k := range p {
+ p[k][0] ^= 1
+ if err := CheckRecord(p, i+1, th, j, leafhashes[j]); err == nil {
+ t.Fatalf("CheckRecord(%d, %d) succeeded with corrupt proof hash #%d!", i+1, j, k)
+ }
+ p[k][0] ^= 1
+ }
+ }
+
+ // Check that leaf proofs work using TileReader.
+ // To prove a leaf that way, all you have to do is read and verify its hash.
+ storage := &testTilesStorage{m: tiles}
+ thr := TileHashReader(Tree{i + 1, th}, storage)
+ for j := int64(0); j <= i; j++ {
+ h, err := thr.ReadHashes([]int64{StoredHashIndex(0, j)})
+ if err != nil {
+ t.Fatalf("TileHashReader(%d).ReadHashes(%d): %v", i+1, j, err)
+ }
+ if h[0] != leafhashes[j] {
+ t.Fatalf("TileHashReader(%d).ReadHashes(%d) returned wrong hash", i+1, j)
+ }
+
+ // Even though reading the hash suffices,
+ // check we can generate the proof too.
+ p, err := ProveRecord(i+1, j, thr)
+ if err != nil {
+ t.Fatalf("ProveRecord(%d, %d, TileHashReader(%d)): %v", i+1, j, i+1, err)
+ }
+ if err := CheckRecord(p, i+1, th, j, leafhashes[j]); err != nil {
+ t.Fatalf("CheckRecord(%d, %d, TileHashReader(%d)): %v", i+1, j, i+1, err)
+ }
+ }
+ if storage.unsaved != 0 {
+ t.Fatalf("TileHashReader(%d) did not save %d tiles", i+1, storage.unsaved)
+ }
+
+ // Check that ReadHashes will give an error if the index is not in the tree.
+ if _, err := thr.ReadHashes([]int64{(i + 1) * 2}); err == nil {
+ t.Fatalf("TileHashReader(%d).ReadHashes(%d) for index not in tree <nil>, want err", i, i+1)
+ }
+ if storage.unsaved != 0 {
+ t.Fatalf("TileHashReader(%d) did not save %d tiles", i+1, storage.unsaved)
+ }
+
+ // Check that tree proofs work, for all trees so far, using TileReader.
+ // To prove a tree that way, all you have to do is compute and verify its hash.
+ for j := int64(0); j <= i; j++ {
+ h, err := TreeHash(j+1, thr)
+ if err != nil {
+ t.Fatalf("TreeHash(%d, TileHashReader(%d)): %v", j, i+1, err)
+ }
+ if h != trees[j] {
+ t.Fatalf("TreeHash(%d, TileHashReader(%d)) = %x, want %x (%v)", j, i+1, h[:], trees[j][:], trees[j])
+ }
+
+ // Even though computing the subtree hash suffices,
+ // check that we can generate the proof too.
+ p, err := ProveTree(i+1, j+1, thr)
+ if err != nil {
+ t.Fatalf("ProveTree(%d, %d): %v", i+1, j+1, err)
+ }
+ if err := CheckTree(p, i+1, th, j+1, trees[j]); err != nil {
+ t.Fatalf("CheckTree(%d, %d): %v [%v]", i+1, j+1, err, p)
+ }
+ for k := range p {
+ p[k][0] ^= 1
+ if err := CheckTree(p, i+1, th, j+1, trees[j]); err == nil {
+ t.Fatalf("CheckTree(%d, %d) succeeded with corrupt proof hash #%d!", i+1, j+1, k)
+ }
+ p[k][0] ^= 1
+ }
+ }
+ if storage.unsaved != 0 {
+ t.Fatalf("TileHashReader(%d) did not save %d tiles", i+1, storage.unsaved)
+ }
+ }
+}
+
+func TestSplitStoredHashIndex(t *testing.T) {
+ for l := 0; l < 10; l++ {
+ for n := int64(0); n < 100; n++ {
+ x := StoredHashIndex(l, n)
+ l1, n1 := SplitStoredHashIndex(x)
+ if l1 != l || n1 != n {
+ t.Fatalf("StoredHashIndex(%d, %d) = %d, but SplitStoredHashIndex(%d) = %d, %d", l, n, x, x, l1, n1)
+ }
+ }
+ }
+}
+
+// TODO(rsc): Test invalid paths too, like "tile/3/5/123/456/078".
+var tilePaths = []struct {
+ path string
+ tile Tile
+}{
+ {"tile/4/0/001", Tile{4, 0, 1, 16}},
+ {"tile/4/0/001.p/5", Tile{4, 0, 1, 5}},
+ {"tile/3/5/x123/x456/078", Tile{3, 5, 123456078, 8}},
+ {"tile/3/5/x123/x456/078.p/2", Tile{3, 5, 123456078, 2}},
+ {"tile/1/0/x003/x057/500", Tile{1, 0, 3057500, 2}},
+ {"tile/3/5/123/456/078", Tile{}},
+ {"tile/3/-1/123/456/078", Tile{}},
+ {"tile/1/data/x003/x057/500", Tile{1, -1, 3057500, 2}},
+}
+
+func TestTilePath(t *testing.T) {
+ for _, tt := range tilePaths {
+ if tt.tile.H > 0 {
+ p := tt.tile.Path()
+ if p != tt.path {
+ t.Errorf("%+v.Path() = %q, want %q", tt.tile, p, tt.path)
+ }
+ }
+ tile, err := ParseTilePath(tt.path)
+ if err != nil {
+ if tt.tile.H == 0 {
+ // Expected error.
+ continue
+ }
+ t.Errorf("ParseTilePath(%q): %v", tt.path, err)
+ } else if tile != tt.tile {
+ if tt.tile.H == 0 {
+ t.Errorf("ParseTilePath(%q): expected error, got %+v", tt.path, tt.tile)
+ continue
+ }
+ t.Errorf("ParseTilePath(%q) = %+v, want %+v", tt.path, tile, tt.tile)
+ }
+ }
+}
diff --git a/typeparams/common.go b/typeparams/common.go
new file mode 100644
index 0000000..7f867cf
--- /dev/null
+++ b/typeparams/common.go
@@ -0,0 +1,182 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package typeparams contains common utilities for writing tools that interact
+// with generic Go code, as introduced with Go 1.18.
+//
+// Many of the types and functions in this package are proxies for the new APIs
+// introduced in the standard library with Go 1.18. For example, the
+// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
+// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
+// versions older than 1.18 these helpers are implemented as stubs, allowing
+// users of this package to write code that handles generic constructs inline,
+// even if the Go version being used to compile does not support generics.
+//
+// Additionally, this package contains common utilities for working with the
+// new generic constructs, to supplement the standard library APIs. Notably,
+// the NormalTerms API computes a minimal representation of the structural
+// restrictions on a type parameter. In the future, these supplemental APIs may
+// be available in the standard library..
+package typeparams
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+)
+
+// Enabled reports whether type parameters are enabled in the current build
+// environment.
+func Enabled() bool {
+ return enabled
+}
+
+// UnpackIndexExpr extracts data from AST nodes that represent index
+// expressions.
+//
+// For an ast.IndexExpr, the resulting indices slice will contain exactly one
+// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable
+// number of index expressions.
+//
+// For nodes that don't represent index expressions, the first return value of
+// UnpackIndexExpr will be nil.
+func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) {
+ switch e := n.(type) {
+ case *ast.IndexExpr:
+ return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
+ case *IndexListExpr:
+ return e.X, e.Lbrack, e.Indices, e.Rbrack
+ }
+ return nil, token.NoPos, nil, token.NoPos
+}
+
+// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on
+// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0
+// will panic.
+func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr {
+ switch len(indices) {
+ case 0:
+ panic("empty indices")
+ case 1:
+ return &ast.IndexExpr{
+ X: x,
+ Lbrack: lbrack,
+ Index: indices[0],
+ Rbrack: rbrack,
+ }
+ default:
+ return &IndexListExpr{
+ X: x,
+ Lbrack: lbrack,
+ Indices: indices,
+ Rbrack: rbrack,
+ }
+ }
+}
+
+// IsTypeParam reports whether t is a type parameter.
+func IsTypeParam(t types.Type) bool {
+ _, ok := t.(*TypeParam)
+ return ok
+}
+
+// OriginMethod returns the origin method associated with the method fn. For
+// methods on a non-generic receiver base type, this is just fn. However, for
+// methods with a generic receiver, OriginMethod returns the corresponding
+// method in the method set of the origin type.
+//
+// As a special case, if fn is not a method (has no receiver), OriginMethod
+// returns fn.
+func OriginMethod(fn *types.Func) *types.Func {
+ recv := fn.Type().(*types.Signature).Recv()
+ if recv == nil {
+ return fn
+ }
+ base := recv.Type()
+ p, isPtr := base.(*types.Pointer)
+ if isPtr {
+ base = p.Elem()
+ }
+ named, isNamed := base.(*types.Named)
+ if !isNamed {
+ // Receiver is a *types.Interface.
+ return fn
+ }
+ if ForNamed(named).Len() == 0 {
+ // Receiver base has no type parameters, so we can avoid the lookup below.
+ return fn
+ }
+ orig := NamedTypeOrigin(named)
+ gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
+ return gfn.(*types.Func)
+}
+
+// GenericAssignableTo is a generalization of types.AssignableTo that
+// implements the following rule for uninstantiated generic types:
+//
+// If V and T are generic named types, then V is considered assignable to T if,
+// for every possible instantation of V[A_1, ..., A_N], the instantiation
+// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
+//
+// If T has structural constraints, they must be satisfied by V.
+//
+// For example, consider the following type declarations:
+//
+// type Interface[T any] interface {
+// Accept(T)
+// }
+//
+// type Container[T any] struct {
+// Element T
+// }
+//
+// func (c Container[T]) Accept(t T) { c.Element = t }
+//
+// In this case, GenericAssignableTo reports that instantiations of Container
+// are assignable to the corresponding instantiation of Interface.
+func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
+ // If V and T are not both named, or do not have matching non-empty type
+ // parameter lists, fall back on types.AssignableTo.
+
+ VN, Vnamed := V.(*types.Named)
+ TN, Tnamed := T.(*types.Named)
+ if !Vnamed || !Tnamed {
+ return types.AssignableTo(V, T)
+ }
+
+ vtparams := ForNamed(VN)
+ ttparams := ForNamed(TN)
+ if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 {
+ return types.AssignableTo(V, T)
+ }
+
+ // V and T have the same (non-zero) number of type params. Instantiate both
+ // with the type parameters of V. This must always succeed for V, and will
+ // succeed for T if and only if the type set of each type parameter of V is a
+ // subset of the type set of the corresponding type parameter of T, meaning
+ // that every instantiation of V corresponds to a valid instantiation of T.
+
+ // Minor optimization: ensure we share a context across the two
+ // instantiations below.
+ if ctxt == nil {
+ ctxt = NewContext()
+ }
+
+ var targs []types.Type
+ for i := 0; i < vtparams.Len(); i++ {
+ targs = append(targs, vtparams.At(i))
+ }
+
+ vinst, err := Instantiate(ctxt, V, targs, true)
+ if err != nil {
+ panic("type parameters should satisfy their own constraints")
+ }
+
+ tinst, err := Instantiate(ctxt, T, targs, true)
+ if err != nil {
+ return false
+ }
+
+ return types.AssignableTo(vinst, tinst)
+}
diff --git a/typeparams/common_test.go b/typeparams/common_test.go
new file mode 100644
index 0000000..5dd5c3f
--- /dev/null
+++ b/typeparams/common_test.go
@@ -0,0 +1,238 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package typeparams_test
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "testing"
+
+ . "golang.org/x/exp/typeparams"
+)
+
+func TestGetIndexExprData(t *testing.T) {
+ x := &ast.Ident{}
+ i := &ast.Ident{}
+
+ want := &IndexListExpr{X: x, Lbrack: 1, Indices: []ast.Expr{i}, Rbrack: 2}
+ tests := map[ast.Node]bool{
+ &ast.IndexExpr{X: x, Lbrack: 1, Index: i, Rbrack: 2}: true,
+ want: true,
+ &ast.Ident{}: false,
+ }
+
+ for n, isIndexExpr := range tests {
+ X, lbrack, indices, rbrack := UnpackIndexExpr(n)
+ if got := X != nil; got != isIndexExpr {
+ t.Errorf("UnpackIndexExpr(%v) = %v, _, _, _; want nil: %t", n, x, !isIndexExpr)
+ }
+ if X == nil {
+ continue
+ }
+ if X != x || lbrack != 1 || indices[0] != i || rbrack != 2 {
+ t.Errorf("UnpackIndexExprData(%v) = %v, %v, %v, %v; want %+v", n, x, lbrack, indices, rbrack, want)
+ }
+ }
+}
+
+func SkipIfNotEnabled(t *testing.T) {
+ if !Enabled() {
+ t.Skip("type parameters are not enabled")
+ }
+}
+
+func TestOriginMethodRecursive(t *testing.T) {
+ SkipIfNotEnabled(t)
+
+ src := `package p
+
+type N[A any] int
+
+func (r N[B]) m() { r.m(); r.n() }
+
+func (r *N[C]) n() { }
+`
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ info := types.Info{
+ Defs: make(map[*ast.Ident]types.Object),
+ Uses: make(map[*ast.Ident]types.Object),
+ }
+ var conf types.Config
+ if _, err := conf.Check("p", fset, []*ast.File{f}, &info); err != nil {
+ t.Fatal(err)
+ }
+
+ // Collect objects from types.Info.
+ var m, n *types.Func // the 'origin' methods in Info.Defs
+ var mm, mn *types.Func // the methods used in the body of m
+
+ for _, decl := range f.Decls {
+ fdecl, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ def := info.Defs[fdecl.Name].(*types.Func)
+ switch fdecl.Name.Name {
+ case "m":
+ m = def
+ ast.Inspect(fdecl.Body, func(n ast.Node) bool {
+ if call, ok := n.(*ast.CallExpr); ok {
+ sel := call.Fun.(*ast.SelectorExpr)
+ use := info.Uses[sel.Sel].(*types.Func)
+ switch sel.Sel.Name {
+ case "m":
+ mm = use
+ case "n":
+ mn = use
+ }
+ }
+ return true
+ })
+ case "n":
+ n = def
+ }
+ }
+
+ tests := []struct {
+ name string
+ input, want *types.Func
+ }{
+ {"declared m", m, m},
+ {"declared n", n, n},
+ {"used m", mm, m},
+ {"used n", mn, n},
+ }
+
+ for _, test := range tests {
+ if got := OriginMethod(test.input); got != test.want {
+ t.Errorf("OriginMethod(%q) = %v, want %v", test.name, test.input, test.want)
+ }
+ }
+}
+
+func TestOriginMethodUses(t *testing.T) {
+ SkipIfNotEnabled(t)
+
+ tests := []string{
+ `type T interface { m() }; func _(t T) { t.m() }`,
+ `type T[P any] interface { m() P }; func _[A any](t T[A]) { t.m() }`,
+ `type T[P any] interface { m() P }; func _(t T[int]) { t.m() }`,
+ `type T[P any] int; func (r T[A]) m() { r.m() }`,
+ `type T[P any] int; func (r *T[A]) m() { r.m() }`,
+ `type T[P any] int; func (r *T[A]) m() {}; func _(t T[int]) { t.m() }`,
+ `type T[P any] int; func (r *T[A]) m() {}; func _[A any](t T[A]) { t.m() }`,
+ }
+
+ for _, src := range tests {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", "package p; "+src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ info := types.Info{
+ Uses: make(map[*ast.Ident]types.Object),
+ }
+ var conf types.Config
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, &info)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ T := pkg.Scope().Lookup("T").Type()
+ obj, _, _ := types.LookupFieldOrMethod(T, true, pkg, "m")
+ m := obj.(*types.Func)
+
+ ast.Inspect(f, func(n ast.Node) bool {
+ if call, ok := n.(*ast.CallExpr); ok {
+ sel := call.Fun.(*ast.SelectorExpr)
+ use := info.Uses[sel.Sel].(*types.Func)
+ orig := OriginMethod(use)
+ if orig != m {
+ t.Errorf("%s:\nUses[%v] = %v, want %v", src, types.ExprString(sel), use, m)
+ }
+ }
+ return true
+ })
+ }
+}
+
+func TestGenericAssignableTo(t *testing.T) {
+ SkipIfNotEnabled(t)
+
+ tests := []struct {
+ src string
+ want bool
+ }{
+ // The inciting issue: golang/go#50887.
+ {`
+ type T[P any] interface {
+ Accept(P)
+ }
+
+ type V[Q any] struct {
+ Element Q
+ }
+
+ func (c V[Q]) Accept(q Q) { c.Element = q }
+ `, true},
+
+ // Various permutations on constraints and signatures.
+ {`type T[P ~int] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P int] interface{ A(P) }; type V[Q ~int] int; func (V[Q]) A(Q) {}`, false},
+ {`type T[P int|string] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P any] interface{ A(P) }; type V[Q any] int; func (V[Q]) A(Q, Q) {}`, false},
+ {`type T[P any] interface{ int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, false},
+
+ // Various structural restrictions on T.
+ {`type T[P any] interface{ ~int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P any] interface{ ~int|string; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P any] interface{ int; A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, false},
+
+ // Various recursive constraints.
+ {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ f *Q }] int; func (V[Q]) A(Q) {}`, true},
+ {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ g *Q }] int; func (V[Q]) A(Q) {}`, false},
+ {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
+ {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~**Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
+ {`type T[P, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
+ {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
+ {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
+
+ // In this test case, we reverse the type parameters in the signature of V.A
+ {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
+ // It would be nice to return true here: V can only be instantiated with
+ // [int, int], so the identity of the type parameters should not matter.
+ {`type T[P, X any] interface{ A(P) X }; type V[Q, Y int] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
+ }
+
+ for _, test := range tests {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", "package p; "+test.src, 0)
+ if err != nil {
+ t.Fatalf("%s:\n%v", test.src, err)
+ }
+ var conf types.Config
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ t.Fatalf("%s:\n%v", test.src, err)
+ }
+
+ V := pkg.Scope().Lookup("V").Type()
+ T := pkg.Scope().Lookup("T").Type()
+
+ if types.AssignableTo(V, T) {
+ t.Fatal("AssignableTo")
+ }
+
+ if got := GenericAssignableTo(nil, V, T); got != test.want {
+ t.Fatalf("%s:\nGenericAssignableTo(%v, %v) = %v, want %v", test.src, V, T, got, test.want)
+ }
+ }
+}
diff --git a/typeparams/copytermlist.go b/typeparams/copytermlist.go
new file mode 100644
index 0000000..c67dc17
--- /dev/null
+++ b/typeparams/copytermlist.go
@@ -0,0 +1,99 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+// copytermlist.go copies the term list algorithm from GOROOT/src/go/types.
+// Run it with:
+// GO111MODULE=off go run copytermlist.go
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/parser"
+ "go/token"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+func main() {
+ if err := doCopy(); err != nil {
+ fmt.Fprintf(os.Stderr, "error copying from go/types: %v", err)
+ os.Exit(1)
+ }
+}
+
+func doCopy() error {
+ dir := filepath.Join(runtime.GOROOT(), "src", "go", "types")
+ for _, name := range []string{"typeterm.go", "termlist.go"} {
+ path := filepath.Join(dir, name)
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+ file.Name.Name = "typeparams"
+ file.Doc = &ast.CommentGroup{List: []*ast.Comment{&ast.Comment{Text: "DO NOT MODIFY"}}}
+ var needImport bool
+ selectorType := reflect.TypeOf((*ast.SelectorExpr)(nil))
+ astutil.Apply(file, func(c *astutil.Cursor) bool {
+ if id, _ := c.Node().(*ast.Ident); id != nil {
+ // Check if this ident should be qualified with types. For simplicity,
+ // assume the copied files do not themselves contain any exported
+ // symbols.
+
+ // As a simple heuristic, just verify that the ident may be replaced by
+ // a selector.
+ if !token.IsExported(id.Name) {
+ return false
+ }
+ v := reflect.TypeOf(c.Parent()).Elem() // ast nodes are all pointers
+ field, ok := v.FieldByName(c.Name())
+ if !ok {
+ panic("missing field")
+ }
+ t := field.Type
+ if c.Index() > 0 { // => t is a slice
+ t = t.Elem()
+ }
+ if !selectorType.AssignableTo(t) {
+ return false
+ }
+ needImport = true
+ c.Replace(&ast.SelectorExpr{
+ X: ast.NewIdent("types"),
+ Sel: ast.NewIdent(id.Name),
+ })
+ }
+ return true
+ }, nil)
+ if needImport {
+ astutil.AddImport(fset, file, "go/types")
+ }
+
+ var b bytes.Buffer
+ if err := format.Node(&b, fset, file); err != nil {
+ return err
+ }
+
+ // Hack in the 'generated' byline.
+ content := b.String()
+ header := "// Code generated by copytermlist.go DO NOT EDIT.\n\npackage typeparams"
+ content = strings.Replace(content, "package typeparams", header, 1)
+
+ if err := os.WriteFile(name, []byte(content), 0644); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/typeparams/example/README.md b/typeparams/example/README.md
new file mode 100644
index 0000000..afe5bfb
--- /dev/null
+++ b/typeparams/example/README.md
@@ -0,0 +1,898 @@
+<!-- Autogenerated by weave; DO NOT EDIT -->
+<!-- To regenerate the readme, run: -->
+<!-- go run golang.org/x/example/gotypes@latest generic-go-types.md -->
+
+# Updating tools to support type parameters.
+
+This guide is maintained by Rob Findley (`rfindley@google.com`).
+
+**status**: this document is currently a rough-draft. See [golang/go#50447](https://go.dev/issues/50447) for more details.
+
+1. [Who should read this guide](#who-should-read-this-guide)
+1. [Introduction](#introduction)
+1. [Summary of new language features and their APIs](#summary-of-new-language-features-and-their-apis)
+1. [Examples](#examples)
+ 1. [Generic types: type parameters](#generic-types:-type-parameters)
+ 1. [Constraint Interfaces](#constraint-interfaces)
+ 1. [Instantiation](#instantiation)
+ 1. [Generic types continued: method sets and predicates](#generic-types-continued:-method-sets-and-predicates)
+1. [Updating tools while building at older Go versions](#updating-tools-while-building-at-older-go-versions)
+1. [Further help](#further-help)
+
+# Who should read this guide
+
+Read this guide if you are a tool author seeking to update your tools to
+support generics Go code. Generics introduce significant new complexity to the
+Go type system, because types can now be _parameterized_. While the
+fundamentals of the `go/types` APIs remain the same, some previously valid
+assumptions no longer hold. For example:
+
+ - Type declarations need not correspond 1:1 with the types they define.
+ - Interfaces are no longer determined entirely by their method set.
+ - The set of concrete types implementing `types.Type` has grown to include
+ `types.TypeParam` and `types.Union`.
+
+# Introduction
+
+With Go 1.18, Go now supports generic programming via type parameters. This
+document is a guide for tool authors that want to update their tools to support
+the new language constructs.
+
+This guide assumes knowledge of the language changes to support generics. See
+the following references for more information:
+
+- The [original proposal](https://go.dev/issue/43651) for type parameters.
+- The [addendum for type sets](https://go.dev/issue/45346).
+- The [latest language specfication](https://tip.golang.org/ref/spec) (still in-progress as of 2021-01-11).
+- The proposals for new APIs in
+ [go/token and go/ast](https://go.dev/issue/47781), and in
+ [go/types](https://go.dev/issue/47916).
+
+It also assumes knowledge of `go/ast` and `go/types`. If you're just getting
+started,
+[x/example/gotypes](https://github.com/golang/example/tree/master/gotypes) is
+a great introduction (and was the inspiration for this guide).
+
+# Summary of new language features and their APIs
+
+The introduction of of generic features appears as a large change to the
+language, but a high level introduces only a few new concepts. We can break
+down our discussion into the following three broad categories: generic types,
+constraint interfaces, and instantiation. In each category below, the relevant
+new APIs are listed (some constructors and getters/setters may be elided where
+they are trivial):
+
+**Generic types**. Types and functions may be _generic_, meaning their
+declaration may have a non-empty _type parameter list_, as in
+`type List[T any] ...` or `func f[T1, T2 any]() { ... }`. Type parameter lists
+define placeholder types (_type parameters_), scoped to the declaration, which
+may be substituted by any type satisfying their corresponding _constraint
+interface_ to _instantiate_ a new type or function.
+
+Generic types may have methods, which declare `receiver type parameters` via
+their receiver type expression: `func (r T[P1, ..., PN]) method(...) (...)
+{...}`.
+
+_New APIs_:
+ - The field `ast.TypeSpec.TypeParams` holds the type parameter list syntax for
+ type declarations.
+ - The field `ast.FuncType.TypeParams` holds the type parameter list syntax for
+ function declarations.
+ - The type `types.TypeParam` is a `types.Type` representing a type parameter.
+ On this type, the `Constraint` and `SetConstraint` methods allow
+ getting/setting the constraint, the `Index` method returns the numeric index
+ of the type parameter in the type parameter list that declares it, and the
+ `Obj` method returns the object in the scope a for the type parameter (a
+ `types.TypeName`). Generic type declarations have a new `*types.Scope` for
+ type parameter declarations.
+ - The type `types.TypeParamList` holds a list of type parameters.
+ - The method `types.Named.TypeParams` returns the type parameters for a type
+ declaration.
+ - The method `types.Named.SetTypeParams` sets type parameters on a defined
+ type.
+ - The function `types.NewSignatureType` creates a new (possibly generic)
+ signature type.
+ - The method `types.Signature.RecvTypeParams` returns the receiver type
+ parameters for a method.
+ - The method `types.Signature.TypeParams` returns the type parameters for
+ a function.
+
+**Constraint Interfaces**: type parameter constraints are interfaces, expressed
+by an interface type expression. Interfaces that are only used in constraint
+position are permitted new embedded elements composed of tilde expressions
+(`~T`) and unions (`A | B | ~C`). The new builtin interface type `comparable`
+is implemented by types for which `==` and `!=` are valid (note that interfaces
+must be statically comparable in this case, i.e., each type in the interface's
+type set must be comparable). As a special case, the `interface` keyword may be
+omitted from constraint expressions if it may be implied (in which case we say
+the interface is _implicit_).
+
+_New APIs_:
+ - The constant `token.TILDE` is used to represent tilde expressions as an
+ `ast.UnaryExpr`.
+ - Union expressions are represented as an `ast.BinaryExpr` using `|`. This
+ means that `ast.BinaryExpr` may now be both a type and value expression.
+ - The method `types.Interface.IsImplicit` reports whether the `interface`
+ keyword was elided from this interface.
+ - The method `types.Interface.MarkImplicit` marks an interface as being
+ implicit.
+ - The method `types.Interface.IsComparable` reports whether every type in an
+ interface's type set is comparable.
+ - The method `types.Interface.IsMethodSet` reports whether an interface is
+ defined entirely by its methods (has no _specific types_).
+ - The type `types.Union` is a type that represents an embedded union
+ expression in an interface. May only appear as an embedded element in
+ interfaces.
+ - The type `types.Term` represents a (possibly tilde) term of a union.
+
+**Instantiation**: generic types and functions may be _instantiated_ to create
+non-generic types and functions by providing _type arguments_ (`var x T[int]`).
+Function type arguments may be _inferred_ via function arguments, or via
+type parameter constraints.
+
+_New APIs_:
+ - The type `ast.IndexListExpr` holds index expressions with multiple indices,
+ as in instantiation expressions with multiple type arguments or in receivers
+ declaring multiple type parameters.
+ - The function `types.Instantiate` instantiates a generic type with type arguments.
+ - The type `types.Context` is an opaque instantiation context that may be
+ shared to reduce duplicate instances.
+ - The field `types.Config.Context` holds a shared `Context` to use for
+ instantiation while type-checking.
+ - The type `types.TypeList` holds a list of types.
+ - The type `types.ArgumentError` holds an error associated with a specific
+ type argument index. Used to represent instantiation errors.
+ - The field `types.Info.Instances` maps instantiated identifiers to information
+ about the resulting type instance.
+ - The type `types.Instance` holds information about a type or function
+ instance.
+ - The method `types.Named.TypeArgs` reports the type arguments used to
+ instantiate a named type.
+
+# Examples
+
+The following examples demonstrate the new APIs, and discuss their properties.
+All examples are runnable, contained in subdirectories of the directory holding
+this README.
+
+## Generic types: type parameters
+
+We say that a type is _generic_ if it has type parameters but no type
+arguments. This section explains how we can inspect generic types with the new
+`go/types` APIs.
+
+### Type parameter lists
+
+Suppose we want to understand the generic library below, which defines a generic
+`Pair`, a constraint interface `Constraint`, and a generic function `MakePair`.
+
+```
+package main
+
+type Constraint interface {
+ Value() any
+}
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func MakePair[L, R Constraint](l L, r R) Pair[L, R] {
+ return Pair[L, R]{l, r}
+}
+```
+
+We can use the new `TypeParams` fields in `ast.TypeSpec` and `ast.FuncType` to
+access the type parameter list. From there, we can access type parameter types
+in at least three ways:
+ - by looking up type parameter definitions in `types.Info`
+ - by calling `TypeParams()` on `types.Named` or `types.Signature`
+ - by looking up type parameter objects in the declaration scope. Note that
+ there now may be a scope associated with an `ast.TypeSpec` node.
+
+```
+func PrintTypeParams(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{Importer: importer.Default()}
+ info := &types.Info{
+ Scopes: make(map[ast.Node]*types.Scope),
+ Defs: make(map[*ast.Ident]types.Object),
+ }
+ _, err := conf.Check("hello", fset, []*ast.File{file}, info)
+ if err != nil {
+ return err
+ }
+
+ // For convenience, we can use ast.Inspect to find the nodes we want to
+ // investigate.
+ ast.Inspect(file, func(n ast.Node) bool {
+ var name *ast.Ident // the name of the generic object, or nil
+ var tparamFields *ast.FieldList // the list of type parameter fields
+ var tparams *types.TypeParamList // the list of type parameter types
+ var scopeNode ast.Node // the node associated with the declaration scope
+
+ switch n := n.(type) {
+ case *ast.TypeSpec:
+ name = n.Name
+ tparamFields = n.TypeParams
+ tparams = info.Defs[name].Type().(*types.Named).TypeParams()
+ scopeNode = n
+ case *ast.FuncDecl:
+ name = n.Name
+ tparamFields = n.Type.TypeParams
+ tparams = info.Defs[name].Type().(*types.Signature).TypeParams()
+ scopeNode = n.Type
+ default:
+ // Not a type or function declaration.
+ return true
+ }
+
+ // Option 1: find type parameters by looking at their declaring field list.
+ if tparamFields != nil {
+ fmt.Printf("%s has a type parameter field list with %d fields\n", name.Name, tparamFields.NumFields())
+ for _, field := range tparamFields.List {
+ for _, name := range field.Names {
+ tparam := info.Defs[name]
+ fmt.Printf(" field %s defines an object %q\n", name.Name, tparam)
+ }
+ }
+ } else {
+ fmt.Printf("%s does not have a type parameter list\n", name.Name)
+ }
+
+ // Option 2: find type parameters via the TypeParams() method on the
+ // generic type.
+ if tparams.Len() > 0 {
+ fmt.Printf("%s has %d type parameters:\n", name.Name, tparams.Len())
+ for i := 0; i < tparams.Len(); i++ {
+ tparam := tparams.At(i)
+ fmt.Printf(" %s has constraint %s\n", tparam, tparam.Constraint())
+ }
+ } else {
+ fmt.Printf("%s does not have type parameters\n", name.Name)
+ }
+
+ // Option 3: find type parameters by looking in the declaration scope.
+ scope, ok := info.Scopes[scopeNode]
+ if ok {
+ fmt.Printf("%s has a scope with %d objects:\n", name.Name, scope.Len())
+ for _, name := range scope.Names() {
+ fmt.Printf(" %s is a %T\n", name, scope.Lookup(name))
+ }
+ } else {
+ fmt.Printf("%s does not have a scope\n", name.Name)
+ }
+
+ return true
+ })
+ return nil
+}
+```
+
+This program produces the following output. Note that not every type spec has
+a scope.
+
+```
+> go run golang.org/x/tools/internal/typeparams/example/findtypeparams
+Constraint does not have a type parameter list
+Constraint does not have type parameters
+Constraint does not have a scope
+Pair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L any"
+ field R defines an object "type parameter R any"
+Pair has 2 type parameters:
+ L has constraint any
+ R has constraint any
+Pair has a scope with 2 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+MakePair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L hello.Constraint"
+ field R defines an object "type parameter R hello.Constraint"
+MakePair has 2 type parameters:
+ L has constraint hello.Constraint
+ R has constraint hello.Constraint
+MakePair has a scope with 4 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+ l is a *types.Var
+ r is a *types.Var
+```
+
+## Constraint Interfaces
+
+In order to allow operations on type parameters, Go 1.18 introduces the notion
+of [_type sets_](https://tip.golang.org/ref/spec#Interface_types), which is
+abstractly the set of types that implement an interface. This section discusses
+the new syntax for restrictions on interface type sets, and the APIs we can use
+to understand them.
+
+### New interface elements
+
+Consider the generic library below:
+
+```
+package p
+
+type Numeric interface{
+ ~int | ~float64 // etc...
+}
+
+func Square[N Numeric](n N) N {
+ return n*n
+}
+
+type Findable interface {
+ comparable
+}
+
+func Find[T Findable](s []T, v T) int {
+ for i, v2 := range s {
+ if v2 == v {
+ return i
+ }
+ }
+ return -1
+}
+```
+
+In this library, we can see a few new features added in Go 1.18. The first is
+the new syntax in the `Numeric` type: unions of tilde-terms, specifying that
+the numeric type may only be satisfied by types whose underlying type is `int`
+or `float64`.
+
+The `go/ast` package parses this new syntax as a combination of unary and
+binary expressions, which we can see using the following program:
+
+```
+func PrintNumericSyntax(fset *token.FileSet, file *ast.File) {
+ // node is the AST node corresponding to the declaration for "Numeric."
+ node := file.Scope.Lookup("Numeric").Decl.(*ast.TypeSpec)
+ // Find the embedded syntax node.
+ embedded := node.Type.(*ast.InterfaceType).Methods.List[0].Type
+ // Use go/ast's built-in Print function to inspect the parsed syntax.
+ ast.Print(fset, embedded)
+}
+```
+
+Output:
+
+```
+ 0 *ast.BinaryExpr {
+ 1 . X: *ast.UnaryExpr {
+ 2 . . OpPos: p.go:6:2
+ 3 . . Op: ~
+ 4 . . X: *ast.Ident {
+ 5 . . . NamePos: p.go:6:3
+ 6 . . . Name: "int"
+ 7 . . }
+ 8 . }
+ 9 . OpPos: p.go:6:7
+ 10 . Op: |
+ 11 . Y: *ast.UnaryExpr {
+ 12 . . OpPos: p.go:6:9
+ 13 . . Op: ~
+ 14 . . X: *ast.Ident {
+ 15 . . . NamePos: p.go:6:10
+ 16 . . . Name: "float64"
+ 17 . . }
+ 18 . }
+ 19 }
+```
+
+Once type-checked, these embedded expressions are represented using the new
+`types.Union` type, which flattens the expression into a list of `*types.Term`.
+We can also investigate two new methods of interface:
+`types.Interface.IsComparable`, which reports whether the type set of an
+interface is comparable, and `types.Interface.IsMethodSet`, which reports
+whether an interface is expressable using methods alone.
+
+```
+func PrintInterfaceTypes(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{}
+ pkg, err := conf.Check("hello", fset, []*ast.File{file}, nil)
+ if err != nil {
+ return err
+ }
+
+ PrintIface(pkg, "Numeric")
+ PrintIface(pkg, "Findable")
+
+ return nil
+}
+
+func PrintIface(pkg *types.Package, name string) {
+ obj := pkg.Scope().Lookup(name)
+ fmt.Println(obj)
+ iface := obj.Type().Underlying().(*types.Interface)
+ for i := 0; i < iface.NumEmbeddeds(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ for i := 0; i < iface.NumMethods(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ fmt.Printf("\tIsComparable(): %t\n", iface.IsComparable())
+ fmt.Printf("\tIsMethodSet(): %t\n", iface.IsMethodSet())
+}
+```
+
+Output:
+
+```
+type hello.Numeric interface{~int|~float64}
+ embeded: ~int|~float64
+ IsComparable(): true
+ IsMethodSet(): false
+type hello.Findable interface{comparable}
+ embeded: comparable
+ IsComparable(): true
+ IsMethodSet(): false
+```
+
+The `Findable` type demonstrates another new feature of Go 1.18: the comparable
+built-in. Comparable is a special interface type, not expressable using
+ordinary Go syntax, whose type-set consists of all comparable types.
+
+### Implicit interfaces
+
+For interfaces that do not have methods, we can inline them in constraints and
+elide the `interface` keyword. In the example above, we could have done this
+for the `Square` function:
+
+```
+package p
+
+func Square[N ~int|~float64](n N) N {
+ return n*n
+}
+```
+
+In such cases, the `types.Interface.IsImplicit` method reports whether the
+interface type was implicit. This does not affect the behavior of the
+interface, but is captured for more accurate type strings:
+
+```
+func ShowImplicit(pkg *types.Package) {
+ Square := pkg.Scope().Lookup("Square").Type().(*types.Signature)
+ N := Square.TypeParams().At(0)
+ constraint := N.Constraint().(*types.Interface)
+ fmt.Println(constraint)
+ fmt.Println("IsImplicit:", constraint.IsImplicit())
+}
+```
+
+Output:
+
+```
+~int|~float64
+IsImplicit: true
+```
+
+The `types.Interface.MarkImplicit` method is used to mark interfaces as
+implicit by the importer.
+
+### Type sets
+
+The examples above demonstrate the new APIs for _accessing_ information about
+the new interface elements, but how do we understand
+[_type sets_](https://tip.golang.org/ref/spec#Interface_types), the new
+abstraction that these elements help define? Type sets may be arbitrarily
+complex, as in the following example:
+
+```
+package complex
+
+type A interface{ ~string|~[]byte }
+
+type B interface{ int|string }
+
+type C interface { ~string|~int }
+
+type D interface{ A|B; C }
+```
+
+Here, the type set of `D` simplifies to `~string|int`, but the current
+`go/types` APIs do not expose this information. This will likely be added to
+`go/types` in future versions of Go, but in the meantime we can use the
+`typeparams.NormalTerms` helper:
+
+```
+func PrintNormalTerms(pkg *types.Package) error {
+ D := pkg.Scope().Lookup("D").Type()
+ terms, err := typeparams.NormalTerms(D)
+ if err != nil {
+ return err
+ }
+ for i, term := range terms {
+ if i > 0 {
+ fmt.Print("|")
+ }
+ fmt.Print(term)
+ }
+ fmt.Println()
+ return nil
+}
+```
+
+which outputs:
+
+```
+~string|int
+```
+
+See the documentation for `typeparams.NormalTerms` for more information on how
+this calculation proceeds.
+
+## Instantiation
+
+We say that a type is _instantiated_ if it is created from a generic type by
+substituting type arguments for type parameters. Instantiation can occur via
+explicitly provided type arguments, as in the expression `T[A_1, ..., A_n]`, or
+implicitly, through type inference.. This section describes how to find and
+understand instantiated types.
+
+### Finding instantiated types
+
+Certain applications may find it useful to locate all instantiated types in
+a package. For this purpose, `go/types` provides a new `types.Info.Instances`
+field that maps instantiated identifiers to information about their instance.
+
+For example, consider the following code:
+
+```
+package p
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func Equal[L, R comparable](x, y Pair[L, R]) bool {
+ return x.left == y.left && x.right == y.right
+}
+
+var X Pair[int, string]
+var Y Pair[string, int]
+
+var E = Equal[int, string]
+```
+
+We can find instances by type-checking with the `types.Info.Instances` map
+initialized:
+
+```
+func CheckInstances(fset *token.FileSet, file *ast.File) (*types.Package, error) {
+ conf := types.Config{}
+ info := &types.Info{
+ Instances: make(map[*ast.Ident]types.Instance),
+ }
+ pkg, err := conf.Check("p", fset, []*ast.File{file}, info)
+ for id, inst := range info.Instances {
+ posn := fset.Position(id.Pos())
+ fmt.Printf("%s: %s instantiated with %s: %s\n", posn, id.Name, FormatTypeList(inst.TypeArgs), inst.Type)
+ }
+ return pkg, err
+}
+```
+
+Output:
+
+```
+hello.go:21:9: Equal instantiated with [int, string]: func(x p.Pair[int, string], y p.Pair[int, string]) bool
+hello.go:10:9: Pair instantiated with [L, _]: p.Pair[L, _]
+hello.go:14:34: Pair instantiated with [L, R]: p.Pair[L, R]
+hello.go:18:7: Pair instantiated with [int, string]: p.Pair[int, string]
+hello.go:19:7: Pair instantiated with [string, int]: p.Pair[string, int]
+```
+
+The `types.Instance` type provides information about the (possibly inferred)
+type arguments that were used to instantiate the generic type, and the
+resulting type. Notably, it does not include the _generic_ type that was
+instantiated, because this type can be found using `types.Info.Uses[id].Type()`
+(where `id` is the identifier node being instantiated).
+
+Note that the receiver type of method `Left` also appears in the `Instances`
+map. This may be counterintuitive -- more on this below.
+
+### Creating new instantiated types
+
+`go/types` also provides an API for creating type instances:
+`types.Instantiate`. This function accepts a generic type and type arguments,
+and returns an instantiated type (or an error). The resulting instance may be
+a newly constructed type, or a previously created instance with the same type
+identity. To facilitate the reuse of frequently used instances,
+`types.Instantiate` accepts a `types.Context` as its first argument, which
+records instances.
+
+If the final `validate` argument to `types.Instantiate` is set, the provided
+type arguments will be verified against their corresponding type parameter
+constraint; i.e., `types.Instantiate` will check that each type arguments
+implements the corresponding type parameter constraint. If a type arguments
+does not implement the respective constraint, the resulting error will wrap
+a new `ArgumentError` type indicating which type argument index was bad.
+
+```
+func Instantiate(pkg *types.Package) error {
+ Pair := pkg.Scope().Lookup("Pair").Type()
+ X := pkg.Scope().Lookup("X").Type()
+ Y := pkg.Scope().Lookup("Y").Type()
+
+ // X and Y have different types, because their type arguments are different.
+ Compare("X", "Y", X, Y)
+
+ // Create a shared context for the subsequent instantiations.
+ ctxt := types.NewContext()
+
+ // Instantiating with [int, string] yields an instance that is identical (but
+ // not equal) to X.
+ Int, String := types.Typ[types.Int], types.Typ[types.String]
+ inst1, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("X", "inst1", X, inst1)
+
+ // Instantiating again returns the same exact instance, because of the shared
+ // Context.
+ inst2, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("inst1", "inst2", inst1, inst2)
+
+ // Instantiating with 'any' is an error, because any is not comparable.
+ Any := types.Universe.Lookup("any").Type()
+ _, err := types.Instantiate(ctxt, Pair, []types.Type{Int, Any}, true)
+ var argErr *types.ArgumentError
+ if errors.As(err, &argErr) {
+ fmt.Printf("Argument %d: %v\n", argErr.Index, argErr.Err)
+ }
+
+ return nil
+}
+
+func Compare(leftName, rightName string, left, right types.Type) {
+ fmt.Printf("Identical(%s, %s) : %t\n", leftName, rightName, types.Identical(left, right))
+ fmt.Printf("%s == %s : %t\n\n", leftName, rightName, left == right)
+}
+```
+
+Output:
+
+```
+Identical(p.Pair[int, string], p.Pair[string, int]) : false
+p.Pair[int, string] == p.Pair[string, int] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : false
+
+Identical(p.Pair[string, int], p.Pair[int, string]) : false
+p.Pair[string, int] == p.Pair[int, string] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : true
+
+Argument 1: any does not implement comparable
+```
+
+### Using a shared context while type checking
+
+To share a common `types.Context` argument with a type-checking pass, set the
+new `types.Config.Context` field.
+
+## Generic types continued: method sets and predicates
+
+Generic types are fundamentally different from ordinary types, in that they may
+not be used without instantiation. In some senses they are not really types:
+the go spec defines [types](https://tip.golang.org/ref/spec#Types) as "a set of
+values, together with operations and methods", but uninstantiated generic types
+do not define a set of values. Rather, they define a set of _types_. In that
+sense, they are a "meta type", or a "type template" (disclaimer: I am using
+these terms imprecisely).
+
+However, for the purposes of `go/types` it is convenient to treat generic types
+as a `types.Type`. This section explains how generic types behave in existing
+`go/types` APIs.
+
+### Method Sets
+
+Methods on uninstantiated generic types are different from methods on an
+ordinary type. Consider that for an ordinary type `T`, the receiver base type
+of each method in its method set is `T`. However, this can't be the case for
+a generic type: generic types cannot be used without instantation, and neither
+can the type of the receiver variable. Instead, the receiver base type is an
+_instantiated_ type, instantiated with the method's receiver type parameters.
+
+This has some surprising consequences, which we observed in the section on
+instantiation above: for a generic type `G`, each of its methods will define
+a unique instantiation of `G`, as each method has distinct receiver type
+parameters.
+
+To see this, consider the following example:
+
+```
+package p
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+var IntPair Pair[int, int]
+```
+
+Let's inspect the method sets of the types in this library:
+
+```
+func PrintMethods(pkg *types.Package) {
+ // Look up *Named types in the package scope.
+ lookup := func(name string) *types.Named {
+ return pkg.Scope().Lookup(name).Type().(*types.Named)
+ }
+
+ Pair := lookup("Pair")
+ IntPair := lookup("IntPair")
+ PrintMethodSet("Pair", Pair)
+ PrintMethodSet("Pair[int, int]", IntPair)
+ LeftObj, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftRecvType := LeftObj.Type().(*types.Signature).Recv().Type()
+ PrintMethodSet("Pair[L, _]", LeftRecvType)
+}
+
+func PrintMethodSet(name string, typ types.Type) {
+ fmt.Println(name + ":")
+ methodSet := types.NewMethodSet(typ)
+ for i := 0; i < methodSet.Len(); i++ {
+ method := methodSet.At(i).Obj()
+ fmt.Println(method)
+ }
+ fmt.Println()
+}
+```
+
+Output:
+
+```
+Pair:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[_, R]).Right() R
+
+Pair[int, int]:
+func (p.Pair[int, int]).Left() int
+func (p.Pair[int, int]).Right() int
+
+Pair[L, _]:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[L, _]).Right() _
+```
+
+In this example, we can see that all of `Pair`, `Pair[int, int]`, and
+`Pair[L, _]` have distinct method sets, though the method set of `Pair` and
+`Pair[L, _]` intersect in the `Left` method.
+
+Only the objects in `Pair`'s method set are recorded in `types.Info.Defs`. To
+get back to this "canonical" method object, the `typeparams` package provides
+the `OriginMethod` helper:
+
+```
+func CompareOrigins(pkg *types.Package) {
+ Pair := pkg.Scope().Lookup("Pair").Type().(*types.Named)
+ IntPair := pkg.Scope().Lookup("IntPair").Type().(*types.Named)
+ Left, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftInt, _, _ := types.LookupFieldOrMethod(IntPair, false, pkg, "Left")
+
+ fmt.Println("Pair.Left == Pair[int, int].Left:", Left == LeftInt)
+ origin := typeparams.OriginMethod(LeftInt.(*types.Func))
+ fmt.Println("Pair.Left == OriginMethod(Pair[int, int].Left):", Left == origin)
+}
+```
+
+Output:
+
+```
+Pair.Left == Pair[int, int].Left: false
+Pair.Left == OriginMethod(Pair[int, int].Left): true
+```
+
+### Predicates
+
+Predicates on generic types are not defined by the spec. As a consequence,
+using e.g. `types.AssignableTo` with operands of generic types leads to an
+undefined result.
+
+The behavior of predicates on generic `*types.Named` types may generally be
+derived from the fact that type parameters bound to different names are
+different types. This means that most predicates involving generic types will
+return `false`.
+
+`*types.Signature` types are treated differently. Two signatures are considered
+identical if they are identical after substituting one's set of type parameters
+for the other's, including having identical type parameter constraints. This is
+analogous to the treatment of ordinary value parameters, whose names do not
+affect type identity.
+
+Consider the following code:
+
+```
+func OrdinaryPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ Mer = pkg.Scope().Lookup("Mer").Type()
+ F = pkg.Scope().Lookup("F").Type()
+ G = pkg.Scope().Lookup("G").Type()
+ H = pkg.Scope().Lookup("H").Type()
+ )
+
+ fmt.Println("AssignableTo(Pair, LeftRighter)", types.AssignableTo(Pair, LeftRighter))
+ fmt.Println("AssignableTo(Pair, Mer): ", types.AssignableTo(Pair, Mer))
+ fmt.Println("Identical(F, G)", types.Identical(F, G))
+ fmt.Println("Identical(F, H)", types.Identical(F, H))
+}
+```
+
+Output:
+
+```
+AssignableTo(Pair, LeftRighter) false
+AssignableTo(Pair, Mer): true
+Identical(F, G) true
+Identical(F, H) false
+```
+
+In this example, we see that despite their similarity the generic `Pair` type
+is not assignable to the generic `LeftRighter` type. We also see the rules for
+signature identity in practice.
+
+This begs the question: how does one ask questions about the relationship
+between generic types? In order to phrase such questions we need more
+information: how does one relate the type parameters of `Pair` to the type
+parameters of `LeftRighter`? Does it suffice for the predicate to hold for one
+element of the type sets, or must it hold for all elements of the type sets?
+
+We can use instantiation to answer some of these questions. In particular, by
+instantiating both `Pair` and `LeftRighter` with the type parameters of `Pair`,
+we can determine if, for all type arguments `[X, Y]` that are valid for `Pair`,
+`[X, Y]` are also valid type arguments of `LeftRighter`, and `Pair[X, Y]` is
+assignable to `LeftRighter[X, Y]`. The `typeparams.GenericAssignableTo`
+function implements exactly this predicate:
+
+```
+func GenericPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ )
+ fmt.Println("GenericAssignableTo(Pair, LeftRighter)", typeparams.GenericAssignableTo(nil, Pair, LeftRighter))
+}
+```
+
+Output:
+
+```
+GenericAssignableTo(Pair, LeftRighter) true
+```
+
+# Updating tools while building at older Go versions
+
+In the examples above, we can see how a lot of the new APIs integrate with
+existing usage of `go/ast` or `go/types`. However, most tools still need to
+build at older Go versions, and handling the new language constructs in-line
+will break builds at older Go versions.
+
+For this purpose, the `x/exp/typeparams` package provides functions and types
+that proxy the new APIs (with stub implementations at older Go versions).
+
+# Further help
+
+If you're working on updating a tool to support generics, and need help, please
+feel free to reach out for help in any of the following ways:
+ - By mailing the [golang-tools](https://groups.google.com/g/golang-tools) mailing list.
+ - Directly to me via email (`rfindley@google.com`).
+ - For bugs, you can [file an issue](https://github.com/golang/go/issues/new/choose).
diff --git a/typeparams/example/findtypeparams/main.go b/typeparams/example/findtypeparams/main.go
new file mode 100644
index 0000000..2026ba2
--- /dev/null
+++ b/typeparams/example/findtypeparams/main.go
@@ -0,0 +1,156 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/importer"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const hello = `
+//!+input
+package main
+
+type Constraint interface {
+ Value() any
+}
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func MakePair[L, R Constraint](l L, r R) Pair[L, R] {
+ return Pair[L, R]{l, r}
+}
+//!-input
+`
+
+// !+print
+func PrintTypeParams(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{Importer: importer.Default()}
+ info := &types.Info{
+ Scopes: make(map[ast.Node]*types.Scope),
+ Defs: make(map[*ast.Ident]types.Object),
+ }
+ _, err := conf.Check("hello", fset, []*ast.File{file}, info)
+ if err != nil {
+ return err
+ }
+
+ // For convenience, we can use ast.Inspect to find the nodes we want to
+ // investigate.
+ ast.Inspect(file, func(n ast.Node) bool {
+ var name *ast.Ident // the name of the generic object, or nil
+ var tparamFields *ast.FieldList // the list of type parameter fields
+ var tparams *types.TypeParamList // the list of type parameter types
+ var scopeNode ast.Node // the node associated with the declaration scope
+
+ switch n := n.(type) {
+ case *ast.TypeSpec:
+ name = n.Name
+ tparamFields = n.TypeParams
+ tparams = info.Defs[name].Type().(*types.Named).TypeParams()
+ scopeNode = n
+ case *ast.FuncDecl:
+ name = n.Name
+ tparamFields = n.Type.TypeParams
+ tparams = info.Defs[name].Type().(*types.Signature).TypeParams()
+ scopeNode = n.Type
+ default:
+ // Not a type or function declaration.
+ return true
+ }
+
+ // Option 1: find type parameters by looking at their declaring field list.
+ if tparamFields != nil {
+ fmt.Printf("%s has a type parameter field list with %d fields\n", name.Name, tparamFields.NumFields())
+ for _, field := range tparamFields.List {
+ for _, name := range field.Names {
+ tparam := info.Defs[name]
+ fmt.Printf(" field %s defines an object %q\n", name.Name, tparam)
+ }
+ }
+ } else {
+ fmt.Printf("%s does not have a type parameter list\n", name.Name)
+ }
+
+ // Option 2: find type parameters via the TypeParams() method on the
+ // generic type.
+ if tparams.Len() > 0 {
+ fmt.Printf("%s has %d type parameters:\n", name.Name, tparams.Len())
+ for i := 0; i < tparams.Len(); i++ {
+ tparam := tparams.At(i)
+ fmt.Printf(" %s has constraint %s\n", tparam, tparam.Constraint())
+ }
+ } else {
+ fmt.Printf("%s does not have type parameters\n", name.Name)
+ }
+
+ // Option 3: find type parameters by looking in the declaration scope.
+ scope, ok := info.Scopes[scopeNode]
+ if ok {
+ fmt.Printf("%s has a scope with %d objects:\n", name.Name, scope.Len())
+ for _, name := range scope.Names() {
+ fmt.Printf(" %s is a %T\n", name, scope.Lookup(name))
+ }
+ } else {
+ fmt.Printf("%s does not have a scope\n", name.Name)
+ }
+
+ return true
+ })
+ return nil
+}
+
+//!-print
+
+/*
+//!+output
+> go run golang.org/x/tools/internal/typeparams/example/findtypeparams
+Constraint does not have a type parameter list
+Constraint does not have type parameters
+Constraint does not have a scope
+Pair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L any"
+ field R defines an object "type parameter R any"
+Pair has 2 type parameters:
+ L has constraint any
+ R has constraint any
+Pair has a scope with 2 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+MakePair has a type parameter field list with 2 fields
+ field L defines an object "type parameter L hello.Constraint"
+ field R defines an object "type parameter R hello.Constraint"
+MakePair has 2 type parameters:
+ L has constraint hello.Constraint
+ R has constraint hello.Constraint
+MakePair has a scope with 4 objects:
+ L is a *types.TypeName
+ R is a *types.TypeName
+ l is a *types.Var
+ r is a *types.Var
+//!-output
+*/
+
+func main() {
+ // Parse one file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", hello, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := PrintTypeParams(fset, f); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/generic-go-types.md b/typeparams/example/generic-go-types.md
new file mode 100644
index 0000000..797dcd4
--- /dev/null
+++ b/typeparams/example/generic-go-types.md
@@ -0,0 +1,448 @@
+<!-- To regenerate the readme, run: -->
+<!-- go run golang.org/x/example/gotypes@latest generic-go-types.md -->
+
+# Updating tools to support type parameters.
+
+This guide is maintained by Rob Findley (`rfindley@google.com`).
+
+**status**: this document is currently a rough-draft. See [golang/go#50447](https://go.dev/issues/50447) for more details.
+
+%toc
+
+# Who should read this guide
+
+Read this guide if you are a tool author seeking to update your tools to
+support generics Go code. Generics introduce significant new complexity to the
+Go type system, because types can now be _parameterized_. While the
+fundamentals of the `go/types` APIs remain the same, some previously valid
+assumptions no longer hold. For example:
+
+ - Type declarations need not correspond 1:1 with the types they define.
+ - Interfaces are no longer determined entirely by their method set.
+ - The set of concrete types implementing `types.Type` has grown to include
+ `types.TypeParam` and `types.Union`.
+
+# Introduction
+
+With Go 1.18, Go now supports generic programming via type parameters. This
+document is a guide for tool authors that want to update their tools to support
+the new language constructs.
+
+This guide assumes knowledge of the language changes to support generics. See
+the following references for more information:
+
+- The [original proposal](https://go.dev/issue/43651) for type parameters.
+- The [addendum for type sets](https://go.dev/issue/45346).
+- The [latest language specfication](https://tip.golang.org/ref/spec) (still in-progress as of 2021-01-11).
+- The proposals for new APIs in
+ [go/token and go/ast](https://go.dev/issue/47781), and in
+ [go/types](https://go.dev/issue/47916).
+
+It also assumes knowledge of `go/ast` and `go/types`. If you're just getting
+started,
+[x/example/gotypes](https://github.com/golang/example/tree/master/gotypes) is
+a great introduction (and was the inspiration for this guide).
+
+# Summary of new language features and their APIs
+
+The introduction of of generic features appears as a large change to the
+language, but a high level introduces only a few new concepts. We can break
+down our discussion into the following three broad categories: generic types,
+constraint interfaces, and instantiation. In each category below, the relevant
+new APIs are listed (some constructors and getters/setters may be elided where
+they are trivial):
+
+**Generic types**. Types and functions may be _generic_, meaning their
+declaration may have a non-empty _type parameter list_, as in
+`type List[T any] ...` or `func f[T1, T2 any]() { ... }`. Type parameter lists
+define placeholder types (_type parameters_), scoped to the declaration, which
+may be substituted by any type satisfying their corresponding _constraint
+interface_ to _instantiate_ a new type or function.
+
+Generic types may have methods, which declare `receiver type parameters` via
+their receiver type expression: `func (r T[P1, ..., PN]) method(...) (...)
+{...}`.
+
+_New APIs_:
+ - The field `ast.TypeSpec.TypeParams` holds the type parameter list syntax for
+ type declarations.
+ - The field `ast.FuncType.TypeParams` holds the type parameter list syntax for
+ function declarations.
+ - The type `types.TypeParam` is a `types.Type` representing a type parameter.
+ On this type, the `Constraint` and `SetConstraint` methods allow
+ getting/setting the constraint, the `Index` method returns the numeric index
+ of the type parameter in the type parameter list that declares it, and the
+ `Obj` method returns the object in the scope a for the type parameter (a
+ `types.TypeName`). Generic type declarations have a new `*types.Scope` for
+ type parameter declarations.
+ - The type `types.TypeParamList` holds a list of type parameters.
+ - The method `types.Named.TypeParams` returns the type parameters for a type
+ declaration.
+ - The method `types.Named.SetTypeParams` sets type parameters on a defined
+ type.
+ - The function `types.NewSignatureType` creates a new (possibly generic)
+ signature type.
+ - The method `types.Signature.RecvTypeParams` returns the receiver type
+ parameters for a method.
+ - The method `types.Signature.TypeParams` returns the type parameters for
+ a function.
+
+**Constraint Interfaces**: type parameter constraints are interfaces, expressed
+by an interface type expression. Interfaces that are only used in constraint
+position are permitted new embedded elements composed of tilde expressions
+(`~T`) and unions (`A | B | ~C`). The new builtin interface type `comparable`
+is implemented by types for which `==` and `!=` are valid (note that interfaces
+must be statically comparable in this case, i.e., each type in the interface's
+type set must be comparable). As a special case, the `interface` keyword may be
+omitted from constraint expressions if it may be implied (in which case we say
+the interface is _implicit_).
+
+_New APIs_:
+ - The constant `token.TILDE` is used to represent tilde expressions as an
+ `ast.UnaryExpr`.
+ - Union expressions are represented as an `ast.BinaryExpr` using `|`. This
+ means that `ast.BinaryExpr` may now be both a type and value expression.
+ - The method `types.Interface.IsImplicit` reports whether the `interface`
+ keyword was elided from this interface.
+ - The method `types.Interface.MarkImplicit` marks an interface as being
+ implicit.
+ - The method `types.Interface.IsComparable` reports whether every type in an
+ interface's type set is comparable.
+ - The method `types.Interface.IsMethodSet` reports whether an interface is
+ defined entirely by its methods (has no _specific types_).
+ - The type `types.Union` is a type that represents an embedded union
+ expression in an interface. May only appear as an embedded element in
+ interfaces.
+ - The type `types.Term` represents a (possibly tilde) term of a union.
+
+**Instantiation**: generic types and functions may be _instantiated_ to create
+non-generic types and functions by providing _type arguments_ (`var x T[int]`).
+Function type arguments may be _inferred_ via function arguments, or via
+type parameter constraints.
+
+_New APIs_:
+ - The type `ast.IndexListExpr` holds index expressions with multiple indices,
+ as in instantiation expressions with multiple type arguments or in receivers
+ declaring multiple type parameters.
+ - The function `types.Instantiate` instantiates a generic type with type arguments.
+ - The type `types.Context` is an opaque instantiation context that may be
+ shared to reduce duplicate instances.
+ - The field `types.Config.Context` holds a shared `Context` to use for
+ instantiation while type-checking.
+ - The type `types.TypeList` holds a list of types.
+ - The type `types.ArgumentError` holds an error associated with a specific
+ type argument index. Used to represent instantiation errors.
+ - The field `types.Info.Instances` maps instantiated identifiers to information
+ about the resulting type instance.
+ - The type `types.Instance` holds information about a type or function
+ instance.
+ - The method `types.Named.TypeArgs` reports the type arguments used to
+ instantiate a named type.
+
+# Examples
+
+The following examples demonstrate the new APIs, and discuss their properties.
+All examples are runnable, contained in subdirectories of the directory holding
+this README.
+
+## Generic types: type parameters
+
+We say that a type is _generic_ if it has type parameters but no type
+arguments. This section explains how we can inspect generic types with the new
+`go/types` APIs.
+
+### Type parameter lists
+
+Suppose we want to understand the generic library below, which defines a generic
+`Pair`, a constraint interface `Constraint`, and a generic function `MakePair`.
+
+%include findtypeparams/main.go input -
+
+We can use the new `TypeParams` fields in `ast.TypeSpec` and `ast.FuncType` to
+access the type parameter list. From there, we can access type parameter types
+in at least three ways:
+ - by looking up type parameter definitions in `types.Info`
+ - by calling `TypeParams()` on `types.Named` or `types.Signature`
+ - by looking up type parameter objects in the declaration scope. Note that
+ there now may be a scope associated with an `ast.TypeSpec` node.
+
+%include findtypeparams/main.go print -
+
+This program produces the following output. Note that not every type spec has
+a scope.
+
+%include findtypeparams/main.go output -
+
+## Constraint Interfaces
+
+In order to allow operations on type parameters, Go 1.18 introduces the notion
+of [_type sets_](https://tip.golang.org/ref/spec#Interface_types), which is
+abstractly the set of types that implement an interface. This section discusses
+the new syntax for restrictions on interface type sets, and the APIs we can use
+to understand them.
+
+### New interface elements
+
+Consider the generic library below:
+
+%include interfaces/main.go input -
+
+In this library, we can see a few new features added in Go 1.18. The first is
+the new syntax in the `Numeric` type: unions of tilde-terms, specifying that
+the numeric type may only be satisfied by types whose underlying type is `int`
+or `float64`.
+
+The `go/ast` package parses this new syntax as a combination of unary and
+binary expressions, which we can see using the following program:
+
+%include interfaces/main.go printsyntax -
+
+Output:
+
+%include interfaces/main.go outputsyntax -
+
+Once type-checked, these embedded expressions are represented using the new
+`types.Union` type, which flattens the expression into a list of `*types.Term`.
+We can also investigate two new methods of interface:
+`types.Interface.IsComparable`, which reports whether the type set of an
+interface is comparable, and `types.Interface.IsMethodSet`, which reports
+whether an interface is expressable using methods alone.
+
+%include interfaces/main.go printtypes -
+
+Output:
+
+%include interfaces/main.go outputtypes -
+
+The `Findable` type demonstrates another new feature of Go 1.18: the comparable
+built-in. Comparable is a special interface type, not expressable using
+ordinary Go syntax, whose type-set consists of all comparable types.
+
+### Implicit interfaces
+
+For interfaces that do not have methods, we can inline them in constraints and
+elide the `interface` keyword. In the example above, we could have done this
+for the `Square` function:
+
+%include implicit/main.go input -
+
+In such cases, the `types.Interface.IsImplicit` method reports whether the
+interface type was implicit. This does not affect the behavior of the
+interface, but is captured for more accurate type strings:
+
+%include implicit/main.go show -
+
+Output:
+
+%include implicit/main.go output -
+
+The `types.Interface.MarkImplicit` method is used to mark interfaces as
+implicit by the importer.
+
+### Type sets
+
+The examples above demonstrate the new APIs for _accessing_ information about
+the new interface elements, but how do we understand
+[_type sets_](https://tip.golang.org/ref/spec#Interface_types), the new
+abstraction that these elements help define? Type sets may be arbitrarily
+complex, as in the following example:
+
+%include typesets/main.go input -
+
+Here, the type set of `D` simplifies to `~string|int`, but the current
+`go/types` APIs do not expose this information. This will likely be added to
+`go/types` in future versions of Go, but in the meantime we can use the
+`typeparams.NormalTerms` helper:
+
+%include typesets/main.go print -
+
+which outputs:
+
+%include typesets/main.go output -
+
+See the documentation for `typeparams.NormalTerms` for more information on how
+this calculation proceeds.
+
+## Instantiation
+
+We say that a type is _instantiated_ if it is created from a generic type by
+substituting type arguments for type parameters. Instantiation can occur via
+explicitly provided type arguments, as in the expression `T[A_1, ..., A_n]`, or
+implicitly, through type inference.. This section describes how to find and
+understand instantiated types.
+
+### Finding instantiated types
+
+Certain applications may find it useful to locate all instantiated types in
+a package. For this purpose, `go/types` provides a new `types.Info.Instances`
+field that maps instantiated identifiers to information about their instance.
+
+For example, consider the following code:
+
+%include instantiation/main.go input -
+
+We can find instances by type-checking with the `types.Info.Instances` map
+initialized:
+
+%include instantiation/main.go check -
+
+Output:
+
+%include instantiation/main.go checkoutput -
+
+The `types.Instance` type provides information about the (possibly inferred)
+type arguments that were used to instantiate the generic type, and the
+resulting type. Notably, it does not include the _generic_ type that was
+instantiated, because this type can be found using `types.Info.Uses[id].Type()`
+(where `id` is the identifier node being instantiated).
+
+Note that the receiver type of method `Left` also appears in the `Instances`
+map. This may be counterintuitive -- more on this below.
+
+### Creating new instantiated types
+
+`go/types` also provides an API for creating type instances:
+`types.Instantiate`. This function accepts a generic type and type arguments,
+and returns an instantiated type (or an error). The resulting instance may be
+a newly constructed type, or a previously created instance with the same type
+identity. To facilitate the reuse of frequently used instances,
+`types.Instantiate` accepts a `types.Context` as its first argument, which
+records instances.
+
+If the final `validate` argument to `types.Instantiate` is set, the provided
+type arguments will be verified against their corresponding type parameter
+constraint; i.e., `types.Instantiate` will check that each type arguments
+implements the corresponding type parameter constraint. If a type arguments
+does not implement the respective constraint, the resulting error will wrap
+a new `ArgumentError` type indicating which type argument index was bad.
+
+%include instantiation/main.go instantiate -
+
+Output:
+
+%include instantiation/main.go instantiateoutput -
+
+### Using a shared context while type checking
+
+To share a common `types.Context` argument with a type-checking pass, set the
+new `types.Config.Context` field.
+
+## Generic types continued: method sets and predicates
+
+Generic types are fundamentally different from ordinary types, in that they may
+not be used without instantiation. In some senses they are not really types:
+the go spec defines [types](https://tip.golang.org/ref/spec#Types) as "a set of
+values, together with operations and methods", but uninstantiated generic types
+do not define a set of values. Rather, they define a set of _types_. In that
+sense, they are a "meta type", or a "type template" (disclaimer: I am using
+these terms imprecisely).
+
+However, for the purposes of `go/types` it is convenient to treat generic types
+as a `types.Type`. This section explains how generic types behave in existing
+`go/types` APIs.
+
+### Method Sets
+
+Methods on uninstantiated generic types are different from methods on an
+ordinary type. Consider that for an ordinary type `T`, the receiver base type
+of each method in its method set is `T`. However, this can't be the case for
+a generic type: generic types cannot be used without instantation, and neither
+can the type of the receiver variable. Instead, the receiver base type is an
+_instantiated_ type, instantiated with the method's receiver type parameters.
+
+This has some surprising consequences, which we observed in the section on
+instantiation above: for a generic type `G`, each of its methods will define
+a unique instantiation of `G`, as each method has distinct receiver type
+parameters.
+
+To see this, consider the following example:
+
+%include genericmethods/main.go input -
+
+Let's inspect the method sets of the types in this library:
+
+%include genericmethods/main.go printmethods -
+
+Output:
+
+%include genericmethods/main.go printoutput -
+
+In this example, we can see that all of `Pair`, `Pair[int, int]`, and
+`Pair[L, _]` have distinct method sets, though the method set of `Pair` and
+`Pair[L, _]` intersect in the `Left` method.
+
+Only the objects in `Pair`'s method set are recorded in `types.Info.Defs`. To
+get back to this "canonical" method object, the `typeparams` package provides
+the `OriginMethod` helper:
+
+%include genericmethods/main.go compareorigins -
+
+Output:
+
+%include genericmethods/main.go compareoutput -
+
+### Predicates
+
+Predicates on generic types are not defined by the spec. As a consequence,
+using e.g. `types.AssignableTo` with operands of generic types leads to an
+undefined result.
+
+The behavior of predicates on generic `*types.Named` types may generally be
+derived from the fact that type parameters bound to different names are
+different types. This means that most predicates involving generic types will
+return `false`.
+
+`*types.Signature` types are treated differently. Two signatures are considered
+identical if they are identical after substituting one's set of type parameters
+for the other's, including having identical type parameter constraints. This is
+analogous to the treatment of ordinary value parameters, whose names do not
+affect type identity.
+
+Consider the following code:
+
+%include predicates/main.go ordinary -
+
+Output:
+
+%include predicates/main.go ordinaryoutput -
+
+In this example, we see that despite their similarity the generic `Pair` type
+is not assignable to the generic `LeftRighter` type. We also see the rules for
+signature identity in practice.
+
+This begs the question: how does one ask questions about the relationship
+between generic types? In order to phrase such questions we need more
+information: how does one relate the type parameters of `Pair` to the type
+parameters of `LeftRighter`? Does it suffice for the predicate to hold for one
+element of the type sets, or must it hold for all elements of the type sets?
+
+We can use instantiation to answer some of these questions. In particular, by
+instantiating both `Pair` and `LeftRighter` with the type parameters of `Pair`,
+we can determine if, for all type arguments `[X, Y]` that are valid for `Pair`,
+`[X, Y]` are also valid type arguments of `LeftRighter`, and `Pair[X, Y]` is
+assignable to `LeftRighter[X, Y]`. The `typeparams.GenericAssignableTo`
+function implements exactly this predicate:
+
+%include predicates/main.go generic -
+
+Output:
+
+%include predicates/main.go genericoutput -
+
+# Updating tools while building at older Go versions
+
+In the examples above, we can see how a lot of the new APIs integrate with
+existing usage of `go/ast` or `go/types`. However, most tools still need to
+build at older Go versions, and handling the new language constructs in-line
+will break builds at older Go versions.
+
+For this purpose, the `x/exp/typeparams` package provides functions and types
+that proxy the new APIs (with stub implementations at older Go versions).
+
+# Further help
+
+If you're working on updating a tool to support generics, and need help, please
+feel free to reach out for help in any of the following ways:
+ - By mailing the [golang-tools](https://groups.google.com/g/golang-tools) mailing list.
+ - Directly to me via email (`rfindley@google.com`).
+ - For bugs, you can [file an issue](https://github.com/golang/go/issues/new/choose).
diff --git a/typeparams/example/genericmethods/main.go b/typeparams/example/genericmethods/main.go
new file mode 100644
index 0000000..6aa5e7a
--- /dev/null
+++ b/typeparams/example/genericmethods/main.go
@@ -0,0 +1,122 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+
+ "golang.org/x/exp/typeparams"
+)
+
+const src = `
+//!+input
+package p
+
+type Pair[L, R any] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+var IntPair Pair[int, int]
+//!-input
+`
+
+// !+printmethods
+func PrintMethods(pkg *types.Package) {
+ // Look up *Named types in the package scope.
+ lookup := func(name string) *types.Named {
+ return pkg.Scope().Lookup(name).Type().(*types.Named)
+ }
+
+ Pair := lookup("Pair")
+ IntPair := lookup("IntPair")
+ PrintMethodSet("Pair", Pair)
+ PrintMethodSet("Pair[int, int]", IntPair)
+ LeftObj, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftRecvType := LeftObj.Type().(*types.Signature).Recv().Type()
+ PrintMethodSet("Pair[L, _]", LeftRecvType)
+}
+
+func PrintMethodSet(name string, typ types.Type) {
+ fmt.Println(name + ":")
+ methodSet := types.NewMethodSet(typ)
+ for i := 0; i < methodSet.Len(); i++ {
+ method := methodSet.At(i).Obj()
+ fmt.Println(method)
+ }
+ fmt.Println()
+}
+
+//!-printmethods
+
+/*
+//!+printoutput
+Pair:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[_, R]).Right() R
+
+Pair[int, int]:
+func (p.Pair[int, int]).Left() int
+func (p.Pair[int, int]).Right() int
+
+Pair[L, _]:
+func (p.Pair[L, _]).Left() L
+func (p.Pair[L, _]).Right() _
+
+//!-printoutput
+*/
+
+// !+compareorigins
+func CompareOrigins(pkg *types.Package) {
+ Pair := pkg.Scope().Lookup("Pair").Type().(*types.Named)
+ IntPair := pkg.Scope().Lookup("IntPair").Type().(*types.Named)
+ Left, _, _ := types.LookupFieldOrMethod(Pair, false, pkg, "Left")
+ LeftInt, _, _ := types.LookupFieldOrMethod(IntPair, false, pkg, "Left")
+
+ fmt.Println("Pair.Left == Pair[int, int].Left:", Left == LeftInt)
+ origin := typeparams.OriginMethod(LeftInt.(*types.Func))
+ fmt.Println("Pair.Left == OriginMethod(Pair[int, int].Left):", Left == origin)
+}
+
+//!-compareorigins
+
+/*
+//!+compareoutput
+Pair.Left == Pair[int, int].Left: false
+Pair.Left == OriginMethod(Pair[int, int].Left): true
+//!-compareoutput
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== PrintMethods ===")
+ PrintMethods(pkg)
+ fmt.Println("=== CompareOrigins ===")
+ CompareOrigins(pkg)
+}
diff --git a/typeparams/example/implicit/main.go b/typeparams/example/implicit/main.go
new file mode 100644
index 0000000..996ad2c
--- /dev/null
+++ b/typeparams/example/implicit/main.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const src = `
+//!+input
+package p
+
+func Square[N ~int|~float64](n N) N {
+ return n*n
+}
+//!-input
+`
+
+// !+show
+func ShowImplicit(pkg *types.Package) {
+ Square := pkg.Scope().Lookup("Square").Type().(*types.Signature)
+ N := Square.TypeParams().At(0)
+ constraint := N.Constraint().(*types.Interface)
+ fmt.Println(constraint)
+ fmt.Println("IsImplicit:", constraint.IsImplicit())
+}
+
+//!-show
+
+/*
+//!+output
+~int|~float64
+IsImplicit: true
+//!-output
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("typesets", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ ShowImplicit(pkg)
+}
diff --git a/typeparams/example/instantiation/main.go b/typeparams/example/instantiation/main.go
new file mode 100644
index 0000000..ed53667
--- /dev/null
+++ b/typeparams/example/instantiation/main.go
@@ -0,0 +1,158 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const src = `
+//!+input
+package p
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func Equal[L, R comparable](x, y Pair[L, R]) bool {
+ return x.left == y.left && x.right == y.right
+}
+
+var X Pair[int, string]
+var Y Pair[string, int]
+
+var E = Equal[int, string]
+//!-input
+`
+
+// !+check
+func CheckInstances(fset *token.FileSet, file *ast.File) (*types.Package, error) {
+ conf := types.Config{}
+ info := &types.Info{
+ Instances: make(map[*ast.Ident]types.Instance),
+ }
+ pkg, err := conf.Check("p", fset, []*ast.File{file}, info)
+ for id, inst := range info.Instances {
+ posn := fset.Position(id.Pos())
+ fmt.Printf("%s: %s instantiated with %s: %s\n", posn, id.Name, FormatTypeList(inst.TypeArgs), inst.Type)
+ }
+ return pkg, err
+}
+
+//!-check
+
+/*
+//!+checkoutput
+hello.go:21:9: Equal instantiated with [int, string]: func(x p.Pair[int, string], y p.Pair[int, string]) bool
+hello.go:10:9: Pair instantiated with [L, _]: p.Pair[L, _]
+hello.go:14:34: Pair instantiated with [L, R]: p.Pair[L, R]
+hello.go:18:7: Pair instantiated with [int, string]: p.Pair[int, string]
+hello.go:19:7: Pair instantiated with [string, int]: p.Pair[string, int]
+
+//!-checkoutput
+*/
+
+func FormatTypeList(list *types.TypeList) string {
+ var buf bytes.Buffer
+ buf.WriteRune('[')
+ for i := 0; i < list.Len(); i++ {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(list.At(i).String())
+ }
+ buf.WriteRune(']')
+ return buf.String()
+}
+
+// !+instantiate
+func Instantiate(pkg *types.Package) error {
+ Pair := pkg.Scope().Lookup("Pair").Type()
+ X := pkg.Scope().Lookup("X").Type()
+ Y := pkg.Scope().Lookup("Y").Type()
+
+ // X and Y have different types, because their type arguments are different.
+ Compare("X", "Y", X, Y)
+
+ // Create a shared context for the subsequent instantiations.
+ ctxt := types.NewContext()
+
+ // Instantiating with [int, string] yields an instance that is identical (but
+ // not equal) to X.
+ Int, String := types.Typ[types.Int], types.Typ[types.String]
+ inst1, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("X", "inst1", X, inst1)
+
+ // Instantiating again returns the same exact instance, because of the shared
+ // Context.
+ inst2, _ := types.Instantiate(ctxt, Pair, []types.Type{Int, String}, true)
+ Compare("inst1", "inst2", inst1, inst2)
+
+ // Instantiating with 'any' is an error, because any is not comparable.
+ Any := types.Universe.Lookup("any").Type()
+ _, err := types.Instantiate(ctxt, Pair, []types.Type{Int, Any}, true)
+ var argErr *types.ArgumentError
+ if errors.As(err, &argErr) {
+ fmt.Printf("Argument %d: %v\n", argErr.Index, argErr.Err)
+ }
+
+ return nil
+}
+
+func Compare(leftName, rightName string, left, right types.Type) {
+ fmt.Printf("Identical(%s, %s) : %t\n", leftName, rightName, types.Identical(left, right))
+ fmt.Printf("%s == %s : %t\n\n", leftName, rightName, left == right)
+}
+
+//!-instantiate
+
+/*
+//!+instantiateoutput
+Identical(p.Pair[int, string], p.Pair[string, int]) : false
+p.Pair[int, string] == p.Pair[string, int] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : false
+
+Identical(p.Pair[string, int], p.Pair[int, string]) : false
+p.Pair[string, int] == p.Pair[int, string] : false
+
+Identical(p.Pair[int, string], p.Pair[int, string]) : true
+p.Pair[int, string] == p.Pair[int, string] : true
+
+Argument 1: any does not implement comparable
+//!-instantiateoutput
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== CheckInstances ===")
+ pkg, err := CheckInstances(fset, f)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== Instantiate ===")
+ if err := Instantiate(pkg); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/interfaces/main.go b/typeparams/example/interfaces/main.go
new file mode 100644
index 0000000..6a0d627
--- /dev/null
+++ b/typeparams/example/interfaces/main.go
@@ -0,0 +1,138 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const src = `
+//!+input
+package p
+
+type Numeric interface{
+ ~int | ~float64 // etc...
+}
+
+func Square[N Numeric](n N) N {
+ return n*n
+}
+
+type Findable interface {
+ comparable
+}
+
+func Find[T Findable](s []T, v T) int {
+ for i, v2 := range s {
+ if v2 == v {
+ return i
+ }
+ }
+ return -1
+}
+//!-input
+`
+
+// !+printsyntax
+func PrintNumericSyntax(fset *token.FileSet, file *ast.File) {
+ // node is the AST node corresponding to the declaration for "Numeric."
+ node := file.Scope.Lookup("Numeric").Decl.(*ast.TypeSpec)
+ // Find the embedded syntax node.
+ embedded := node.Type.(*ast.InterfaceType).Methods.List[0].Type
+ // Use go/ast's built-in Print function to inspect the parsed syntax.
+ ast.Print(fset, embedded)
+}
+
+//!-printsyntax
+
+/*
+//!+outputsyntax
+ 0 *ast.BinaryExpr {
+ 1 . X: *ast.UnaryExpr {
+ 2 . . OpPos: p.go:6:2
+ 3 . . Op: ~
+ 4 . . X: *ast.Ident {
+ 5 . . . NamePos: p.go:6:3
+ 6 . . . Name: "int"
+ 7 . . }
+ 8 . }
+ 9 . OpPos: p.go:6:7
+ 10 . Op: |
+ 11 . Y: *ast.UnaryExpr {
+ 12 . . OpPos: p.go:6:9
+ 13 . . Op: ~
+ 14 . . X: *ast.Ident {
+ 15 . . . NamePos: p.go:6:10
+ 16 . . . Name: "float64"
+ 17 . . }
+ 18 . }
+ 19 }
+//!-outputsyntax
+*/
+
+// !+printtypes
+func PrintInterfaceTypes(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{}
+ pkg, err := conf.Check("hello", fset, []*ast.File{file}, nil)
+ if err != nil {
+ return err
+ }
+
+ PrintIface(pkg, "Numeric")
+ PrintIface(pkg, "Findable")
+
+ return nil
+}
+
+func PrintIface(pkg *types.Package, name string) {
+ obj := pkg.Scope().Lookup(name)
+ fmt.Println(obj)
+ iface := obj.Type().Underlying().(*types.Interface)
+ for i := 0; i < iface.NumEmbeddeds(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ for i := 0; i < iface.NumMethods(); i++ {
+ fmt.Printf("\tembeded: %s\n", iface.EmbeddedType(i))
+ }
+ fmt.Printf("\tIsComparable(): %t\n", iface.IsComparable())
+ fmt.Printf("\tIsMethodSet(): %t\n", iface.IsMethodSet())
+}
+
+//!-printtypes
+
+/*
+//!+outputtypes
+type hello.Numeric interface{~int|~float64}
+ embeded: ~int|~float64
+ IsComparable(): true
+ IsMethodSet(): false
+type hello.Findable interface{comparable}
+ embeded: comparable
+ IsComparable(): true
+ IsMethodSet(): false
+//!-outputtypes
+*/
+
+func main() {
+ // Parse one file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== PrintNumericSyntax ==")
+ PrintNumericSyntax(fset, f)
+ fmt.Println("=== PrintInterfaceTypes ==")
+ if err := PrintInterfaceTypes(fset, f); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/methoddecls/main.go b/typeparams/example/methoddecls/main.go
new file mode 100644
index 0000000..52ae8be
--- /dev/null
+++ b/typeparams/example/methoddecls/main.go
@@ -0,0 +1,137 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/importer"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+)
+
+const methods = `
+//!+input
+package methods
+
+type List[E any] []E
+
+func (l List[_]) Len() int {
+ return len(l)
+}
+
+func (l *List[E]) Append(v E) {
+ *l = append(*l, v)
+}
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+func (p Pair[L, R]) Equal(other Pair[L, R]) bool {
+ return p.Left() == other.Left() && p.Right() == other.Right()
+}
+//!-input
+`
+
+// !+describe
+func Describe(fset *token.FileSet, file *ast.File) error {
+ conf := types.Config{Importer: importer.Default()}
+ info := &types.Info{
+ Defs: make(map[*ast.Ident]types.Object),
+ }
+ pkg, err := conf.Check("pair", fset, []*ast.File{file}, info)
+ if err != nil {
+ return err
+ }
+ for _, name := range pkg.Scope().Names() {
+ obj := pkg.Scope().Lookup(name)
+ typ := obj.Type().(*types.Named)
+
+ fmt.Printf("type %s has %d methods\n", name, typ.NumMethods())
+ for i := 0; i < typ.NumMethods(); i++ {
+ m := typ.Method(i)
+ sig := m.Type().(*types.Signature)
+ recv := sig.Recv().Type()
+ fmt.Printf(" %s has %d receiver type parameters\n", m.Name(), sig.RecvTypeParams().Len())
+ fmt.Printf(" ...and receiver type %+v\n", recv)
+ }
+ }
+ // }
+
+ // func foo() {}
+ for _, decl := range file.Decls {
+ fdecl, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ fmt.Printf("Declaration of %q has receiver node type %T\n", fdecl.Name, fdecl.Recv.List[0].Type)
+ declObj := info.Defs[fdecl.Name]
+ recvIdent := receiverTypeName(fdecl.Recv.List[0].Type)
+ typ := pkg.Scope().Lookup(recvIdent.Name).Type().(*types.Named)
+ // ptr := types.NewPointer(typ)
+ name := fdecl.Name.Name
+ methodObj, _, _ := types.LookupFieldOrMethod(typ, false, pkg, name)
+ if declObj == methodObj {
+ fmt.Printf(" info.Uses[%s] == types.LookupFieldOrMethod(%s, ..., %q)\n", fdecl.Name, typ, name)
+ }
+ }
+ return nil
+}
+
+func receiverTypeName(e ast.Expr) *ast.Ident {
+ if s, ok := e.(*ast.StarExpr); ok {
+ e = s.X
+ }
+ switch e := e.(type) {
+ case *ast.IndexExpr:
+ return e.X.(*ast.Ident)
+ case *ast.IndexListExpr:
+ return e.X.(*ast.Ident)
+ }
+ panic("unexpected receiver node type")
+}
+
+//!-describe
+
+/*
+//!+output
+> go run golang.org/x/tools/internal/typeparams/example/methoddecls
+Pair has 2 methods
+ Left has 2 receiver type parameters
+ ...and receiver type pair.Pair[L, _]
+ Right has 2 receiver type parameters
+ ...and receiver type pair.Pair[_, R]
+Declaration of "Left" has receiver node type *ast.IndexListExpr
+ info.Uses[Left] == types.LookupFieldOrMethod(pair.Pair[L, R any], ..., "Left")
+Declaration of "Right" has receiver node type *ast.IndexListExpr
+ info.Uses[Right] == types.LookupFieldOrMethod(pair.Pair[L, R any], ..., "Right")
+//!-output
+*/
+
+func main() {
+ // Parse one file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "methods.go", methods, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := Describe(fset, f); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/example/predicates/main.go b/typeparams/example/predicates/main.go
new file mode 100644
index 0000000..5237c06
--- /dev/null
+++ b/typeparams/example/predicates/main.go
@@ -0,0 +1,114 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+
+ "golang.org/x/exp/typeparams"
+)
+
+const src = `
+//!+input
+package p
+
+type Pair[L, R comparable] struct {
+ left L
+ right R
+}
+
+func (p Pair[L, _]) Left() L {
+ return p.left
+}
+
+func (p Pair[_, R]) Right() R {
+ return p.right
+}
+
+// M does not use type parameters, and therefore implements the Mer interface.
+func (p Pair[_, _]) M() int { return 0 }
+
+type LeftRighter[L, R comparable] interface {
+ Left() L
+ Right() R
+}
+
+type Mer interface {
+ M() int
+}
+
+// F and G have identical signatures "modulo renaming", H does not.
+func F[P any](P) int { return 0 }
+func G[Q any](Q) int { return 1 }
+func H[R ~int](R) int { return 2 }
+//!-input
+`
+
+// !+ordinary
+func OrdinaryPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ Mer = pkg.Scope().Lookup("Mer").Type()
+ F = pkg.Scope().Lookup("F").Type()
+ G = pkg.Scope().Lookup("G").Type()
+ H = pkg.Scope().Lookup("H").Type()
+ )
+
+ fmt.Println("AssignableTo(Pair, LeftRighter)", types.AssignableTo(Pair, LeftRighter))
+ fmt.Println("AssignableTo(Pair, Mer): ", types.AssignableTo(Pair, Mer))
+ fmt.Println("Identical(F, G)", types.Identical(F, G))
+ fmt.Println("Identical(F, H)", types.Identical(F, H))
+}
+
+//!-ordinary
+
+/*
+//!+ordinaryoutput
+AssignableTo(Pair, LeftRighter) false
+AssignableTo(Pair, Mer): true
+Identical(F, G) true
+Identical(F, H) false
+//!-ordinaryoutput
+*/
+
+// !+generic
+func GenericPredicates(pkg *types.Package) {
+ var (
+ Pair = pkg.Scope().Lookup("Pair").Type()
+ LeftRighter = pkg.Scope().Lookup("LeftRighter").Type()
+ )
+ fmt.Println("GenericAssignableTo(Pair, LeftRighter)", typeparams.GenericAssignableTo(nil, Pair, LeftRighter))
+}
+
+//!-generic
+
+/*
+//!+genericoutput
+GenericAssignableTo(Pair, LeftRighter) true
+//!-genericoutput
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "hello.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("=== ordinary ===")
+ OrdinaryPredicates(pkg)
+ fmt.Println("=== generic ===")
+ GenericPredicates(pkg)
+}
diff --git a/typeparams/example/typesets/main.go b/typeparams/example/typesets/main.go
new file mode 100644
index 0000000..05da8be
--- /dev/null
+++ b/typeparams/example/typesets/main.go
@@ -0,0 +1,71 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "log"
+
+ "golang.org/x/exp/typeparams"
+)
+
+const src = `
+//!+input
+package complex
+
+type A interface{ ~string|~[]byte }
+
+type B interface{ int|string }
+
+type C interface { ~string|~int }
+
+type D interface{ A|B; C }
+//!-input
+`
+
+// !+print
+func PrintNormalTerms(pkg *types.Package) error {
+ D := pkg.Scope().Lookup("D").Type()
+ terms, err := typeparams.NormalTerms(D)
+ if err != nil {
+ return err
+ }
+ for i, term := range terms {
+ if i > 0 {
+ fmt.Print("|")
+ }
+ fmt.Print(term)
+ }
+ fmt.Println()
+ return nil
+}
+
+//!-print
+
+/*
+//!+output
+~string|int
+//!-output
+*/
+
+func main() {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", src, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ conf := types.Config{}
+ pkg, err := conf.Check("typesets", fset, []*ast.File{f}, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := PrintNormalTerms(pkg); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/typeparams/go.mod b/typeparams/go.mod
new file mode 100644
index 0000000..b1224b1
--- /dev/null
+++ b/typeparams/go.mod
@@ -0,0 +1,3 @@
+module golang.org/x/exp/typeparams
+
+go 1.18
diff --git a/typeparams/normalize.go b/typeparams/normalize.go
new file mode 100644
index 0000000..6cf71f0
--- /dev/null
+++ b/typeparams/normalize.go
@@ -0,0 +1,200 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package typeparams
+
+import (
+ "errors"
+ "fmt"
+ "go/types"
+ "os"
+ "strings"
+)
+
+const debug = false
+
+// ErrEmptyTypeSet is returned if a type set computation results in a type set
+// with no types.
+var ErrEmptyTypeSet = errors.New("empty type set")
+
+// NormalTerms returns a slice of terms representing the normalized structural
+// type restrictions of a type, if any.
+//
+// For all types whose underlying type is not *types.TypeParam,
+// *types.Interface, or *types.Union, this is just a single term with Tilde()
+// == false and Type() == typ. For types whose underlying type is
+// *types.TypeParam, *types.Interface, and *types.Union, see below.
+//
+// Structural type restrictions of a type parameter are created via
+// non-interface types embedded in its constraint interface (directly, or via a
+// chain of interface embeddings). For example, in the declaration type T[P
+// interface{~int; m()}] int is the structural restriction of the type
+// parameter P is ~int.
+//
+// With interface embedding and unions, the specification of structural type
+// restrictions may be arbitrarily complex. For example, consider the
+// following:
+//
+// type A interface{ ~string|~[]byte }
+//
+// type B interface{ int|string }
+//
+// type C interface { ~string|~int }
+//
+// type T[P interface{ A|B; C }] int
+//
+// In this example, the structural type restriction of P is ~string|int: A|B
+// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
+// which when intersected with C (~string|~int) yields ~string|int.
+//
+// NormalTerms computes these expansions and reductions, producing a
+// "normalized" form of the embeddings. A structural restriction is normalized
+// if it is a single union containing no interface terms, and is minimal in the
+// sense that removing any term changes the set of types satisfying the
+// constraint. It is left as a proof for the reader that, modulo sorting, there
+// is exactly one such normalized form.
+//
+// Because the minimal representation always takes this form, NormalTerms
+// returns a slice of tilde terms corresponding to the terms of the union in
+// the normalized structural restriction. An error is returned if the type is
+// invalid, exceeds complexity bounds, or has an empty type set. In the latter
+// case, NormalTerms returns ErrEmptyTypeSet.
+//
+// NormalTerms makes no guarantees about the order of terms, except that it
+// is deterministic.
+func NormalTerms(typ types.Type) ([]*Term, error) {
+ if tparam, ok := typ.(*TypeParam); ok {
+ constraint := tparam.Constraint()
+ if constraint == nil {
+ return nil, fmt.Errorf("%s has nil constraint", tparam)
+ }
+ iface, _ := constraint.Underlying().(*types.Interface)
+ if iface == nil {
+ return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying())
+ }
+ typ = iface
+ }
+ tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0)
+ if err != nil {
+ return nil, err
+ }
+ if tset.terms.isEmpty() {
+ return nil, ErrEmptyTypeSet
+ }
+ if tset.terms.isAll() {
+ return nil, nil
+ }
+ var terms []*Term
+ for _, term := range tset.terms {
+ terms = append(terms, NewTerm(term.tilde, term.typ))
+ }
+ return terms, nil
+}
+
+// A termSet holds the normalized set of terms for a given type.
+//
+// The name termSet is intentionally distinct from 'type set': a type set is
+// all types that implement a type (and includes method restrictions), whereas
+// a term set just represents the structural restrictions on a type.
+type termSet struct {
+ complete bool
+ terms termlist
+}
+
+func indentf(depth int, format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...)
+}
+
+func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) {
+ if t == nil {
+ panic("nil type")
+ }
+
+ if debug {
+ indentf(depth, "%s", t.String())
+ defer func() {
+ if err != nil {
+ indentf(depth, "=> %s", err)
+ } else {
+ indentf(depth, "=> %s", res.terms.String())
+ }
+ }()
+ }
+
+ const maxTermCount = 100
+ if tset, ok := seen[t]; ok {
+ if !tset.complete {
+ return nil, fmt.Errorf("cycle detected in the declaration of %s", t)
+ }
+ return tset, nil
+ }
+
+ // Mark the current type as seen to avoid infinite recursion.
+ tset := new(termSet)
+ defer func() {
+ tset.complete = true
+ }()
+ seen[t] = tset
+
+ switch u := t.Underlying().(type) {
+ case *types.Interface:
+ // The term set of an interface is the intersection of the term sets of its
+ // embedded types.
+ tset.terms = allTermlist
+ for i := 0; i < u.NumEmbeddeds(); i++ {
+ embedded := u.EmbeddedType(i)
+ if _, ok := embedded.Underlying().(*TypeParam); ok {
+ return nil, fmt.Errorf("invalid embedded type %T", embedded)
+ }
+ tset2, err := computeTermSetInternal(embedded, seen, depth+1)
+ if err != nil {
+ return nil, err
+ }
+ tset.terms = tset.terms.intersect(tset2.terms)
+ }
+ case *Union:
+ // The term set of a union is the union of term sets of its terms.
+ tset.terms = nil
+ for i := 0; i < u.Len(); i++ {
+ t := u.Term(i)
+ var terms termlist
+ switch t.Type().Underlying().(type) {
+ case *types.Interface:
+ tset2, err := computeTermSetInternal(t.Type(), seen, depth+1)
+ if err != nil {
+ return nil, err
+ }
+ terms = tset2.terms
+ case *TypeParam, *Union:
+ // A stand-alone type parameter or union is not permitted as union
+ // term.
+ return nil, fmt.Errorf("invalid union term %T", t)
+ default:
+ if t.Type() == types.Typ[types.Invalid] {
+ continue
+ }
+ terms = termlist{{t.Tilde(), t.Type()}}
+ }
+ tset.terms = tset.terms.union(terms)
+ if len(tset.terms) > maxTermCount {
+ return nil, fmt.Errorf("exceeded max term count %d", maxTermCount)
+ }
+ }
+ case *TypeParam:
+ panic("unreachable")
+ default:
+ // For all other types, the term set is just a single non-tilde term
+ // holding the type itself.
+ if u != types.Typ[types.Invalid] {
+ tset.terms = termlist{{false, t}}
+ }
+ }
+ return tset, nil
+}
+
+// under is a facade for the go/types internal function of the same name. It is
+// used by typeterm.go.
+func under(t types.Type) types.Type {
+ return t.Underlying()
+}
diff --git a/typeparams/normalize_test.go b/typeparams/normalize_test.go
new file mode 100644
index 0000000..98f7ec4
--- /dev/null
+++ b/typeparams/normalize_test.go
@@ -0,0 +1,104 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package typeparams_test
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "regexp"
+ "strings"
+ "testing"
+
+ . "golang.org/x/exp/typeparams"
+)
+
+func TestNormalTerms(t *testing.T) {
+ if !Enabled() {
+ t.Skip("typeparams are not enabled")
+ }
+
+ // In the following tests, src must define a type T with (at least) one type
+ // parameter. We will compute the normal terms of the first type parameter.
+ tests := []struct {
+ src string
+ want string
+ wantError string
+ }{
+ {"package emptyinterface0; type T[P interface{}] int", "all", ""},
+ {"package emptyinterface1; type T[P interface{ int | interface{} }] int", "all", ""},
+ {"package singleton; type T[P interface{ int }] int", "int", ""},
+ {"package under; type T[P interface{~int}] int", "~int", ""},
+ {"package superset; type T[P interface{ ~int | int }] int", "~int", ""},
+ {"package overlap; type T[P interface{ ~int; int }] int", "int", ""},
+ {"package emptyintersection; type T[P interface{ ~int; string }] int", "", "empty type set"},
+
+ {"package embedded0; type T[P interface{ I }] int; type I interface { int }", "int", ""},
+ {"package embedded1; type T[P interface{ I | string }] int; type I interface{ int | ~string }", "int ?\\| ?~string", ""},
+ {"package embedded2; type T[P interface{ I; string }] int; type I interface{ int | ~string }", "string", ""},
+
+ {"package named; type T[P C] int; type C interface{ ~int|int }", "~int", ""},
+ {`// package example is taken from the docstring for StructuralTerms
+package example
+
+type A interface{ ~string|~[]byte }
+
+type B interface{ int|string }
+
+type C interface { ~string|~int }
+
+type T[P interface{ A|B; C }] int
+`, "~string ?\\| ?int", ""},
+ }
+
+ for _, test := range tests {
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", test.src, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Run(f.Name.Name, func(t *testing.T) {
+ conf := types.Config{
+ Error: func(error) {}, // keep going on errors
+ }
+ pkg, err := conf.Check("", fset, []*ast.File{f}, nil)
+ if err != nil {
+ t.Logf("types.Config.Check: %v", err)
+ // keep going on type checker errors: we want to assert on behavior of
+ // invalid code as well.
+ }
+ obj := pkg.Scope().Lookup("T")
+ if obj == nil {
+ t.Fatal("type T not found")
+ }
+ T := ForNamed(obj.Type().(*types.Named)).At(0)
+ terms, err := NormalTerms(T)
+ if test.wantError != "" {
+ if err == nil {
+ t.Fatalf("StructuralTerms(%s): nil error, want %q", T, test.wantError)
+ }
+ if !strings.Contains(err.Error(), test.wantError) {
+ t.Errorf("StructuralTerms(%s): err = %q, want %q", T, err, test.wantError)
+ }
+ return
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got string
+ if len(terms) == 0 {
+ got = "all"
+ } else {
+ qf := types.RelativeTo(pkg)
+ got = types.TypeString(NewUnion(terms), qf)
+ }
+ want := regexp.MustCompile(test.want)
+ if !want.MatchString(got) {
+ t.Errorf("NormalTerms(%s) = %q, want matching %q", T, got, test.want)
+ }
+ })
+ }
+}
diff --git a/typeparams/termlist.go b/typeparams/termlist.go
new file mode 100644
index 0000000..6f88bfa
--- /dev/null
+++ b/typeparams/termlist.go
@@ -0,0 +1,172 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by copytermlist.go DO NOT EDIT.
+
+package typeparams
+
+import (
+ "bytes"
+ "go/types"
+)
+
+// A termlist represents the type set represented by the union
+// t1 βˆͺ y2 βˆͺ ... tn of the type sets of the terms t1 to tn.
+// A termlist is in normal form if all terms are disjoint.
+// termlist operations don't require the operands to be in
+// normal form.
+type termlist []*term
+
+// allTermlist represents the set of all types.
+// It is in normal form.
+var allTermlist = termlist{new(term)}
+
+// String prints the termlist exactly (without normalization).
+func (xl termlist) String() string {
+ if len(xl) == 0 {
+ return "βˆ…"
+ }
+ var buf bytes.Buffer
+ for i, x := range xl {
+ if i > 0 {
+ buf.WriteString(" βˆͺ ")
+ }
+ buf.WriteString(x.String())
+ }
+ return buf.String()
+}
+
+// isEmpty reports whether the termlist xl represents the empty set of types.
+func (xl termlist) isEmpty() bool {
+ // If there's a non-nil term, the entire list is not empty.
+ // If the termlist is in normal form, this requires at most
+ // one iteration.
+ for _, x := range xl {
+ if x != nil {
+ return false
+ }
+ }
+ return true
+}
+
+// isAll reports whether the termlist xl represents the set of all types.
+func (xl termlist) isAll() bool {
+ // If there's a 𝓀 term, the entire list is 𝓀.
+ // If the termlist is in normal form, this requires at most
+ // one iteration.
+ for _, x := range xl {
+ if x != nil && x.typ == nil {
+ return true
+ }
+ }
+ return false
+}
+
+// norm returns the normal form of xl.
+func (xl termlist) norm() termlist {
+ // Quadratic algorithm, but good enough for now.
+ // TODO(gri) fix asymptotic performance
+ used := make([]bool, len(xl))
+ var rl termlist
+ for i, xi := range xl {
+ if xi == nil || used[i] {
+ continue
+ }
+ for j := i + 1; j < len(xl); j++ {
+ xj := xl[j]
+ if xj == nil || used[j] {
+ continue
+ }
+ if u1, u2 := xi.union(xj); u2 == nil {
+ // If we encounter a 𝓀 term, the entire list is 𝓀.
+ // Exit early.
+ // (Note that this is not just an optimization;
+ // if we continue, we may end up with a 𝓀 term
+ // and other terms and the result would not be
+ // in normal form.)
+ if u1.typ == nil {
+ return allTermlist
+ }
+ xi = u1
+ used[j] = true // xj is now unioned into xi - ignore it in future iterations
+ }
+ }
+ rl = append(rl, xi)
+ }
+ return rl
+}
+
+// If the type set represented by xl is specified by a single (non-𝓀) term,
+// singleType returns that type. Otherwise it returns nil.
+func (xl termlist) singleType() types.Type {
+ if nl := xl.norm(); len(nl) == 1 {
+ return nl[0].typ // if nl.isAll() then typ is nil, which is ok
+ }
+ return nil
+}
+
+// union returns the union xl βˆͺ yl.
+func (xl termlist) union(yl termlist) termlist {
+ return append(xl, yl...).norm()
+}
+
+// intersect returns the intersection xl ∩ yl.
+func (xl termlist) intersect(yl termlist) termlist {
+ if xl.isEmpty() || yl.isEmpty() {
+ return nil
+ }
+
+ // Quadratic algorithm, but good enough for now.
+ // TODO(gri) fix asymptotic performance
+ var rl termlist
+ for _, x := range xl {
+ for _, y := range yl {
+ if r := x.intersect(y); r != nil {
+ rl = append(rl, r)
+ }
+ }
+ }
+ return rl.norm()
+}
+
+// equal reports whether xl and yl represent the same type set.
+func (xl termlist) equal(yl termlist) bool {
+ // TODO(gri) this should be more efficient
+ return xl.subsetOf(yl) && yl.subsetOf(xl)
+}
+
+// includes reports whether t ∈ xl.
+func (xl termlist) includes(t types.Type) bool {
+ for _, x := range xl {
+ if x.includes(t) {
+ return true
+ }
+ }
+ return false
+}
+
+// supersetOf reports whether y βŠ† xl.
+func (xl termlist) supersetOf(y *term) bool {
+ for _, x := range xl {
+ if y.subsetOf(x) {
+ return true
+ }
+ }
+ return false
+}
+
+// subsetOf reports whether xl βŠ† yl.
+func (xl termlist) subsetOf(yl termlist) bool {
+ if yl.isEmpty() {
+ return xl.isEmpty()
+ }
+
+ // each term x of xl must be a subset of yl
+ for _, x := range xl {
+ if !yl.supersetOf(x) {
+ return false // x is not a subset yl
+ }
+ }
+ return true
+}
diff --git a/typeparams/typeparams_go117.go b/typeparams/typeparams_go117.go
new file mode 100644
index 0000000..c1da793
--- /dev/null
+++ b/typeparams/typeparams_go117.go
@@ -0,0 +1,201 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.18
+
+package typeparams
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+)
+
+const enabled = false
+
+func unsupported() {
+ panic("type parameters are unsupported at this go version")
+}
+
+// IndexListExpr is a placeholder type, as type parameters are not supported at
+// this Go version. Its methods panic on use.
+type IndexListExpr struct {
+ ast.Expr
+ X ast.Expr // expression
+ Lbrack token.Pos // position of "["
+ Indices []ast.Expr // index expressions
+ Rbrack token.Pos // position of "]"
+}
+
+func (*IndexListExpr) Pos() token.Pos { unsupported(); return token.NoPos }
+func (*IndexListExpr) End() token.Pos { unsupported(); return token.NoPos }
+
+// ForTypeSpec returns an empty field list, as type parameters on not supported
+// at this Go version.
+func ForTypeSpec(*ast.TypeSpec) *ast.FieldList {
+ return nil
+}
+
+// ForFuncType returns an empty field list, as type parameters are not
+// supported at this Go version.
+func ForFuncType(*ast.FuncType) *ast.FieldList {
+ return nil
+}
+
+// TypeParam is a placeholder type, as type parameters are not supported at
+// this Go version. Its methods panic on use.
+type TypeParam struct{ types.Type }
+
+func (*TypeParam) String() string { unsupported(); return "" }
+func (*TypeParam) Underlying() types.Type { unsupported(); return nil }
+func (*TypeParam) Index() int { unsupported(); return 0 }
+func (*TypeParam) Constraint() types.Type { unsupported(); return nil }
+func (*TypeParam) SetConstraint(types.Type) { unsupported() }
+func (*TypeParam) Obj() *types.TypeName { unsupported(); return nil }
+
+// TypeParamList is a placeholder for an empty type parameter list.
+type TypeParamList struct{}
+
+func (*TypeParamList) Len() int { return 0 }
+func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil }
+
+// TypeList is a placeholder for an empty type list.
+type TypeList struct{}
+
+func (*TypeList) Len() int { return 0 }
+func (*TypeList) At(int) types.Type { unsupported(); return nil }
+
+// NewTypeParam is unsupported at this Go version, and panics.
+func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
+ unsupported()
+ return nil
+}
+
+// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or
+// typeParams is non-empty.
+func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
+ if len(recvTypeParams) != 0 || len(typeParams) != 0 {
+ unsupported()
+ }
+ return types.NewSignature(recv, params, results, variadic)
+}
+
+// ForSignature returns an empty slice.
+func ForSignature(*types.Signature) *TypeParamList {
+ return nil
+}
+
+// RecvTypeParams returns a nil slice.
+func RecvTypeParams(sig *types.Signature) *TypeParamList {
+ return nil
+}
+
+// IsComparable returns false, as no interfaces are type-restricted at this Go
+// version.
+func IsComparable(*types.Interface) bool {
+ return false
+}
+
+// IsMethodSet returns true, as no interfaces are type-restricted at this Go
+// version.
+func IsMethodSet(*types.Interface) bool {
+ return true
+}
+
+// IsImplicit returns false, as no interfaces are implicit at this Go version.
+func IsImplicit(*types.Interface) bool {
+ return false
+}
+
+// MarkImplicit does nothing, because this Go version does not have implicit
+// interfaces.
+func MarkImplicit(*types.Interface) {}
+
+// ForNamed returns an empty type parameter list, as type parameters are not
+// supported at this Go version.
+func ForNamed(*types.Named) *TypeParamList {
+ return nil
+}
+
+// SetForNamed panics if tparams is non-empty.
+func SetForNamed(_ *types.Named, tparams []*TypeParam) {
+ if len(tparams) > 0 {
+ unsupported()
+ }
+}
+
+// NamedTypeArgs returns nil.
+func NamedTypeArgs(*types.Named) *TypeList {
+ return nil
+}
+
+// NamedTypeOrigin is the identity method at this Go version.
+func NamedTypeOrigin(named *types.Named) types.Type {
+ return named
+}
+
+// Term holds information about a structural type restriction.
+type Term struct {
+ tilde bool
+ typ types.Type
+}
+
+func (m *Term) Tilde() bool { return m.tilde }
+func (m *Term) Type() types.Type { return m.typ }
+func (m *Term) String() string {
+ pre := ""
+ if m.tilde {
+ pre = "~"
+ }
+ return pre + m.typ.String()
+}
+
+// NewTerm creates a new placeholder term type.
+func NewTerm(tilde bool, typ types.Type) *Term {
+ return &Term{tilde, typ}
+}
+
+// Union is a placeholder type, as type parameters are not supported at this Go
+// version. Its methods panic on use.
+type Union struct{ types.Type }
+
+func (*Union) String() string { unsupported(); return "" }
+func (*Union) Underlying() types.Type { unsupported(); return nil }
+func (*Union) Len() int { return 0 }
+func (*Union) Term(i int) *Term { unsupported(); return nil }
+
+// NewUnion is unsupported at this Go version, and panics.
+func NewUnion(terms []*Term) *Union {
+ unsupported()
+ return nil
+}
+
+// InitInstances is a noop at this Go version.
+func InitInstances(*types.Info) {}
+
+// Instance is a placeholder type, as type parameters are not supported at this
+// Go version.
+type Instance struct {
+ TypeArgs *TypeList
+ Type types.Type
+}
+
+// GetInstances returns a nil map, as type parameters are not supported at this
+// Go version.
+func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil }
+
+// Context is a placeholder type, as type parameters are not supported at
+// this Go version.
+type Context struct{}
+
+// NewContext returns a placeholder Context instance.
+func NewContext() *Context {
+ return &Context{}
+}
+
+// Instantiate is unsupported on this Go version, and panics.
+func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
+ unsupported()
+ return nil, nil
+}
diff --git a/typeparams/typeparams_go118.go b/typeparams/typeparams_go118.go
new file mode 100644
index 0000000..0b35449
--- /dev/null
+++ b/typeparams/typeparams_go118.go
@@ -0,0 +1,147 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package typeparams
+
+import (
+ "go/ast"
+ "go/types"
+)
+
+const enabled = true
+
+// IndexListExpr is an alias for ast.IndexListExpr.
+type IndexListExpr = ast.IndexListExpr
+
+// ForTypeSpec returns n.TypeParams.
+func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList {
+ if n == nil {
+ return nil
+ }
+ return n.TypeParams
+}
+
+// ForFuncType returns n.TypeParams.
+func ForFuncType(n *ast.FuncType) *ast.FieldList {
+ if n == nil {
+ return nil
+ }
+ return n.TypeParams
+}
+
+// TypeParam is an alias for types.TypeParam
+type TypeParam = types.TypeParam
+
+// TypeParamList is an alias for types.TypeParamList
+type TypeParamList = types.TypeParamList
+
+// TypeList is an alias for types.TypeList
+type TypeList = types.TypeList
+
+// NewTypeParam calls types.NewTypeParam.
+func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
+ return types.NewTypeParam(name, constraint)
+}
+
+// NewSignatureType calls types.NewSignatureType.
+func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
+ return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic)
+}
+
+// ForSignature returns sig.TypeParams()
+func ForSignature(sig *types.Signature) *TypeParamList {
+ return sig.TypeParams()
+}
+
+// RecvTypeParams returns sig.RecvTypeParams().
+func RecvTypeParams(sig *types.Signature) *TypeParamList {
+ return sig.RecvTypeParams()
+}
+
+// IsComparable calls iface.IsComparable().
+func IsComparable(iface *types.Interface) bool {
+ return iface.IsComparable()
+}
+
+// IsMethodSet calls iface.IsMethodSet().
+func IsMethodSet(iface *types.Interface) bool {
+ return iface.IsMethodSet()
+}
+
+// IsImplicit calls iface.IsImplicit().
+func IsImplicit(iface *types.Interface) bool {
+ return iface.IsImplicit()
+}
+
+// MarkImplicit calls iface.MarkImplicit().
+func MarkImplicit(iface *types.Interface) {
+ iface.MarkImplicit()
+}
+
+// ForNamed extracts the (possibly empty) type parameter object list from
+// named.
+func ForNamed(named *types.Named) *TypeParamList {
+ return named.TypeParams()
+}
+
+// SetForNamed sets the type params tparams on n. Each tparam must be of
+// dynamic type *types.TypeParam.
+func SetForNamed(n *types.Named, tparams []*TypeParam) {
+ n.SetTypeParams(tparams)
+}
+
+// NamedTypeArgs returns named.TypeArgs().
+func NamedTypeArgs(named *types.Named) *TypeList {
+ return named.TypeArgs()
+}
+
+// NamedTypeOrigin returns named.Orig().
+func NamedTypeOrigin(named *types.Named) types.Type {
+ return named.Origin()
+}
+
+// Term is an alias for types.Term.
+type Term = types.Term
+
+// NewTerm calls types.NewTerm.
+func NewTerm(tilde bool, typ types.Type) *Term {
+ return types.NewTerm(tilde, typ)
+}
+
+// Union is an alias for types.Union
+type Union = types.Union
+
+// NewUnion calls types.NewUnion.
+func NewUnion(terms []*Term) *Union {
+ return types.NewUnion(terms)
+}
+
+// InitInstances initializes info to record information about type and function
+// instances.
+func InitInstances(info *types.Info) {
+ info.Instances = make(map[*ast.Ident]types.Instance)
+}
+
+// Instance is an alias for types.Instance.
+type Instance = types.Instance
+
+// GetInstances returns info.Instances.
+func GetInstances(info *types.Info) map[*ast.Ident]Instance {
+ return info.Instances
+}
+
+// Context is an alias for types.Context.
+type Context = types.Context
+
+// NewContext calls types.NewContext.
+func NewContext() *Context {
+ return types.NewContext()
+}
+
+// Instantiate calls types.Instantiate.
+func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
+ return types.Instantiate(ctxt, typ, targs, validate)
+}
diff --git a/typeparams/typeparams_test.go b/typeparams/typeparams_test.go
new file mode 100644
index 0000000..2136a59
--- /dev/null
+++ b/typeparams/typeparams_test.go
@@ -0,0 +1,137 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.18
+
+package typeparams_test
+
+import (
+ "bytes"
+ "go/ast"
+ "go/build"
+ "go/importer"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "testing"
+)
+
+// TestAPIConsistency verifies that exported APIs match at Go 1.17 and Go
+// 1.18+.
+//
+// It relies on the convention that the names of type aliases in the typeparams
+// package match the names of the types they are aliasing.
+//
+// This test could be made more precise.
+func TestAPIConsistency(t *testing.T) {
+ api118 := getAPI(buildPackage(t, true))
+ api117 := getAPI(buildPackage(t, false))
+
+ for name, api := range api117 {
+ if api != api118[name] {
+ t.Errorf("%q: got %s at 1.17, but %s at 1.18+", name, api, api118[name])
+ }
+ delete(api118, name)
+ }
+ for name, api := range api118 {
+ if api != api117[name] {
+ t.Errorf("%q: got %s at 1.18+, but %s at 1.17", name, api, api117[name])
+ }
+ }
+}
+
+func getAPI(pkg *types.Package) map[string]string {
+ api := make(map[string]string)
+ for _, name := range pkg.Scope().Names() {
+ if !token.IsExported(name) {
+ continue
+ }
+ api[name] = name
+ obj := pkg.Scope().Lookup(name)
+ if f, ok := obj.(*types.Func); ok {
+ api[name] = formatSignature(f.Type().(*types.Signature))
+ }
+ typ := pkg.Scope().Lookup(name).Type()
+ // Consider method sets of pointer and non-pointer receivers.
+ msets := map[string]*types.MethodSet{
+ name: types.NewMethodSet(typ),
+ "*" + name: types.NewMethodSet(types.NewPointer(typ)),
+ }
+ for name, mset := range msets {
+ for i := 0; i < mset.Len(); i++ {
+ f := mset.At(i).Obj().(*types.Func)
+ mname := f.Name()
+ if token.IsExported(mname) {
+ api[name+"."+mname] = formatSignature(f.Type().(*types.Signature))
+ }
+ }
+ }
+ }
+ return api
+}
+
+func formatSignature(sig *types.Signature) string {
+ var b bytes.Buffer
+ b.WriteString("func")
+ writeTuple(&b, sig.Params())
+ writeTuple(&b, sig.Results())
+ return b.String()
+}
+
+func writeTuple(buf *bytes.Buffer, t *types.Tuple) {
+ buf.WriteRune('(')
+
+ // The API at Go 1.18 uses aliases for types in go/types. These types are
+ // _actually_ in the go/types package, and therefore would be formatted as
+ // e.g. *types.TypeParam, which would not match *typeparams.TypeParam --
+ // go/types does not track aliases. As we use the same name for all aliases,
+ // we can make the formatted signatures match by dropping the package
+ // qualifier.
+ qf := func(*types.Package) string { return "" }
+
+ for i := 0; i < t.Len(); i++ {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(types.TypeString(t.At(i).Type(), qf))
+ }
+ buf.WriteRune(')')
+}
+
+func buildPackage(t *testing.T, go118 bool) *types.Package {
+ ctxt := build.Default
+ if !go118 {
+ for i, tag := range ctxt.ReleaseTags {
+ if tag == "go1.18" {
+ ctxt.ReleaseTags = ctxt.ReleaseTags[:i]
+ break
+ }
+ }
+ }
+ bpkg, err := ctxt.ImportDir(".", 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return typeCheck(t, bpkg.GoFiles)
+}
+
+func typeCheck(t *testing.T, filenames []string) *types.Package {
+ fset := token.NewFileSet()
+ var files []*ast.File
+ for _, name := range filenames {
+ f, err := parser.ParseFile(fset, name, nil, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ files = append(files, f)
+ }
+ conf := types.Config{
+ Importer: importer.Default(),
+ }
+ pkg, err := conf.Check("", fset, files, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return pkg
+}
diff --git a/typeparams/typeterm.go b/typeparams/typeterm.go
new file mode 100644
index 0000000..7350bb7
--- /dev/null
+++ b/typeparams/typeterm.go
@@ -0,0 +1,169 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Code generated by copytermlist.go DO NOT EDIT.
+
+package typeparams
+
+import "go/types"
+
+// A term describes elementary type sets:
+//
+// βˆ…: (*term)(nil) == βˆ… // set of no types (empty set)
+// 𝓀: &term{} == 𝓀 // set of all types (𝓀niverse)
+// T: &term{false, T} == {T} // set of type T
+// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t
+type term struct {
+ tilde bool // valid if typ != nil
+ typ types.Type
+}
+
+func (x *term) String() string {
+ switch {
+ case x == nil:
+ return "βˆ…"
+ case x.typ == nil:
+ return "𝓀"
+ case x.tilde:
+ return "~" + x.typ.String()
+ default:
+ return x.typ.String()
+ }
+}
+
+// equal reports whether x and y represent the same type set.
+func (x *term) equal(y *term) bool {
+ // easy cases
+ switch {
+ case x == nil || y == nil:
+ return x == y
+ case x.typ == nil || y.typ == nil:
+ return x.typ == y.typ
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ return x.tilde == y.tilde && types.Identical(x.typ, y.typ)
+}
+
+// union returns the union x βˆͺ y: zero, one, or two non-nil terms.
+func (x *term) union(y *term) (_, _ *term) {
+ // easy cases
+ switch {
+ case x == nil && y == nil:
+ return nil, nil // βˆ… βˆͺ βˆ… == βˆ…
+ case x == nil:
+ return y, nil // βˆ… βˆͺ y == y
+ case y == nil:
+ return x, nil // x βˆͺ βˆ… == x
+ case x.typ == nil:
+ return x, nil // 𝓀 βˆͺ y == 𝓀
+ case y.typ == nil:
+ return y, nil // x βˆͺ 𝓀 == 𝓀
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ if x.disjoint(y) {
+ return x, y // x βˆͺ y == (x, y) if x ∩ y == βˆ…
+ }
+ // x.typ == y.typ
+
+ // ~t βˆͺ ~t == ~t
+ // ~t βˆͺ T == ~t
+ // T βˆͺ ~t == ~t
+ // T βˆͺ T == T
+ if x.tilde || !y.tilde {
+ return x, nil
+ }
+ return y, nil
+}
+
+// intersect returns the intersection x ∩ y.
+func (x *term) intersect(y *term) *term {
+ // easy cases
+ switch {
+ case x == nil || y == nil:
+ return nil // βˆ… ∩ y == βˆ… and ∩ βˆ… == βˆ…
+ case x.typ == nil:
+ return y // 𝓀 ∩ y == y
+ case y.typ == nil:
+ return x // x ∩ 𝓀 == x
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ if x.disjoint(y) {
+ return nil // x ∩ y == βˆ… if x ∩ y == βˆ…
+ }
+ // x.typ == y.typ
+
+ // ~t ∩ ~t == ~t
+ // ~t ∩ T == T
+ // T ∩ ~t == T
+ // T ∩ T == T
+ if !x.tilde || y.tilde {
+ return x
+ }
+ return y
+}
+
+// includes reports whether t ∈ x.
+func (x *term) includes(t types.Type) bool {
+ // easy cases
+ switch {
+ case x == nil:
+ return false // t ∈ βˆ… == false
+ case x.typ == nil:
+ return true // t ∈ 𝓀 == true
+ }
+ // βˆ… βŠ‚ x βŠ‚ 𝓀
+
+ u := t
+ if x.tilde {
+ u = under(u)
+ }
+ return types.Identical(x.typ, u)
+}
+
+// subsetOf reports whether x βŠ† y.
+func (x *term) subsetOf(y *term) bool {
+ // easy cases
+ switch {
+ case x == nil:
+ return true // βˆ… βŠ† y == true
+ case y == nil:
+ return false // x βŠ† βˆ… == false since x != βˆ…
+ case y.typ == nil:
+ return true // x βŠ† 𝓀 == true
+ case x.typ == nil:
+ return false // 𝓀 βŠ† y == false since y != 𝓀
+ }
+ // βˆ… βŠ‚ x, y βŠ‚ 𝓀
+
+ if x.disjoint(y) {
+ return false // x βŠ† y == false if x ∩ y == βˆ…
+ }
+ // x.typ == y.typ
+
+ // ~t βŠ† ~t == true
+ // ~t βŠ† T == false
+ // T βŠ† ~t == true
+ // T βŠ† T == true
+ return !x.tilde || y.tilde
+}
+
+// disjoint reports whether x ∩ y == βˆ….
+// x.typ and y.typ must not be nil.
+func (x *term) disjoint(y *term) bool {
+ if debug && (x.typ == nil || y.typ == nil) {
+ panic("invalid argument(s)")
+ }
+ ux := x.typ
+ if y.tilde {
+ ux = under(ux)
+ }
+ uy := y.typ
+ if x.tilde {
+ uy = under(uy)
+ }
+ return !types.Identical(ux, uy)
+}
diff --git a/utf8string/string.go b/utf8string/string.go
new file mode 100644
index 0000000..281d5ac
--- /dev/null
+++ b/utf8string/string.go
@@ -0,0 +1,203 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package utf8string provides an efficient way to index strings by rune rather than by byte.
+package utf8string // import "golang.org/x/exp/utf8string"
+
+import (
+ "errors"
+ "unicode/utf8"
+)
+
+// String wraps a regular string with a small structure that provides more
+// efficient indexing by code point index, as opposed to byte index.
+// Scanning incrementally forwards or backwards is O(1) per index operation
+// (although not as fast a range clause going forwards). Random access is
+// O(N) in the length of the string, but the overhead is less than always
+// scanning from the beginning.
+// If the string is ASCII, random access is O(1).
+// Unlike the built-in string type, String has internal mutable state and
+// is not thread-safe.
+type String struct {
+ str string
+ numRunes int
+ // If width > 0, the rune at runePos starts at bytePos and has the specified width.
+ width int
+ bytePos int
+ runePos int
+ nonASCII int // byte index of the first non-ASCII rune.
+}
+
+// NewString returns a new UTF-8 string with the provided contents.
+func NewString(contents string) *String {
+ return new(String).Init(contents)
+}
+
+// Init initializes an existing String to hold the provided contents.
+// It returns a pointer to the initialized String.
+func (s *String) Init(contents string) *String {
+ s.str = contents
+ s.bytePos = 0
+ s.runePos = 0
+ for i := 0; i < len(contents); i++ {
+ if contents[i] >= utf8.RuneSelf {
+ // Not ASCII.
+ s.numRunes = utf8.RuneCountInString(contents)
+ _, s.width = utf8.DecodeRuneInString(contents)
+ s.nonASCII = i
+ return s
+ }
+ }
+ // ASCII is simple. Also, the empty string is ASCII.
+ s.numRunes = len(contents)
+ s.width = 0
+ s.nonASCII = len(contents)
+ return s
+}
+
+// String returns the contents of the String. This method also means the
+// String is directly printable by fmt.Print.
+func (s *String) String() string {
+ return s.str
+}
+
+// RuneCount returns the number of runes (Unicode code points) in the String.
+func (s *String) RuneCount() int {
+ return s.numRunes
+}
+
+// IsASCII returns a boolean indicating whether the String contains only ASCII bytes.
+func (s *String) IsASCII() bool {
+ return s.width == 0
+}
+
+// Slice returns the string sliced at rune positions [i:j].
+func (s *String) Slice(i, j int) string {
+ // ASCII is easy. Let the compiler catch the indexing error if there is one.
+ if j < s.nonASCII {
+ return s.str[i:j]
+ }
+ if i < 0 || j > s.numRunes || i > j {
+ panic(sliceOutOfRange)
+ }
+ if i == j {
+ return ""
+ }
+ // For non-ASCII, after At(i), bytePos is always the position of the indexed character.
+ var low, high int
+ switch {
+ case i < s.nonASCII:
+ low = i
+ case i == s.numRunes:
+ low = len(s.str)
+ default:
+ s.At(i)
+ low = s.bytePos
+ }
+ switch {
+ case j == s.numRunes:
+ high = len(s.str)
+ default:
+ s.At(j)
+ high = s.bytePos
+ }
+ return s.str[low:high]
+}
+
+// At returns the rune with index i in the String. The sequence of runes is the same
+// as iterating over the contents with a "for range" clause.
+func (s *String) At(i int) rune {
+ // ASCII is easy. Let the compiler catch the indexing error if there is one.
+ if i < s.nonASCII {
+ return rune(s.str[i])
+ }
+
+ // Now we do need to know the index is valid.
+ if i < 0 || i >= s.numRunes {
+ panic(outOfRange)
+ }
+
+ var r rune
+
+ // Five easy common cases: within 1 spot of bytePos/runePos, or the beginning, or the end.
+ // With these cases, all scans from beginning or end work in O(1) time per rune.
+ switch {
+
+ case i == s.runePos-1: // backing up one rune
+ r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos])
+ s.runePos = i
+ s.bytePos -= s.width
+ return r
+ case i == s.runePos+1: // moving ahead one rune
+ s.runePos = i
+ s.bytePos += s.width
+ fallthrough
+ case i == s.runePos:
+ r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:])
+ return r
+ case i == 0: // start of string
+ r, s.width = utf8.DecodeRuneInString(s.str)
+ s.runePos = 0
+ s.bytePos = 0
+ return r
+
+ case i == s.numRunes-1: // last rune in string
+ r, s.width = utf8.DecodeLastRuneInString(s.str)
+ s.runePos = i
+ s.bytePos = len(s.str) - s.width
+ return r
+ }
+
+ // We need to do a linear scan. There are three places to start from:
+ // 1) The beginning
+ // 2) bytePos/runePos.
+ // 3) The end
+ // Choose the closest in rune count, scanning backwards if necessary.
+ forward := true
+ if i < s.runePos {
+ // Between beginning and pos. Which is closer?
+ // Since both i and runePos are guaranteed >= nonASCII, that's the
+ // lowest location we need to start from.
+ if i < (s.runePos-s.nonASCII)/2 {
+ // Scan forward from beginning
+ s.bytePos, s.runePos = s.nonASCII, s.nonASCII
+ } else {
+ // Scan backwards from where we are
+ forward = false
+ }
+ } else {
+ // Between pos and end. Which is closer?
+ if i-s.runePos < (s.numRunes-s.runePos)/2 {
+ // Scan forward from pos
+ } else {
+ // Scan backwards from end
+ s.bytePos, s.runePos = len(s.str), s.numRunes
+ forward = false
+ }
+ }
+ if forward {
+ // TODO: Is it much faster to use a range loop for this scan?
+ for {
+ r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:])
+ if s.runePos == i {
+ break
+ }
+ s.runePos++
+ s.bytePos += s.width
+ }
+ } else {
+ for {
+ r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos])
+ s.runePos--
+ s.bytePos -= s.width
+ if s.runePos == i {
+ break
+ }
+ }
+ }
+ return r
+}
+
+var outOfRange = errors.New("utf8string: index out of range")
+var sliceOutOfRange = errors.New("utf8string: slice index out of range")
diff --git a/utf8string/string_test.go b/utf8string/string_test.go
new file mode 100644
index 0000000..28511b2
--- /dev/null
+++ b/utf8string/string_test.go
@@ -0,0 +1,123 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package utf8string
+
+import (
+ "math/rand"
+ "testing"
+ "unicode/utf8"
+)
+
+var testStrings = []string{
+ "",
+ "abcd",
+ "☺☻☹",
+ "ζ—₯a本bθͺžΓ§ζ—₯ð本ÊθͺžΓΎζ—₯Β₯本¼θͺžiζ—₯Β©",
+ "ζ—₯a本bθͺžΓ§ζ—₯ð本ÊθͺžΓΎζ—₯Β₯本¼θͺžiζ—₯Β©ζ—₯a本bθͺžΓ§ζ—₯ð本ÊθͺžΓΎζ—₯Β₯本¼θͺžiζ—₯Β©ζ—₯a本bθͺžΓ§ζ—₯ð本ÊθͺžΓΎζ—₯Β₯本¼θͺžiζ—₯Β©",
+ "\x80\x80\x80\x80",
+}
+
+func TestScanForwards(t *testing.T) {
+ for _, s := range testStrings {
+ runes := []rune(s)
+ str := NewString(s)
+ if str.RuneCount() != len(runes) {
+ t.Errorf("%s: expected %d runes; got %d", s, len(runes), str.RuneCount())
+ break
+ }
+ for i, expect := range runes {
+ got := str.At(i)
+ if got != expect {
+ t.Errorf("%s[%d]: expected %c (%U); got %c (%U)", s, i, expect, expect, got, got)
+ }
+ }
+ }
+}
+
+func TestScanBackwards(t *testing.T) {
+ for _, s := range testStrings {
+ runes := []rune(s)
+ str := NewString(s)
+ if str.RuneCount() != len(runes) {
+ t.Errorf("%s: expected %d runes; got %d", s, len(runes), str.RuneCount())
+ break
+ }
+ for i := len(runes) - 1; i >= 0; i-- {
+ expect := runes[i]
+ got := str.At(i)
+ if got != expect {
+ t.Errorf("%s[%d]: expected %c (%U); got %c (%U)", s, i, expect, expect, got, got)
+ }
+ }
+ }
+}
+
+func randCount() int {
+ if testing.Short() {
+ return 100
+ }
+ return 100000
+}
+
+func TestRandomAccess(t *testing.T) {
+ for _, s := range testStrings {
+ if len(s) == 0 {
+ continue
+ }
+ runes := []rune(s)
+ str := NewString(s)
+ if str.RuneCount() != len(runes) {
+ t.Errorf("%s: expected %d runes; got %d", s, len(runes), str.RuneCount())
+ break
+ }
+ for j := 0; j < randCount(); j++ {
+ i := rand.Intn(len(runes))
+ expect := runes[i]
+ got := str.At(i)
+ if got != expect {
+ t.Errorf("%s[%d]: expected %c (%U); got %c (%U)", s, i, expect, expect, got, got)
+ }
+ }
+ }
+}
+
+func TestRandomSliceAccess(t *testing.T) {
+ for _, s := range testStrings {
+ if len(s) == 0 || s[0] == '\x80' { // the bad-UTF-8 string fools this simple test
+ continue
+ }
+ runes := []rune(s)
+ str := NewString(s)
+ if str.RuneCount() != len(runes) {
+ t.Errorf("%s: expected %d runes; got %d", s, len(runes), str.RuneCount())
+ break
+ }
+ for k := 0; k < randCount(); k++ {
+ i := rand.Intn(len(runes))
+ j := rand.Intn(len(runes) + 1)
+ if i > j { // include empty strings
+ continue
+ }
+ expect := string(runes[i:j])
+ got := str.Slice(i, j)
+ if got != expect {
+ t.Errorf("%s[%d:%d]: expected %q got %q", s, i, j, expect, got)
+ }
+ }
+ }
+}
+
+func TestLimitSliceAccess(t *testing.T) {
+ for _, s := range testStrings {
+ str := NewString(s)
+ if str.Slice(0, 0) != "" {
+ t.Error("failure with empty slice at beginning")
+ }
+ nr := utf8.RuneCountInString(s)
+ if str.Slice(nr, nr) != "" {
+ t.Error("failure with empty slice at end")
+ }
+ }
+}