├── .gitattributes ├── .gitignore ├── README.md ├── code0.jpg ├── code1.jpg ├── gifmaker ├── .gitignore ├── .vscode │ └── settings.json ├── Cargo.lock ├── Cargo.toml ├── build.cmd ├── build.rs └── src │ ├── gifmaker.rs │ ├── js.rs │ └── lib.rs ├── program ├── .gitignore ├── app.js ├── app.json ├── app.wxss ├── pages │ ├── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── logs │ │ ├── logs.js │ │ ├── logs.json │ │ ├── logs.wxml │ │ └── logs.wxss ├── project.config.json ├── project.private.config.json ├── sitemap.json ├── static │ ├── album.png │ ├── basicprofile.png │ ├── camera.png │ ├── campos.png │ ├── code.jpg │ ├── delete.png │ ├── faceoff_logo.png │ ├── ic_close.png │ ├── ic_img.png │ └── trash.png └── utils │ ├── encoding.js │ ├── gifmaker │ ├── gifmaker.js │ └── gifmaker_bg.wasm │ ├── img_sec_check.js │ └── util.js ├── screenrecorder.gif ├── screenshot.jpg └── server ├── README.md ├── server_utc_now ├── .cargo │ └── config.toml ├── .gitignore ├── Cargo.toml ├── build-run.cmd ├── run.cmd ├── spin.toml └── src │ └── lib.rs └── wx_sec_check ├── .cargo └── config.toml ├── .gitignore ├── Cargo.toml ├── build-run.cmd ├── run.cmd ├── spin.toml ├── src ├── lib.rs ├── sec_check.rs ├── secret.rs ├── token.rs └── tools.rs └── test_client ├── .gitignore ├── Cargo.toml ├── src └── main.rs └── test.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.history -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # miniprogram-gifmaker 2 | 3 | GIF动画制作微信小程序 4 | 5 | ## 目录结构 6 | 7 | /gifmaker 生成GIF的Rust库,编译为Webassembly 8 | 9 | /program 微信小程序代码 10 | 11 | /server 图像审查服务器端代码 12 | 13 | ## 运行截图 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /code0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/code0.jpg -------------------------------------------------------------------------------- /code1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/code1.jpg -------------------------------------------------------------------------------- /gifmaker/.gitignore: -------------------------------------------------------------------------------- 1 | /.history 2 | /target 3 | **/*.rs.bk 4 | /pkg 5 | /gm_check_code.rs -------------------------------------------------------------------------------- /gifmaker/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "wasm32-unknown-unknown", 3 | } -------------------------------------------------------------------------------- /gifmaker/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "adler32" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 16 | 17 | [[package]] 18 | name = "aes" 19 | version = "0.7.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 22 | dependencies = [ 23 | "cfg-if 1.0.0", 24 | "cipher", 25 | "cpufeatures", 26 | "opaque-debug", 27 | ] 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.55" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" 34 | 35 | [[package]] 36 | name = "base64" 37 | version = "0.13.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.20.0-alpha.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "149ea5dc24cb11513350770afebba32b68e3d2e356f9221351a2a1ee89112a82" 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.0.4" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 52 | 53 | [[package]] 54 | name = "block-buffer" 55 | version = "0.9.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 58 | dependencies = [ 59 | "block-padding", 60 | "generic-array", 61 | ] 62 | 63 | [[package]] 64 | name = "block-modes" 65 | version = "0.8.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" 68 | dependencies = [ 69 | "block-padding", 70 | "cipher", 71 | ] 72 | 73 | [[package]] 74 | name = "block-padding" 75 | version = "0.2.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" 78 | 79 | [[package]] 80 | name = "bumpalo" 81 | version = "3.9.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 84 | 85 | [[package]] 86 | name = "byteorder" 87 | version = "1.4.3" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 90 | 91 | [[package]] 92 | name = "cfg-if" 93 | version = "0.1.10" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 96 | 97 | [[package]] 98 | name = "cfg-if" 99 | version = "1.0.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 102 | 103 | [[package]] 104 | name = "cipher" 105 | version = "0.3.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 108 | dependencies = [ 109 | "generic-array", 110 | ] 111 | 112 | [[package]] 113 | name = "color_quant" 114 | version = "1.0.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 117 | 118 | [[package]] 119 | name = "cpufeatures" 120 | version = "0.2.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" 123 | dependencies = [ 124 | "libc", 125 | ] 126 | 127 | [[package]] 128 | name = "crc-any" 129 | version = "2.4.3" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "774646b687f63643eb0f4bf13dc263cb581c8c9e57973b6ddf78bda3994d88df" 132 | dependencies = [ 133 | "debug-helper", 134 | ] 135 | 136 | [[package]] 137 | name = "crc32fast" 138 | version = "1.2.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 141 | dependencies = [ 142 | "cfg-if 0.1.10", 143 | ] 144 | 145 | [[package]] 146 | name = "debug-helper" 147 | version = "0.3.13" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" 150 | 151 | [[package]] 152 | name = "deflate" 153 | version = "1.0.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" 156 | dependencies = [ 157 | "adler32", 158 | ] 159 | 160 | [[package]] 161 | name = "des" 162 | version = "0.7.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d" 165 | dependencies = [ 166 | "byteorder", 167 | "cipher", 168 | "opaque-debug", 169 | ] 170 | 171 | [[package]] 172 | name = "digest" 173 | version = "0.9.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 176 | dependencies = [ 177 | "generic-array", 178 | ] 179 | 180 | [[package]] 181 | name = "generic-array" 182 | version = "0.14.5" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 185 | dependencies = [ 186 | "typenum", 187 | "version_check", 188 | ] 189 | 190 | [[package]] 191 | name = "gif" 192 | version = "0.11.3" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b" 195 | dependencies = [ 196 | "color_quant", 197 | "weezl", 198 | ] 199 | 200 | [[package]] 201 | name = "gifmaker" 202 | version = "1.0.3" 203 | dependencies = [ 204 | "anyhow", 205 | "base64 0.20.0-alpha.1", 206 | "gif", 207 | "js-sys", 208 | "magic-crypt", 209 | "once_cell", 210 | "png", 211 | "wasm-bindgen", 212 | "web-sys", 213 | ] 214 | 215 | [[package]] 216 | name = "js-sys" 217 | version = "0.3.57" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" 220 | dependencies = [ 221 | "wasm-bindgen", 222 | ] 223 | 224 | [[package]] 225 | name = "lazy_static" 226 | version = "1.4.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 229 | 230 | [[package]] 231 | name = "libc" 232 | version = "0.2.126" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 235 | 236 | [[package]] 237 | name = "log" 238 | version = "0.4.14" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 241 | dependencies = [ 242 | "cfg-if 1.0.0", 243 | ] 244 | 245 | [[package]] 246 | name = "magic-crypt" 247 | version = "3.1.10" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "6c913782c21b53ad246863641fffbaf73a9eb32ff0d939b10d361b7294e2ea9c" 250 | dependencies = [ 251 | "aes", 252 | "base64 0.13.0", 253 | "block-modes", 254 | "crc-any", 255 | "des", 256 | "digest", 257 | "md-5", 258 | "sha2", 259 | "tiger", 260 | ] 261 | 262 | [[package]] 263 | name = "md-5" 264 | version = "0.9.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" 267 | dependencies = [ 268 | "block-buffer", 269 | "digest", 270 | "opaque-debug", 271 | ] 272 | 273 | [[package]] 274 | name = "miniz_oxide" 275 | version = "0.5.1" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" 278 | dependencies = [ 279 | "adler", 280 | ] 281 | 282 | [[package]] 283 | name = "once_cell" 284 | version = "1.12.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 287 | 288 | [[package]] 289 | name = "opaque-debug" 290 | version = "0.3.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 293 | 294 | [[package]] 295 | name = "png" 296 | version = "0.17.5" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" 299 | dependencies = [ 300 | "bitflags", 301 | "crc32fast", 302 | "deflate", 303 | "miniz_oxide", 304 | ] 305 | 306 | [[package]] 307 | name = "proc-macro2" 308 | version = "1.0.36" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 311 | dependencies = [ 312 | "unicode-xid", 313 | ] 314 | 315 | [[package]] 316 | name = "quote" 317 | version = "1.0.15" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 320 | dependencies = [ 321 | "proc-macro2", 322 | ] 323 | 324 | [[package]] 325 | name = "sha2" 326 | version = "0.9.9" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 329 | dependencies = [ 330 | "block-buffer", 331 | "cfg-if 1.0.0", 332 | "cpufeatures", 333 | "digest", 334 | "opaque-debug", 335 | ] 336 | 337 | [[package]] 338 | name = "syn" 339 | version = "1.0.86" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 342 | dependencies = [ 343 | "proc-macro2", 344 | "quote", 345 | "unicode-xid", 346 | ] 347 | 348 | [[package]] 349 | name = "tiger" 350 | version = "0.1.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "443e531cbcf9de83258cfef70bcd56c91188de5819ebd4b19c85f589e0617005" 353 | dependencies = [ 354 | "block-buffer", 355 | "byteorder", 356 | "digest", 357 | ] 358 | 359 | [[package]] 360 | name = "typenum" 361 | version = "1.15.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 364 | 365 | [[package]] 366 | name = "unicode-xid" 367 | version = "0.2.2" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 370 | 371 | [[package]] 372 | name = "version_check" 373 | version = "0.9.4" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 376 | 377 | [[package]] 378 | name = "wasm-bindgen" 379 | version = "0.2.80" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" 382 | dependencies = [ 383 | "cfg-if 1.0.0", 384 | "wasm-bindgen-macro", 385 | ] 386 | 387 | [[package]] 388 | name = "wasm-bindgen-backend" 389 | version = "0.2.80" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" 392 | dependencies = [ 393 | "bumpalo", 394 | "lazy_static", 395 | "log", 396 | "proc-macro2", 397 | "quote", 398 | "syn", 399 | "wasm-bindgen-shared", 400 | ] 401 | 402 | [[package]] 403 | name = "wasm-bindgen-macro" 404 | version = "0.2.80" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" 407 | dependencies = [ 408 | "quote", 409 | "wasm-bindgen-macro-support", 410 | ] 411 | 412 | [[package]] 413 | name = "wasm-bindgen-macro-support" 414 | version = "0.2.80" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" 417 | dependencies = [ 418 | "proc-macro2", 419 | "quote", 420 | "syn", 421 | "wasm-bindgen-backend", 422 | "wasm-bindgen-shared", 423 | ] 424 | 425 | [[package]] 426 | name = "wasm-bindgen-shared" 427 | version = "0.2.80" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" 430 | 431 | [[package]] 432 | name = "web-sys" 433 | version = "0.3.57" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" 436 | dependencies = [ 437 | "js-sys", 438 | "wasm-bindgen", 439 | ] 440 | 441 | [[package]] 442 | name = "weezl" 443 | version = "0.1.5" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" 446 | -------------------------------------------------------------------------------- /gifmaker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gifmaker" 3 | version = "1.0.3" 4 | authors = ["Jia Ye "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | wasm-bindgen = "0.2.80" 12 | js-sys = "0.3.57" 13 | gif = "0.11.3" 14 | png = "0.17.5" 15 | once_cell = "1.12" 16 | anyhow = "1" 17 | base64 = "0.20.0-alpha.1" 18 | magic-crypt = "3.1.10" 19 | 20 | [dependencies.web-sys] 21 | version = "0.3.57" 22 | features = [ 23 | 'Document', 24 | 'Window', 25 | ] 26 | 27 | [profile.release] 28 | lto = true 29 | opt-level = 'z' 30 | codegen-units = 1 31 | panic = 'abort' -------------------------------------------------------------------------------- /gifmaker/build.cmd: -------------------------------------------------------------------------------- 1 | :: Compile our wasm module and run `wasm-bindgen` 2 | wasm-pack build --target web -------------------------------------------------------------------------------- /gifmaker/build.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::Write}; 2 | 3 | fn main(){ 4 | let mut file = File::create("gm_check_code.rs").unwrap(); 5 | file.write_all(env!("gm_check_code").as_bytes()).unwrap(); 6 | } -------------------------------------------------------------------------------- /gifmaker/src/gifmaker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::io::{self, Write, IoSlice}; 3 | use anyhow::{anyhow, Result}; 4 | use gif::*; 5 | use crate::js::*; 6 | 7 | struct MyData{ 8 | data: Arc>>, 9 | } 10 | 11 | impl Clone for MyData{ 12 | fn clone(&self) -> Self { 13 | MyData{ 14 | data: self.data.clone() 15 | } 16 | } 17 | } 18 | 19 | impl Write for MyData{ 20 | #[inline] 21 | fn write(&mut self, buf: &[u8]) -> io::Result { 22 | self.data.lock().unwrap().extend_from_slice(buf); 23 | Ok(buf.len()) 24 | } 25 | 26 | #[inline] 27 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { 28 | let len = bufs.iter().map(|b| b.len()).sum(); 29 | let mut _self = self.data.lock().unwrap(); 30 | _self.reserve(len); 31 | for buf in bufs { 32 | _self.extend_from_slice(buf); 33 | } 34 | Ok(len) 35 | } 36 | 37 | #[inline] 38 | fn flush(&mut self) -> io::Result<()> { 39 | Ok(()) 40 | } 41 | } 42 | 43 | pub struct GifMaker{ 44 | //最终生成的文件数据 45 | data: MyData, 46 | // Encoder 47 | encoder: Encoder, 48 | fps: u16, 49 | width: u16, 50 | height: u16, 51 | } 52 | 53 | impl GifMaker{ 54 | pub fn new(width: u16, height: u16, fps: u16) -> Result{ 55 | log(&format!("GifMaker new: width={} height={} fps={}", width, height, fps)); 56 | let data = MyData{data: Arc::new(Mutex::new(vec![])) }; 57 | let mut encoder = Encoder::new(data.clone(), width, height, &[])?; 58 | encoder.set_repeat(Repeat::Infinite)?; 59 | Ok(GifMaker{ 60 | data, 61 | encoder, 62 | fps, 63 | width, 64 | height 65 | }) 66 | } 67 | 68 | // pub fn get_width(&self) -> u16{ 69 | // self.width 70 | // } 71 | 72 | // pub fn get_height(&self) -> u16{ 73 | // self.height 74 | // } 75 | 76 | pub fn add_png(&mut self, file:&[u8]) -> Result<()>{ 77 | let decoder = png::Decoder::new(file); 78 | let mut reader = decoder.read_info()?; 79 | let mut buf = vec![0; reader.output_buffer_size()]; 80 | reader.next_frame(&mut buf)?; 81 | let mut frame = gif::Frame::from_rgba_speed(self.width, self.height, &mut buf, 30); 82 | frame.delay = 1000 / self.fps / 10; //设置帧率 10ms倍数 83 | self.encoder.write_frame(&frame)?; 84 | 85 | Ok(()) 86 | } 87 | 88 | pub fn get_file(&mut self) -> Result>{ 89 | match self.data.data.lock(){ 90 | Ok(data) => Ok(data.clone()), 91 | Err(err) => Err(anyhow!("{:?}", err)) 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /gifmaker/src/js.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Object; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | #[wasm_bindgen(js_namespace = console)] 7 | pub fn log(s: &str); 8 | #[wasm_bindgen(js_namespace = console)] 9 | pub fn error(s: &str); 10 | #[wasm_bindgen(js_namespace = wx)] 11 | pub fn showModal(param: &Object); 12 | #[wasm_bindgen(js_namespace = wx)] 13 | pub fn getAccountInfoSync() -> Object; 14 | } 15 | -------------------------------------------------------------------------------- /gifmaker/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::{Mutex, MutexGuard}}; 2 | use js_sys::*; 3 | use magic_crypt::{new_magic_crypt, MagicCryptTrait}; 4 | use once_cell::sync::Lazy; 5 | use wasm_bindgen::prelude::*; 6 | mod js; 7 | mod gifmaker; 8 | use js::*; 9 | use gifmaker::*; 10 | 11 | static GIFMAKER: Lazy>> = Lazy::new(|| { 12 | Mutex::new(None) 13 | }); 14 | 15 | fn lock() -> Result>, wasm_bindgen::JsValue>{ 16 | match GIFMAKER.lock(){ 17 | Ok(gifmaker) => { 18 | Ok(gifmaker) 19 | } 20 | Err(err) => Err(JsValue::from(format!("{:?}", err))) 21 | } 22 | } 23 | 24 | /// 初始化Gif编码器 25 | #[wasm_bindgen(js_name = create)] 26 | pub fn create(width: u16, height: u16, fps: u16) -> Result<(), JsValue>{ 27 | log("开始创建Gifmaker"); 28 | let mut gifmaker = lock()?; 29 | match GifMaker::new(width, height, fps){ 30 | Ok(mk) => { 31 | gifmaker.replace(mk); 32 | log("Gifmaker创建完成"); 33 | Ok(()) 34 | } 35 | Err(err) => Err(JsValue::from(format!("{:?}", err))), 36 | } 37 | } 38 | 39 | /// 添加图片(png文件) 40 | #[wasm_bindgen(js_name = addPng)] 41 | pub fn add_png(src: Uint8ClampedArray) -> Result<(), JsValue>{ 42 | let mut gifmaker = lock()?; 43 | match gifmaker.as_mut(){ 44 | Some(gifmaker) => { 45 | if let Err(err) = gifmaker.add_png(&src.to_vec()){ 46 | Err(JsValue::from(format!("{:?}", err))) 47 | }else{ 48 | Ok(()) 49 | } 50 | } 51 | None => Err(JsValue::from("请先创建GifMaker")) 52 | } 53 | } 54 | 55 | /// 生成gif 56 | #[wasm_bindgen(js_name = getFile)] 57 | pub fn get_file() -> Result{ 58 | let mut gifmaker = lock()?; 59 | match gifmaker.as_mut(){ 60 | Some(gifmaker) => { 61 | match gifmaker.get_file(){ 62 | Ok(file) => { 63 | let arr = Uint8ClampedArray::new_with_length(file.len() as u32); 64 | arr.copy_from(&file); 65 | Ok(arr) 66 | } 67 | Err(err) => Err(JsValue::from(format!("{:?}", err))) 68 | } 69 | } 70 | None => Err(JsValue::from("请先创建GifMaker")) 71 | } 72 | } 73 | 74 | const SECRET_KEY: &str = env!("gm_secret_key"); 75 | const SECRET_IV: &str = env!("gm_secret_iv"); 76 | pub const APPID: &str = env!("gm_app_id"); 77 | 78 | /// 生成验证API网关的密钥Header 79 | #[wasm_bindgen(js_name = generateHeaders)] 80 | pub fn generate_headers(mut timestamp: f64) -> Result{ 81 | // log(&format!("generate_headers>> timestamp={timestamp}")); 82 | 83 | // 这段代码编译时请删除 84 | include_str!("../gm_check_code.rs"); 85 | 86 | let crypt = new_magic_crypt!(SECRET_KEY, 128, SECRET_IV); 87 | 88 | let current_time = format!("{}", timestamp as i64); 89 | 90 | let encrypted_time_str = crypt.encrypt_str_to_base64(current_time); 91 | 92 | // log(&format!("encrypted_time_str={encrypted_time_str}")); 93 | 94 | let map = Map::new(); 95 | map.set(&JsValue::from_str("secret"), &JsValue::from_str(&encrypted_time_str)); 96 | 97 | Ok(Object::from_entries(&map)?) 98 | } 99 | 100 | #[wasm_bindgen(start)] 101 | pub fn run() { 102 | log("start"); 103 | } -------------------------------------------------------------------------------- /program/.gitignore: -------------------------------------------------------------------------------- 1 | # Windows 2 | [Dd]esktop.ini 3 | Thumbs.db 4 | $RECYCLE.BIN/ 5 | 6 | # macOS 7 | .DS_Store 8 | .fseventsd 9 | .Spotlight-V100 10 | .TemporaryItems 11 | .Trashes 12 | 13 | # Node.js 14 | node_modules/ 15 | -------------------------------------------------------------------------------- /program/app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function () { 4 | wx.clearStorage(); 5 | }, 6 | globalData: { 7 | userDataPath: `${wx.env.USER_DATA_PATH}/`, 8 | } 9 | }) 10 | 11 | /* 12 | 版本1.0.3更新内容: 13 | 1、使用最新Rust版本、依赖库编译 14 | 2、GifMaker封装和代码分离, 减小体积 15 | 3、添加底部广告位 16 | 4、修复bug 17 | 5、调用security.imgSecCheck检查图片是否违规 18 | 版本1.0.4更新内容: 19 | 1、修复进度条 20 | 2、广告改为插屏广告 21 | 版本1.0.5内容: 22 | 修改图片校验逻辑,解决上传失败的问题。 23 | */ 24 | -------------------------------------------------------------------------------- /program/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/logs/logs" 5 | ], 6 | "window": { 7 | "backgroundTextStyle": "light", 8 | "navigationBarBackgroundColor": "#fff", 9 | "navigationBarTitleText": "大头贴动画制作", 10 | "navigationBarTextStyle": "black" 11 | }, 12 | "navigateToMiniProgramAppIdList": [ 13 | "wx69d023c4e39979c1" 14 | ], 15 | "sitemapLocation": "sitemap.json" 16 | } -------------------------------------------------------------------------------- /program/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | 12 | .img_sm{ 13 | width: 50rpx; 14 | height: 50rpx; 15 | margin:10rpx 0rpx 0rpx 10rpx; 16 | } 17 | 18 | .horizontal_center{ 19 | display:flex; 20 | justify-content: center; 21 | } 22 | 23 | .horizontal{ 24 | display:flex; 25 | } 26 | 27 | .pt10{ 28 | padding-top: 10rpx; 29 | } 30 | .p10{ 31 | padding:10rpx; 32 | } 33 | 34 | .p5{ 35 | padding-top: 5rpx; 36 | } 37 | 38 | .tp10{ 39 | padding-top: 10rpx; 40 | } 41 | 42 | .reload,.album,.trash{ 43 | width:48rpx; 44 | height:48rpx; 45 | padding: 10rpx; 46 | border-radius: 34rpx; 47 | background: white; 48 | border: 1px solid #c6c6c6; 49 | margin-top: 32rpx; 50 | position: absolute; 51 | } 52 | 53 | .link_faceoff{ 54 | padding-right: 26rpx; 55 | padding-left: 16rpx; 56 | position: absolute; 57 | right: 23rpx; 58 | margin-top: 110rpx; 59 | display: flex; 60 | border: solid 1px #c6c6c6; 61 | border-radius: 45rpx; 62 | background: linear-gradient(0deg, #dddfe0, white); 63 | } 64 | .link_faceoff image{ 65 | width: 58rpx; 66 | height: 58rpx; 67 | } 68 | .link_faceoff text{ 69 | font-size: 30rpx; 70 | padding-top: 10rpx; 71 | padding-right: 10rpx; 72 | padding-left: 10rpx; 73 | color: #4f322c; 74 | } 75 | 76 | .reload{ 77 | right: 23rpx; 78 | } 79 | 80 | .album{ 81 | right: 104rpx; 82 | } 83 | 84 | .trash{ 85 | right: 187rpx; 86 | } 87 | .trash:active, .album:active, .reload:active{ 88 | background: eee; 89 | border: 2px solid #c6c6c6; 90 | } 91 | 92 | .photo{ 93 | width: 100rpx; 94 | height: 100rpx; 95 | border:solid 2px #fefefe; 96 | border-radius: 32rpx; 97 | margin-right: 1px; 98 | } 99 | 100 | .arrow-down{ 101 | margin-left: 5px; 102 | width: 12rpx; 103 | height: 12rpx; 104 | /* background: rgba(255, 0,0,0.1); */ 105 | border-color:#969696; 106 | border-width: 0 0 4rpx 4rpx; 107 | border-style: solid; 108 | transform:rotateZ(-45deg); 109 | } 110 | 111 | .picker_text{ 112 | font-size: 26rpx; 113 | text-align: center; 114 | } 115 | .picker_text_parent{ 116 | border: 1px solid #c6c6c6; 117 | border-radius: 36rpx; 118 | display: flex; 119 | width:68rpx; 120 | height: 68rpx; 121 | align-items: center; 122 | justify-content: center; 123 | } 124 | .picker_text_w{ 125 | width:auto; 126 | min-width: 120rpx; 127 | padding-left: 10rpx; 128 | padding-right: 10rpx; 129 | } 130 | .picker{ 131 | left: 20rpx; 132 | position: absolute; 133 | margin-top: 26rpx; 134 | } 135 | .picker_text_img_size{ 136 | font-size: 24rpx; 137 | } 138 | .image_size{ 139 | margin-top: 110rpx; 140 | } 141 | 142 | .make{ 143 | position: absolute; 144 | left: 104rpx; 145 | margin-top: 26rpx; 146 | font-size: 26rpx; 147 | border: 1px solid #c6c6c6; 148 | border-radius: 36rpx; 149 | display: flex; 150 | padding: 0px; 151 | height: 68rpx; 152 | background: #fff; 153 | color: black; 154 | width: 68rpx; 155 | align-items: center; 156 | justify-content: center; 157 | } 158 | 159 | .make1{ 160 | left: 496rpx; 161 | margin-top: 110rpx; 162 | padding-left: 20rpx; 163 | padding-right: 20rpx; 164 | line-height: 60rpx; 165 | height: auto; 166 | background-color: #07c160; 167 | color: #fff; 168 | border: solid 1rpx #158a14; 169 | } 170 | .make3{ 171 | margin-top: 110rpx; 172 | left: 196rpx; 173 | } 174 | .make2{ 175 | /* font-size: 24rpx; */ 176 | margin-top: 110rpx; 177 | } 178 | .make0{ 179 | background-color: #1aad19; 180 | color: #fff; 181 | border: solid 1rpx #158a14; 182 | } 183 | .make-m{ 184 | min-width: 80rpx; 185 | left: 185rpx; 186 | } 187 | .gif{ 188 | width:100rpx; 189 | height:52rpx; 190 | left: 160rpx; 191 | position: absolute; 192 | margin-top: 60rpx; 193 | } 194 | 195 | .btn1{ 196 | width:148rpx; 197 | height: 148rpx; 198 | margin: 20rpx; 199 | border-radius: 74rpx; 200 | display: flex; 201 | margin-top: 30rpx; 202 | box-shadow:0px 0px 24rpx #e64c3c; 203 | } 204 | .make:active{ 205 | background: #eee; 206 | color: #000; 207 | } 208 | 209 | .btn1:active{ 210 | box-shadow:0px 0px 24rpx #e64c3c; 211 | opacity: 0.9; 212 | } 213 | 214 | .btn2{ 215 | width:100%; 216 | margin-left: 12rpx; 217 | margin-right: 24rpx; 218 | } 219 | 220 | .circle{ 221 | border: 1px solid #c6c6c6; 222 | border-radius: 100rpx; 223 | padding-top: 10rpx; 224 | padding-bottom: 10rpx; 225 | padding-left: 160rpx; 226 | padding-right: 160rpx; 227 | margin-top: 20rpx; 228 | color: #c6c6c6; 229 | background-color: white; 230 | } -------------------------------------------------------------------------------- /program/pages/index/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | const app = getApp(); 4 | import init, { create, addPng, getFile } from '../../utils/gifmaker/gifmaker' 5 | var imgSecCheck = require("../../utils/img_sec_check.js"); 6 | 7 | var tipId = 0; 8 | 9 | var prompt; 10 | var textColor = 'white'; 11 | 12 | var textColors = ['white', 'black', 'red', 'yellow', 'green', 'blue']; 13 | 14 | var text = ""; 15 | var bindText = ""; 16 | 17 | var canvasContext; 18 | var cameraContext; 19 | var photos = []; 20 | var tmpPhotos = []; 21 | 22 | // 在页面中定义插屏广告 23 | let interstitialAd = null 24 | let nextShowTime = 0; 25 | 26 | Page({ 27 | async onLoad() { 28 | this.showLoading("加载中"); 29 | console.log('gifmaker init...'); 30 | await init("/utils/gifmaker/gifmaker_bg.wasm"); 31 | console.log('gifmaker init ok.'); 32 | wx.hideLoading(); 33 | 34 | // 在页面onLoad回调事件中创建插屏广告实例 35 | if (wx.createInterstitialAd) { 36 | interstitialAd = wx.createInterstitialAd({ 37 | adUnitId: 'adunit-7153322d8e28fc6c' 38 | }) 39 | interstitialAd.onLoad(() => { }) 40 | interstitialAd.onError((err) => { }) 41 | interstitialAd.onClose(() => { }) 42 | } else { 43 | console.log("wx.createInterstitialAd不存在"); 44 | } 45 | canvasContext = wx.createCanvasContext('canvas'); 46 | cameraContext = wx.createCameraContext(); 47 | let fsm = wx.getFileSystemManager(); 48 | let filePath = `${wx.env.USER_DATA_PATH}/` + 'app.data'; 49 | try { 50 | let res = fsm.readFile({ 51 | filePath: filePath, 52 | encoding: "utf8", 53 | success: (res) => { 54 | console.log("临时文件读取成功", res); 55 | var data = JSON.parse(res.data); 56 | data.photos = []; 57 | data.showPreview = "false"; 58 | this.setData(data); 59 | }, 60 | fail: (res)=> { 61 | //console.log('临时文件读取失败!', res); 62 | } 63 | }); 64 | } catch (e) { } 65 | }, 66 | onHide: function(){ 67 | }, 68 | 69 | onCameraError(err){ 70 | this.setData({ 71 | cameraError: true 72 | }); 73 | wx.showToast({ 74 | icon: 'none', 75 | title: '相机开启失败', 76 | }); 77 | console.log('onCameraError>>', err); 78 | }, 79 | 80 | jumpToFaceOff: function(){ 81 | wx.navigateToMiniProgram({ 82 | appId: 'wx69d023c4e39979c1', 83 | path: 'page/index/index', 84 | // extraData: { 85 | // foo: 'bar' 86 | // }, 87 | // envVersion: 'develop', 88 | success:(res)=> { 89 | // 打开成功 90 | } 91 | }) 92 | }, 93 | 94 | saveData: function(){ 95 | let fsm = wx.getFileSystemManager(); 96 | let filePath = `${wx.env.USER_DATA_PATH}/` + 'app.data'; 97 | try { 98 | let res = fsm.writeFile({ 99 | filePath: filePath, data: JSON.stringify(this.data), 100 | success: (res)=> { 101 | //console.log("临时文件保存成功", res); 102 | }, 103 | fail: (res)=> { 104 | //console.log('临时文件保存失败!', res); 105 | } 106 | }); 107 | } catch (e) { } 108 | }, 109 | showInputText: function(){ 110 | this.setData({ isInputTextHidden: false }); 111 | }, 112 | bindText: function(e){ 113 | bindText = e.detail.value; 114 | }, 115 | setText: function(){ 116 | if(!bindText || bindText.length<=0){ 117 | this.setData({ isInputTextHidden: true}); 118 | return; 119 | } 120 | this.showLoading("正在验证文本"); 121 | imgSecCheck.checkText(bindText).then(()=>{ 122 | wx.hideLoading(); 123 | text = bindText; 124 | console.log("文本:", text); 125 | this.setData({ isInputTextHidden: true}); 126 | }).catch(()=>{ 127 | wx.hideLoading(); 128 | wx.showModal({ 129 | content: '文字存在敏感内容,请重新填写', 130 | showCancel: false, 131 | confirmText: '我知道了' 132 | }); 133 | }) 134 | }, 135 | clearText(){ 136 | text = ""; 137 | bindText = ''; 138 | this.setData({ textContent: '', isInputTextHidden: true }); 139 | }, 140 | // onShareAppMessage: function (res) { 141 | // return { 142 | // title: '大头贴动画制作', 143 | // imageUrl: "/static/basicprofile.png" 144 | // } 145 | // }, 146 | showLoading: function(title){ 147 | wx.showLoading({ 148 | title: title, 149 | mask: false, 150 | }); 151 | }, 152 | showError: function(msg){ 153 | wx.showModal({ 154 | title: '错误', 155 | content: msg, 156 | }); 157 | }, 158 | data: { 159 | textContent: '', 160 | tipPreview: '点击“预览”按钮后,长按GIF图片可保存至本地相册', 161 | cameraError: false, 162 | // showShare: false, 163 | finishGifPath: null, 164 | isInputTextHidden: true, 165 | cam_position: '前置', 166 | btnDisabled: false, 167 | showPreview: "false", 168 | image_count: 0, 169 | fps: '4帧', 170 | fps_id: 4, 171 | fpsArray: ['1帧/秒', '2帧/秒', '3帧/秒', '4帧/秒', '5帧/秒', '6帧/秒', '7帧/秒', '8帧/秒', '9帧/秒', '10帧/秒', '11帧/秒', '12帧/秒'], 172 | imageSize: 300, 173 | imgSize: '300px', 174 | imgSizeId: 6, 175 | imgSizeArray: ["图宽50px", "图宽80px", "图宽100px", "图宽150px", "图宽200px", "图宽250px", "图宽300px", "图宽350px", "图宽400px", "图宽450px"], 176 | previewMode: "scaleToFill", 177 | textColorId: 0, 178 | textColor: '白色', 179 | textColorArray: ['白色', '黑色', '红色', '黄色', '绿色', '蓝色'], 180 | photos: [], 181 | tool_tip: '点击拍照按钮添加照片' 182 | }, 183 | previewGif: function(){ 184 | if (this.data.showPreview == "") { 185 | this.closePreviewDialog(); 186 | return; 187 | } 188 | //如果已经生成过gif,直接预览 189 | if(this.data.finishGifPath){ 190 | this.showPreviewDialog(); 191 | }else{ 192 | wx.showModal({ 193 | title: '提示', 194 | content: "请先制作Gif", 195 | showCancel: false, 196 | }); 197 | } 198 | }, 199 | createGif: function(){ 200 | let count = photos.length; 201 | if(count <= 1){ 202 | var msg = "至少拍摄两张照片"; 203 | if (count == 0) { 204 | msg = "请先拍摄照片"; 205 | } 206 | wx.showToast({icon:'none', title: msg, }); 207 | return; 208 | } 209 | 210 | tmpPhotos.length = 0; 211 | this.addImage(()=>{ 212 | //生成gif 213 | this.showLoading("初始化GIF..."); 214 | create(this.data.imageSize, this.data.imageSize, parseInt(this.data.fps.replace('帧', ''))); 215 | for(var i=0; i { 251 | this.setData({ finishGifPath: filePath, photos }); 252 | this.showPreviewDialog(); 253 | tmpPhotos.length = 0; 254 | }, 255 | fail: (res) => { 256 | wx.hideLoading(); 257 | this.showError('临时文件保存失败!' + JSON.stringify(res)); 258 | }, 259 | complete: (res)=> {} 260 | }); 261 | } catch (e) { 262 | wx.hideLoading(); 263 | this.showError('图片读取失败!' + JSON.stringify(e)); 264 | } 265 | }); 266 | }, 267 | clearImage: function(){ 268 | photos.length = 0; 269 | this.setData({ photos: photos, finishGifPath: null, }); 270 | this.saveData(); 271 | //清空文件 272 | let filePath = `${wx.env.USER_DATA_PATH}/` + 'create.gif'; 273 | let fsm = wx.getFileSystemManager(); 274 | fsm.unlink({ 275 | filePath, 276 | success:(res)=>{ 277 | console.log('文件删除成功:', res); 278 | }, 279 | }); 280 | wx.showToast({icon:'none', title: '文件已删除', }); 281 | }, 282 | //一次性给GIF制作器添加所有图片 283 | addImage: function(cb){ 284 | //添加一张照片 285 | if(tmpPhotos.length == photos.length){ 286 | cb(); 287 | return; 288 | } 289 | //选出当前要添加的图片 290 | var nextId = tmpPhotos.length; 291 | var photo = photos[nextId]; 292 | this.showLoading("保存图片" + tmpPhotos.length + "/" + photos.length); 293 | wx.getImageInfo({ 294 | src: photo.path, 295 | success:(res)=> { 296 | let width = this.data.imageSize; 297 | let height = this.data.imageSize / res.width * res.height; 298 | let top = (this.data.imageSize - height) / 2; 299 | canvasContext.drawImage(photo.path, 0, top, width, height); 300 | canvasContext.setFillStyle(textColor); 301 | canvasContext.setFontSize(this.data.imageSize / 8); 302 | canvasContext.fillText(text, 10, this.data.imageSize - (this.data.imageSize / 7), this.data.imageSize); 303 | 304 | canvasContext.draw(false, ()=> { 305 | var fileType = 'png'; 306 | var obj = { 307 | x: 0, 308 | y: 0, 309 | width: this.data.imageSize, 310 | height: this.data.imageSize, 311 | destWidth: this.data.imageSize, 312 | destHeight: this.data.imageSize, 313 | canvasId: 'canvas', 314 | fileType: fileType, 315 | success: (res)=> { 316 | // console.log("图片保存成功:", res); 317 | var tmpPath = res.tempFilePath; 318 | var savePath = `${wx.env.USER_DATA_PATH}/` + 'gen' + nextId + '.' + fileType; 319 | wx.getFileSystemManager().saveFile({ 320 | tempFilePath: tmpPath, 321 | filePath: savePath, 322 | success:(res)=> { 323 | tmpPhotos.push(res.savedFilePath); 324 | console.log("图片文件保存成功:", res.savedFilePath); 325 | //保存下一张 326 | this.addImage(cb); 327 | } 328 | }); 329 | }, 330 | fail: (res)=> { 331 | this.showError('图片保存失败,请重试!'); 332 | } 333 | }; 334 | wx.canvasToTempFilePath(obj); 335 | 336 | }); 337 | }, 338 | fail: (res)=>{ 339 | wx.hideLoading(); 340 | tmpPhotos.length = 0; 341 | this.showError('图片保存失败,请重试!'); 342 | this.clearImage(); 343 | } 344 | }); 345 | }, 346 | //从相册选择照片 347 | chooseImage: function(){ 348 | wx.chooseMedia({ 349 | count: 1, 350 | sizeType: ['compressed'], 351 | mediaType: ['image'], 352 | sourceType: ['album'], 353 | maxDuration: 30, 354 | camera: 'back', 355 | success: res => { 356 | console.log('图片选择完成:', res); 357 | if (res.tempFiles && res.tempFiles.length>0){ 358 | var filePath = res.tempFiles[0].tempFilePath; 359 | this.checkAddImage(filePath); 360 | } 361 | } 362 | }); 363 | }, 364 | //切换前后摄像头 365 | changeCamera: function(){ 366 | let curPos = this.data.cam_position; 367 | if(curPos=='front'){ 368 | this.setData({ cam_position: 'back'}); 369 | }else{ 370 | this.setData({ cam_position: 'front' }); 371 | } 372 | }, 373 | checkAddImage(srcFilePath, onFinish){ 374 | console.log('checkAddImage之前 photos:', JSON.stringify(photos)); 375 | //审查图片 376 | this.showLoading('正在验证图片'); 377 | 378 | //压缩图片 379 | const filePath = `${wx.env.USER_DATA_PATH}/` + 'small_image.jpg'; 380 | 381 | let checkOk = ()=>{ 382 | wx.hideLoading(); 383 | photos.push({ path: srcFilePath, valid: true, }); 384 | console.log('checkAddImage之前 photos:', JSON.stringify(photos)); 385 | this.setData({ photos }); 386 | if(onFinish){ onFinish()}; 387 | }; 388 | 389 | let checkError = (e)=>{ 390 | if(onFinish){ onFinish()}; 391 | wx.hideLoading(); 392 | console.error('图片审查失败', e); 393 | wx.showModal({ 394 | title: '提示', 395 | content: "图片审查失败,请更换", 396 | showCancel: false, 397 | }); 398 | }; 399 | 400 | this.resizeImageTo(srcFilePath, filePath, 'jpg').then(() => { 401 | imgSecCheck.checkImage(filePath).then(()=>{ 402 | checkOk(); 403 | }).catch(e => { 404 | console.error('checkImage=>', e); 405 | checkError(); 406 | }); 407 | }).catch(e => { 408 | console.error('resizeImageTo', e); 409 | checkError(); 410 | }) 411 | }, 412 | takePhoto: function(){ 413 | //禁止拍照按钮 414 | this.setData({ btnDisabled: true}); 415 | cameraContext.takePhoto({ 416 | quality: 'normal', 417 | success: (res) => { 418 | this.checkAddImage(res.tempImagePath, ()=>{ 419 | this.setData({ btnDisabled: false }); 420 | var strs = [ 421 | '微微移动相机、调高帧率,使动画更流畅', 422 | '如果无法拍照,请退出小程序并重新进入']; 423 | var change = Math.random() < 0.3; 424 | if (change) { 425 | let rand = Math.random(); 426 | if (rand < 0.4) { 427 | tipId = 0; 428 | } else if (rand > 0.4 && rand < 0.8) { 429 | tipId = 1; 430 | } else { 431 | tipId = 2; 432 | } 433 | } 434 | this.setData({ tool_tip: strs[tipId] }); 435 | }); 436 | }, 437 | fail: (res)=> { 438 | this.setData({ btnDisabled: false }); 439 | this.showError('拍照失败!' + JSON.stringify(res)); 440 | } 441 | }); 442 | }, 443 | //事件处理函数 444 | bindFpsChange: function(res) { 445 | this.setData({ fps_id: res.detail.value }); 446 | this.setData({fps: this.data.fpsArray[res.detail.value].replace('/秒', '')}); 447 | }, 448 | bindImgSizeChange: function(res){ 449 | this.data.imageSize = parseInt(this.data.imgSizeArray[res.detail.value].replace('图宽', '').replace('px', '')); 450 | this.setData({ imgSizeId: res.detail.value, imgSize: this.data.imageSize+'px' }); 451 | }, 452 | bindTextColorChange: function(res){ 453 | textColor = textColors[res.detail.value]; 454 | this.setData({ textColor: this.data.textColorArray[res.detail.value], textColorId: res.detail.value }); 455 | }, 456 | onClose: function(){ 457 | nextShowTime = 0; 458 | }, 459 | onShow: function(){ 460 | console.log('cameraContext=', cameraContext); 461 | if(!cameraContext){ 462 | cameraContext = wx.createCameraContext(); 463 | } 464 | this.setData({ btnDisabled: false }); 465 | console.log("nextShowTime=", nextShowTime); 466 | //1分钟显示一次广告 467 | if (this.data.finishGifPath && new Date().getTime() > nextShowTime) { 468 | if (interstitialAd) { 469 | nextShowTime = new Date().getTime() + 1000 * 60; 470 | interstitialAd.show().catch((err) => { 471 | nextShowTime -= 1000 * 60; 472 | console.error(err) 473 | }); 474 | } 475 | } else { 476 | if (!this.data.finishGifPath) { 477 | console.log("用户未使用,不显示广告"); 478 | } else { 479 | console.log("时间未到,不显示广告"); 480 | } 481 | } 482 | }, 483 | 484 | showPreviewDialog: function(){ 485 | // this.showLoading("读取图片"); 486 | //这里有缓存 487 | //this.setData({ previewMode: previewMode, showPreview: "", previewGifPath: this.data.finishGifPath+"?"+Math.random()}); 488 | this.previewImage(); 489 | // const base64 = wx.arrayBufferToBase64(res.data); 490 | }, 491 | closePreviewDialog: function(){ 492 | this.setData({ showPreview: "false" }); 493 | }, 494 | previewImage: function(){ 495 | this.showLoading("正在预览"); 496 | wx.previewImage({ 497 | urls: [this.data.finishGifPath], 498 | complete: function(){ 499 | wx.hideLoading(); 500 | } 501 | }); 502 | }, 503 | deletePhoto: function(event){ 504 | var path = event.target.dataset.id; 505 | var del_id = -1; 506 | for(var p in photos){ 507 | if(photos[p].path == path){ 508 | del_id = p; 509 | break; 510 | } 511 | } 512 | photos.splice(del_id, 1); 513 | this.setData({ photos: photos }); 514 | }, 515 | 516 | // showShare: function () { 517 | // this.setData({showShare: true }); 518 | // }, 519 | // hideShare: function () { 520 | // this.setData({showShare: false }); 521 | // }, 522 | requestCameraAuth(){ 523 | wx.openSetting({ 524 | success: (res)=> { 525 | if(res.authSetting['scope.camera']){ 526 | this.setData({ cameraError:false}) 527 | } 528 | } 529 | }); 530 | }, 531 | closeTipPreview(){ 532 | wx.showModal({ 533 | title: '提示', 534 | confirmText: '不再提示', 535 | content: this.data.tipPreview, 536 | success: (res)=> { 537 | if (res.confirm) { 538 | this.setData({ 539 | hideTipPreview: true, 540 | }); 541 | this.saveData(); 542 | } 543 | } 544 | }); 545 | }, 546 | //压缩图片 547 | resizeImageTo(path, savePath, fileType){ 548 | return new Promise((resolve, reject) => { 549 | wx.getImageInfo({ 550 | src: path, 551 | success:(res)=> { 552 | let width = this.data.imageSize; 553 | let height = this.data.imageSize / res.width * res.height; 554 | let top = (this.data.imageSize - height) / 2; 555 | canvasContext.drawImage(path, 0, top, width, height); 556 | canvasContext.setFillStyle(textColor); 557 | canvasContext.setFontSize(this.data.imageSize / 8); 558 | canvasContext.fillText(text, 10, this.data.imageSize - (this.data.imageSize / 7), this.data.imageSize); 559 | 560 | canvasContext.draw(false, ()=> { 561 | var obj = { 562 | x: 0, 563 | y: 0, 564 | width: this.data.imageSize, 565 | height: this.data.imageSize, 566 | destWidth: this.data.imageSize, 567 | destHeight: this.data.imageSize, 568 | canvasId: 'canvas', 569 | fileType: fileType, 570 | success: (res)=> { 571 | // console.log("图片保存成功:", res); 572 | var tmpPath = res.tempFilePath; 573 | wx.getFileSystemManager().saveFile({ 574 | tempFilePath: tmpPath, 575 | filePath: savePath, 576 | success:(res)=> { 577 | console.error('resizeImageTo成功', res); 578 | resolve(); 579 | } 580 | }); 581 | }, 582 | fail: (res)=> { 583 | console.error('resizeImageTo 出错', res); 584 | reject(); 585 | } 586 | }; 587 | wx.canvasToTempFilePath(obj); 588 | }); 589 | }, 590 | fail: (res)=>{ 591 | console.error('resizeImageTo 出错', res); 592 | reject(); 593 | } 594 | }); 595 | }); 596 | }, 597 | 598 | goMiniprogram(){ 599 | wx.navigateToMiniProgram({ 600 | appId: 'wxf6f4b4ee979ccb85', 601 | path: 'pages/mine/walkinCoupon/walkinCoupon?storeId=9&couponId=1240', 602 | // envVersion: 'develop', 603 | success(res) { 604 | // 打开成功 605 | } 606 | }) 607 | }, 608 | }) -------------------------------------------------------------------------------- /program/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "disableScroll": false 3 | } -------------------------------------------------------------------------------- /program/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 相机开启失败,请到设置中勾选“摄像头”允许使用相机 9 | 10 | 11 | 12 | 13 | {{tool_tip}} 14 | 15 | 16 | 速度:{{fps}} 17 | 18 | 制作 19 | 预览 20 | 21 | 22 | 23 | 大小:{{imgSize}} 24 | 25 | 文字 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 57 | 58 | 59 | {{ tipPreview }}不再提示 60 | 61 | 62 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /program/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | 3 | .text_color{ 4 | font-size: 32rpx; 5 | } 6 | 7 | .picker-arrow{ 8 | width: 0; 9 | height: 0; 10 | border-width: 20rpx; 11 | border-style: solid; 12 | border-color: #c6c6c6 transparent transparent transparent; 13 | margin-top: 20rpx; 14 | } 15 | .picker-text-parent{ 16 | border: 1px solid #c6c6c6; 17 | border-radius: 20rpx; 18 | display: flex; 19 | padding-left: 20rpx; 20 | padding-right: 20rpx; 21 | } 22 | 23 | .input-view input{ 24 | margin-top: 30rpx; 25 | margin-bottom: 30rpx; 26 | } 27 | 28 | .input-view{ 29 | line-height: 70rpx; 30 | font-size: 18px; 31 | position: absolute; 32 | top:100vw; 33 | bottom: 100vh; 34 | width:90%; 35 | height: 330rpx; 36 | z-index: 20; 37 | border: solid 1px #b2b2b2; 38 | background: rgb(253, 253, 253); 39 | box-shadow:0px 0px 24rpx #333333; 40 | margin-left: 3%; 41 | margin-right: 3%; 42 | margin-top: 2%; 43 | border-radius: 20rpx; 44 | padding-top: 50rpx; 45 | padding-left: 40rpx; 46 | } 47 | 48 | .test-img{ 49 | position: absolute; 50 | left: 70rpx; 51 | top: 80rpx; 52 | } 53 | 54 | .preview-dialog{ 55 | position: absolute; 56 | left: 70rpx; 57 | top: 80rpx; 58 | background: rgb(253, 253, 253); 59 | /* position: absolute; */ 60 | /* left: 50%; */ 61 | /* top: 95%; */ 62 | border: solid 1px #b2b2b2; 63 | border-radius: 20rpx; 64 | width: 600rpx; 65 | height: 660rpx; 66 | margin: 30rpx auto; 67 | /* margin-left: -300rpx; */ 68 | box-shadow:0px 0px 24rpx #333333; 69 | } 70 | 71 | .preview-dialog image,cover-image{ 72 | width: 300rpx; 73 | height: 300rpx; 74 | margin-left: 150rpx; 75 | } 76 | 77 | .preview-dialog .buttons{ 78 | display: flex; 79 | 80 | } 81 | 82 | .preview-dialog .text{ 83 | display: block; 84 | text-align: center; 85 | padding: 0rpx; 86 | margin: 0rpx; 87 | } 88 | 89 | .preview-dialog .title{ 90 | font-size: 36rpx; 91 | line-height: 100rpx; 92 | padding: 0rpx; 93 | margin: 0rpx; 94 | } 95 | 96 | .preview-dialog .tip{ 97 | color: #999999; 98 | font-size: 36rpx; 99 | line-height: 70rpx; 100 | } 101 | 102 | .preview-dialog button{ 103 | width: 240rpx; 104 | } 105 | 106 | .preview-dialog button.preview{ 107 | color: #3ec621; 108 | } 109 | 110 | .shaer-app{ 111 | position: absolute; 112 | top: 150rpx; 113 | left: 22rpx; 114 | display: block; 115 | width: 710rpx; 116 | background: #fff; 117 | height: 810rpx; 118 | z-index: 9; 119 | box-shadow:0px 0px 24rpx #333333; 120 | border-radius: 70rpx; 121 | border: 1px solid #c6c6c6; 122 | } 123 | .shaer-app cover-image{ 124 | width: 500rpx; 125 | height: 500rpx; 126 | margin-left: 100rpx; 127 | margin-top: 10%; 128 | } 129 | .shaer-app .share-tip{ 130 | display: block; 131 | padding: 10rpx; 132 | text-align: center; 133 | } 134 | .shaer-app .about{ 135 | display: block; 136 | text-align: center; 137 | font-size: 30rpx; 138 | line-height: 60rpx; 139 | color: #ccc; 140 | } 141 | .shaer-app button{ 142 | display: block; 143 | min-width: 260rpx; 144 | } 145 | 146 | .vtip{ 147 | font-size: 30.5rpx; 148 | line-height: 140%; 149 | background-color: #ff976a; 150 | color: white; 151 | border-radius: 10rpx; 152 | margin-left: 20rpx; 153 | margin-right: 20rpx; 154 | margin-top: 20rpx; 155 | padding: 20rpx; 156 | text-align: center; 157 | } 158 | 159 | .vtip image{ 160 | width: 18rpx; 161 | height: 18rpx; 162 | margin-left: 6rpx; 163 | margin-right: 6rpx; 164 | } 165 | 166 | .tv-close{ 167 | background-color: white; 168 | color: #333333; 169 | padding: 6rpx; 170 | border-radius: 5rpx; 171 | line-height: 30rpx; 172 | height: 30rpx; 173 | margin-left: 20rpx; 174 | margin-right: 20rpx; 175 | font-size: 26rpx; 176 | margin-top: 10rpx; 177 | display: inline-block; 178 | } 179 | 180 | .cover-view{ 181 | width:100vw; 182 | height:100vw; 183 | display: flex; 184 | background-color: #f7f7f7; 185 | justify-content: center; 186 | align-items: center; 187 | } 188 | 189 | .cover-view text{ 190 | font-size: 32rpx; 191 | color: #3ec621; 192 | display: block; 193 | padding: 30rpx; 194 | margin-left: 30rpx; 195 | margin-right: 30rpx; 196 | text-align: center; 197 | } 198 | 199 | .cover-view button{ 200 | width: 50%; 201 | } -------------------------------------------------------------------------------- /program/pages/logs/logs.js: -------------------------------------------------------------------------------- 1 | //logs.js 2 | const util = require('../../utils/util.js') 3 | 4 | Page({ 5 | data: { 6 | logs: [] 7 | }, 8 | onLoad: function () { 9 | this.setData({ 10 | logs: (wx.getStorageSync('logs') || []).map(log => { 11 | return util.formatTime(new Date(log)) 12 | }) 13 | }) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /program/pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志" 3 | } -------------------------------------------------------------------------------- /program/pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{index + 1}}. {{log}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /program/pages/logs/logs.wxss: -------------------------------------------------------------------------------- 1 | .log-list { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 40rpx; 5 | } 6 | .log-item { 7 | margin: 10rpx; 8 | } 9 | -------------------------------------------------------------------------------- /program/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "packOptions": { 4 | "ignore": [], 5 | "include": [] 6 | }, 7 | "setting": { 8 | "urlCheck": true, 9 | "es6": false, 10 | "enhance": true, 11 | "postcss": true, 12 | "preloadBackgroundData": false, 13 | "minified": true, 14 | "newFeature": false, 15 | "coverView": true, 16 | "nodeModules": false, 17 | "autoAudits": false, 18 | "showShadowRootInWxmlPanel": true, 19 | "scopeDataCheck": false, 20 | "uglifyFileName": false, 21 | "checkInvalidKey": true, 22 | "checkSiteMap": true, 23 | "uploadWithSourceMap": true, 24 | "compileHotReLoad": false, 25 | "lazyloadPlaceholderEnable": false, 26 | "useMultiFrameRuntime": false, 27 | "useApiHook": false, 28 | "useApiHostProcess": false, 29 | "babelSetting": { 30 | "ignore": [], 31 | "disablePlugins": [], 32 | "outputPath": "" 33 | }, 34 | "useIsolateContext": false, 35 | "userConfirmedBundleSwitch": false, 36 | "packNpmManually": false, 37 | "packNpmRelationList": [], 38 | "minifyWXSS": true, 39 | "disableUseStrict": false, 40 | "minifyWXML": true, 41 | "showES6CompileOption": false, 42 | "useCompilerPlugins": false, 43 | "ignoreUploadUnusedFiles": true 44 | }, 45 | "compileType": "miniprogram", 46 | "libVersion": "2.22.1", 47 | "appid": "wx6c68f8045539a828", 48 | "projectname": "wxmini", 49 | "simulatorType": "wechat", 50 | "simulatorPluginLibVersion": {}, 51 | "editorSetting": { 52 | "tabIndent": "insertSpaces", 53 | "tabSize": 2 54 | }, 55 | "condition": {} 56 | } -------------------------------------------------------------------------------- /program/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": { 3 | "urlCheck": false 4 | }, 5 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html" 6 | } -------------------------------------------------------------------------------- /program/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /program/static/album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/album.png -------------------------------------------------------------------------------- /program/static/basicprofile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/basicprofile.png -------------------------------------------------------------------------------- /program/static/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/camera.png -------------------------------------------------------------------------------- /program/static/campos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/campos.png -------------------------------------------------------------------------------- /program/static/code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/code.jpg -------------------------------------------------------------------------------- /program/static/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/delete.png -------------------------------------------------------------------------------- /program/static/faceoff_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/faceoff_logo.png -------------------------------------------------------------------------------- /program/static/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/ic_close.png -------------------------------------------------------------------------------- /program/static/ic_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/ic_img.png -------------------------------------------------------------------------------- /program/static/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/static/trash.png -------------------------------------------------------------------------------- /program/utils/encoding.js: -------------------------------------------------------------------------------- 1 | // This is free and unencumbered software released into the public domain. 2 | // See LICENSE.md for more information. 3 | 4 | /** 5 | * @fileoverview Global |this| required for resolving indexes in node. 6 | * @suppress {globalThis} 7 | */ 8 | (function(global) { 9 | 'use strict'; 10 | 11 | // see https://github.com/inexorabletash/text-encoding/blob/master/lib/encoding-indexes.js 12 | // If we're in node require encoding-indexes and attach it to the global. 13 | // if (typeof module !== "undefined" && module.exports && 14 | // !global["encoding-indexes"]) { 15 | // global["encoding-indexes"] = 16 | // require("./encoding-indexes.js")["encoding-indexes"]; 17 | // } 18 | 19 | // 20 | // Utilities 21 | // 22 | 23 | /** 24 | * @param {number} a The number to test. 25 | * @param {number} min The minimum value in the range, inclusive. 26 | * @param {number} max The maximum value in the range, inclusive. 27 | * @return {boolean} True if a >= min and a <= max. 28 | */ 29 | function inRange(a, min, max) { 30 | return min <= a && a <= max; 31 | } 32 | 33 | /** 34 | * @param {!Array.<*>} array The array to check. 35 | * @param {*} item The item to look for in the array. 36 | * @return {boolean} True if the item appears in the array. 37 | */ 38 | function includes(array, item) { 39 | return array.indexOf(item) !== -1; 40 | } 41 | 42 | var floor = Math.floor; 43 | 44 | /** 45 | * @param {*} o 46 | * @return {Object} 47 | */ 48 | function ToDictionary(o) { 49 | if (o === undefined) return {}; 50 | if (o === Object(o)) return o; 51 | throw TypeError('Could not convert argument to dictionary'); 52 | } 53 | 54 | /** 55 | * @param {string} string Input string of UTF-16 code units. 56 | * @return {!Array.} Code points. 57 | */ 58 | function stringToCodePoints(string) { 59 | // https://heycam.github.io/webidl/#dfn-obtain-unicode 60 | 61 | // 1. Let S be the DOMString value. 62 | var s = String(string); 63 | 64 | // 2. Let n be the length of S. 65 | var n = s.length; 66 | 67 | // 3. Initialize i to 0. 68 | var i = 0; 69 | 70 | // 4. Initialize U to be an empty sequence of Unicode characters. 71 | var u = []; 72 | 73 | // 5. While i < n: 74 | while (i < n) { 75 | 76 | // 1. Let c be the code unit in S at index i. 77 | var c = s.charCodeAt(i); 78 | 79 | // 2. Depending on the value of c: 80 | 81 | // c < 0xD800 or c > 0xDFFF 82 | if (c < 0xD800 || c > 0xDFFF) { 83 | // Append to U the Unicode character with code point c. 84 | u.push(c); 85 | } 86 | 87 | // 0xDC00 ≤ c ≤ 0xDFFF 88 | else if (0xDC00 <= c && c <= 0xDFFF) { 89 | // Append to U a U+FFFD REPLACEMENT CHARACTER. 90 | u.push(0xFFFD); 91 | } 92 | 93 | // 0xD800 ≤ c ≤ 0xDBFF 94 | else if (0xD800 <= c && c <= 0xDBFF) { 95 | // 1. If i = n−1, then append to U a U+FFFD REPLACEMENT 96 | // CHARACTER. 97 | if (i === n - 1) { 98 | u.push(0xFFFD); 99 | } 100 | // 2. Otherwise, i < n−1: 101 | else { 102 | // 1. Let d be the code unit in S at index i+1. 103 | var d = s.charCodeAt(i + 1); 104 | 105 | // 2. If 0xDC00 ≤ d ≤ 0xDFFF, then: 106 | if (0xDC00 <= d && d <= 0xDFFF) { 107 | // 1. Let a be c & 0x3FF. 108 | var a = c & 0x3FF; 109 | 110 | // 2. Let b be d & 0x3FF. 111 | var b = d & 0x3FF; 112 | 113 | // 3. Append to U the Unicode character with code point 114 | // 2^16+2^10*a+b. 115 | u.push(0x10000 + (a << 10) + b); 116 | 117 | // 4. Set i to i+1. 118 | i += 1; 119 | } 120 | 121 | // 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a 122 | // U+FFFD REPLACEMENT CHARACTER. 123 | else { 124 | u.push(0xFFFD); 125 | } 126 | } 127 | } 128 | 129 | // 3. Set i to i+1. 130 | i += 1; 131 | } 132 | 133 | // 6. Return U. 134 | return u; 135 | } 136 | 137 | /** 138 | * @param {!Array.} code_points Array of code points. 139 | * @return {string} string String of UTF-16 code units. 140 | */ 141 | function codePointsToString(code_points) { 142 | var s = ''; 143 | for (var i = 0; i < code_points.length; ++i) { 144 | var cp = code_points[i]; 145 | if (cp <= 0xFFFF) { 146 | s += String.fromCharCode(cp); 147 | } else { 148 | cp -= 0x10000; 149 | s += String.fromCharCode((cp >> 10) + 0xD800, 150 | (cp & 0x3FF) + 0xDC00); 151 | } 152 | } 153 | return s; 154 | } 155 | 156 | 157 | // 158 | // Implementation of Encoding specification 159 | // https://encoding.spec.whatwg.org/ 160 | // 161 | 162 | // 163 | // 4. Terminology 164 | // 165 | 166 | /** 167 | * An ASCII byte is a byte in the range 0x00 to 0x7F, inclusive. 168 | * @param {number} a The number to test. 169 | * @return {boolean} True if a is in the range 0x00 to 0x7F, inclusive. 170 | */ 171 | function isASCIIByte(a) { 172 | return 0x00 <= a && a <= 0x7F; 173 | } 174 | 175 | /** 176 | * An ASCII code point is a code point in the range U+0000 to 177 | * U+007F, inclusive. 178 | */ 179 | var isASCIICodePoint = isASCIIByte; 180 | 181 | 182 | /** 183 | * End-of-stream is a special token that signifies no more tokens 184 | * are in the stream. 185 | * @const 186 | */ var end_of_stream = -1; 187 | 188 | /** 189 | * A stream represents an ordered sequence of tokens. 190 | * 191 | * @constructor 192 | * @param {!(Array.|Uint8Array)} tokens Array of tokens that provide 193 | * the stream. 194 | */ 195 | function Stream(tokens) { 196 | /** @type {!Array.} */ 197 | this.tokens = [].slice.call(tokens); 198 | // Reversed as push/pop is more efficient than shift/unshift. 199 | this.tokens.reverse(); 200 | } 201 | 202 | Stream.prototype = { 203 | /** 204 | * @return {boolean} True if end-of-stream has been hit. 205 | */ 206 | endOfStream: function() { 207 | return !this.tokens.length; 208 | }, 209 | 210 | /** 211 | * When a token is read from a stream, the first token in the 212 | * stream must be returned and subsequently removed, and 213 | * end-of-stream must be returned otherwise. 214 | * 215 | * @return {number} Get the next token from the stream, or 216 | * end_of_stream. 217 | */ 218 | read: function() { 219 | if (!this.tokens.length) 220 | return end_of_stream; 221 | return this.tokens.pop(); 222 | }, 223 | 224 | /** 225 | * When one or more tokens are prepended to a stream, those tokens 226 | * must be inserted, in given order, before the first token in the 227 | * stream. 228 | * 229 | * @param {(number|!Array.)} token The token(s) to prepend to the 230 | * stream. 231 | */ 232 | prepend: function(token) { 233 | if (Array.isArray(token)) { 234 | var tokens = /**@type {!Array.}*/(token); 235 | while (tokens.length) 236 | this.tokens.push(tokens.pop()); 237 | } else { 238 | this.tokens.push(token); 239 | } 240 | }, 241 | 242 | /** 243 | * When one or more tokens are pushed to a stream, those tokens 244 | * must be inserted, in given order, after the last token in the 245 | * stream. 246 | * 247 | * @param {(number|!Array.)} token The tokens(s) to push to the 248 | * stream. 249 | */ 250 | push: function(token) { 251 | if (Array.isArray(token)) { 252 | var tokens = /**@type {!Array.}*/(token); 253 | while (tokens.length) 254 | this.tokens.unshift(tokens.shift()); 255 | } else { 256 | this.tokens.unshift(token); 257 | } 258 | } 259 | }; 260 | 261 | // 262 | // 5. Encodings 263 | // 264 | 265 | // 5.1 Encoders and decoders 266 | 267 | /** @const */ 268 | var finished = -1; 269 | 270 | /** 271 | * @param {boolean} fatal If true, decoding errors raise an exception. 272 | * @param {number=} opt_code_point Override the standard fallback code point. 273 | * @return {number} The code point to insert on a decoding error. 274 | */ 275 | function decoderError(fatal, opt_code_point) { 276 | if (fatal) 277 | throw TypeError('Decoder error'); 278 | return opt_code_point || 0xFFFD; 279 | } 280 | 281 | /** 282 | * @param {number} code_point The code point that could not be encoded. 283 | * @return {number} Always throws, no value is actually returned. 284 | */ 285 | function encoderError(code_point) { 286 | throw TypeError('The code point ' + code_point + ' could not be encoded.'); 287 | } 288 | 289 | /** @interface */ 290 | function Decoder() {} 291 | Decoder.prototype = { 292 | /** 293 | * @param {Stream} stream The stream of bytes being decoded. 294 | * @param {number} bite The next byte read from the stream. 295 | * @return {?(number|!Array.)} The next code point(s) 296 | * decoded, or null if not enough data exists in the input 297 | * stream to decode a complete code point, or |finished|. 298 | */ 299 | handler: function(stream, bite) {} 300 | }; 301 | 302 | /** @interface */ 303 | function Encoder() {} 304 | Encoder.prototype = { 305 | /** 306 | * @param {Stream} stream The stream of code points being encoded. 307 | * @param {number} code_point Next code point read from the stream. 308 | * @return {(number|!Array.)} Byte(s) to emit, or |finished|. 309 | */ 310 | handler: function(stream, code_point) {} 311 | }; 312 | 313 | // 5.2 Names and labels 314 | 315 | // TODO: Define @typedef for Encoding: {name:string,labels:Array.} 316 | // https://github.com/google/closure-compiler/issues/247 317 | 318 | /** 319 | * @param {string} label The encoding label. 320 | * @return {?{name:string,labels:Array.}} 321 | */ 322 | function getEncoding(label) { 323 | // 1. Remove any leading and trailing ASCII whitespace from label. 324 | label = String(label).trim().toLowerCase(); 325 | 326 | // 2. If label is an ASCII case-insensitive match for any of the 327 | // labels listed in the table below, return the corresponding 328 | // encoding, and failure otherwise. 329 | if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) { 330 | return label_to_encoding[label]; 331 | } 332 | return null; 333 | } 334 | 335 | /** 336 | * Encodings table: https://encoding.spec.whatwg.org/encodings.json 337 | * @const 338 | * @type {!Array.<{ 339 | * heading: string, 340 | * encodings: Array.<{name:string,labels:Array.}> 341 | * }>} 342 | */ 343 | var encodings = [ 344 | { 345 | "encodings": [ 346 | { 347 | "labels": [ 348 | "unicode-1-1-utf-8", 349 | "utf-8", 350 | "utf8" 351 | ], 352 | "name": "UTF-8" 353 | } 354 | ], 355 | "heading": "The Encoding" 356 | }, 357 | { 358 | "encodings": [ 359 | { 360 | "labels": [ 361 | "866", 362 | "cp866", 363 | "csibm866", 364 | "ibm866" 365 | ], 366 | "name": "IBM866" 367 | }, 368 | { 369 | "labels": [ 370 | "csisolatin2", 371 | "iso-8859-2", 372 | "iso-ir-101", 373 | "iso8859-2", 374 | "iso88592", 375 | "iso_8859-2", 376 | "iso_8859-2:1987", 377 | "l2", 378 | "latin2" 379 | ], 380 | "name": "ISO-8859-2" 381 | }, 382 | { 383 | "labels": [ 384 | "csisolatin3", 385 | "iso-8859-3", 386 | "iso-ir-109", 387 | "iso8859-3", 388 | "iso88593", 389 | "iso_8859-3", 390 | "iso_8859-3:1988", 391 | "l3", 392 | "latin3" 393 | ], 394 | "name": "ISO-8859-3" 395 | }, 396 | { 397 | "labels": [ 398 | "csisolatin4", 399 | "iso-8859-4", 400 | "iso-ir-110", 401 | "iso8859-4", 402 | "iso88594", 403 | "iso_8859-4", 404 | "iso_8859-4:1988", 405 | "l4", 406 | "latin4" 407 | ], 408 | "name": "ISO-8859-4" 409 | }, 410 | { 411 | "labels": [ 412 | "csisolatincyrillic", 413 | "cyrillic", 414 | "iso-8859-5", 415 | "iso-ir-144", 416 | "iso8859-5", 417 | "iso88595", 418 | "iso_8859-5", 419 | "iso_8859-5:1988" 420 | ], 421 | "name": "ISO-8859-5" 422 | }, 423 | { 424 | "labels": [ 425 | "arabic", 426 | "asmo-708", 427 | "csiso88596e", 428 | "csiso88596i", 429 | "csisolatinarabic", 430 | "ecma-114", 431 | "iso-8859-6", 432 | "iso-8859-6-e", 433 | "iso-8859-6-i", 434 | "iso-ir-127", 435 | "iso8859-6", 436 | "iso88596", 437 | "iso_8859-6", 438 | "iso_8859-6:1987" 439 | ], 440 | "name": "ISO-8859-6" 441 | }, 442 | { 443 | "labels": [ 444 | "csisolatingreek", 445 | "ecma-118", 446 | "elot_928", 447 | "greek", 448 | "greek8", 449 | "iso-8859-7", 450 | "iso-ir-126", 451 | "iso8859-7", 452 | "iso88597", 453 | "iso_8859-7", 454 | "iso_8859-7:1987", 455 | "sun_eu_greek" 456 | ], 457 | "name": "ISO-8859-7" 458 | }, 459 | { 460 | "labels": [ 461 | "csiso88598e", 462 | "csisolatinhebrew", 463 | "hebrew", 464 | "iso-8859-8", 465 | "iso-8859-8-e", 466 | "iso-ir-138", 467 | "iso8859-8", 468 | "iso88598", 469 | "iso_8859-8", 470 | "iso_8859-8:1988", 471 | "visual" 472 | ], 473 | "name": "ISO-8859-8" 474 | }, 475 | { 476 | "labels": [ 477 | "csiso88598i", 478 | "iso-8859-8-i", 479 | "logical" 480 | ], 481 | "name": "ISO-8859-8-I" 482 | }, 483 | { 484 | "labels": [ 485 | "csisolatin6", 486 | "iso-8859-10", 487 | "iso-ir-157", 488 | "iso8859-10", 489 | "iso885910", 490 | "l6", 491 | "latin6" 492 | ], 493 | "name": "ISO-8859-10" 494 | }, 495 | { 496 | "labels": [ 497 | "iso-8859-13", 498 | "iso8859-13", 499 | "iso885913" 500 | ], 501 | "name": "ISO-8859-13" 502 | }, 503 | { 504 | "labels": [ 505 | "iso-8859-14", 506 | "iso8859-14", 507 | "iso885914" 508 | ], 509 | "name": "ISO-8859-14" 510 | }, 511 | { 512 | "labels": [ 513 | "csisolatin9", 514 | "iso-8859-15", 515 | "iso8859-15", 516 | "iso885915", 517 | "iso_8859-15", 518 | "l9" 519 | ], 520 | "name": "ISO-8859-15" 521 | }, 522 | { 523 | "labels": [ 524 | "iso-8859-16" 525 | ], 526 | "name": "ISO-8859-16" 527 | }, 528 | { 529 | "labels": [ 530 | "cskoi8r", 531 | "koi", 532 | "koi8", 533 | "koi8-r", 534 | "koi8_r" 535 | ], 536 | "name": "KOI8-R" 537 | }, 538 | { 539 | "labels": [ 540 | "koi8-ru", 541 | "koi8-u" 542 | ], 543 | "name": "KOI8-U" 544 | }, 545 | { 546 | "labels": [ 547 | "csmacintosh", 548 | "mac", 549 | "macintosh", 550 | "x-mac-roman" 551 | ], 552 | "name": "macintosh" 553 | }, 554 | { 555 | "labels": [ 556 | "dos-874", 557 | "iso-8859-11", 558 | "iso8859-11", 559 | "iso885911", 560 | "tis-620", 561 | "windows-874" 562 | ], 563 | "name": "windows-874" 564 | }, 565 | { 566 | "labels": [ 567 | "cp1250", 568 | "windows-1250", 569 | "x-cp1250" 570 | ], 571 | "name": "windows-1250" 572 | }, 573 | { 574 | "labels": [ 575 | "cp1251", 576 | "windows-1251", 577 | "x-cp1251" 578 | ], 579 | "name": "windows-1251" 580 | }, 581 | { 582 | "labels": [ 583 | "ansi_x3.4-1968", 584 | "ascii", 585 | "cp1252", 586 | "cp819", 587 | "csisolatin1", 588 | "ibm819", 589 | "iso-8859-1", 590 | "iso-ir-100", 591 | "iso8859-1", 592 | "iso88591", 593 | "iso_8859-1", 594 | "iso_8859-1:1987", 595 | "l1", 596 | "latin1", 597 | "us-ascii", 598 | "windows-1252", 599 | "x-cp1252" 600 | ], 601 | "name": "windows-1252" 602 | }, 603 | { 604 | "labels": [ 605 | "cp1253", 606 | "windows-1253", 607 | "x-cp1253" 608 | ], 609 | "name": "windows-1253" 610 | }, 611 | { 612 | "labels": [ 613 | "cp1254", 614 | "csisolatin5", 615 | "iso-8859-9", 616 | "iso-ir-148", 617 | "iso8859-9", 618 | "iso88599", 619 | "iso_8859-9", 620 | "iso_8859-9:1989", 621 | "l5", 622 | "latin5", 623 | "windows-1254", 624 | "x-cp1254" 625 | ], 626 | "name": "windows-1254" 627 | }, 628 | { 629 | "labels": [ 630 | "cp1255", 631 | "windows-1255", 632 | "x-cp1255" 633 | ], 634 | "name": "windows-1255" 635 | }, 636 | { 637 | "labels": [ 638 | "cp1256", 639 | "windows-1256", 640 | "x-cp1256" 641 | ], 642 | "name": "windows-1256" 643 | }, 644 | { 645 | "labels": [ 646 | "cp1257", 647 | "windows-1257", 648 | "x-cp1257" 649 | ], 650 | "name": "windows-1257" 651 | }, 652 | { 653 | "labels": [ 654 | "cp1258", 655 | "windows-1258", 656 | "x-cp1258" 657 | ], 658 | "name": "windows-1258" 659 | }, 660 | { 661 | "labels": [ 662 | "x-mac-cyrillic", 663 | "x-mac-ukrainian" 664 | ], 665 | "name": "x-mac-cyrillic" 666 | } 667 | ], 668 | "heading": "Legacy single-byte encodings" 669 | }, 670 | { 671 | "encodings": [ 672 | { 673 | "labels": [ 674 | "chinese", 675 | "csgb2312", 676 | "csiso58gb231280", 677 | "gb2312", 678 | "gb_2312", 679 | "gb_2312-80", 680 | "gbk", 681 | "iso-ir-58", 682 | "x-gbk" 683 | ], 684 | "name": "GBK" 685 | }, 686 | { 687 | "labels": [ 688 | "gb18030" 689 | ], 690 | "name": "gb18030" 691 | } 692 | ], 693 | "heading": "Legacy multi-byte Chinese (simplified) encodings" 694 | }, 695 | { 696 | "encodings": [ 697 | { 698 | "labels": [ 699 | "big5", 700 | "big5-hkscs", 701 | "cn-big5", 702 | "csbig5", 703 | "x-x-big5" 704 | ], 705 | "name": "Big5" 706 | } 707 | ], 708 | "heading": "Legacy multi-byte Chinese (traditional) encodings" 709 | }, 710 | { 711 | "encodings": [ 712 | { 713 | "labels": [ 714 | "cseucpkdfmtjapanese", 715 | "euc-jp", 716 | "x-euc-jp" 717 | ], 718 | "name": "EUC-JP" 719 | }, 720 | { 721 | "labels": [ 722 | "csiso2022jp", 723 | "iso-2022-jp" 724 | ], 725 | "name": "ISO-2022-JP" 726 | }, 727 | { 728 | "labels": [ 729 | "csshiftjis", 730 | "ms932", 731 | "ms_kanji", 732 | "shift-jis", 733 | "shift_jis", 734 | "sjis", 735 | "windows-31j", 736 | "x-sjis" 737 | ], 738 | "name": "Shift_JIS" 739 | } 740 | ], 741 | "heading": "Legacy multi-byte Japanese encodings" 742 | }, 743 | { 744 | "encodings": [ 745 | { 746 | "labels": [ 747 | "cseuckr", 748 | "csksc56011987", 749 | "euc-kr", 750 | "iso-ir-149", 751 | "korean", 752 | "ks_c_5601-1987", 753 | "ks_c_5601-1989", 754 | "ksc5601", 755 | "ksc_5601", 756 | "windows-949" 757 | ], 758 | "name": "EUC-KR" 759 | } 760 | ], 761 | "heading": "Legacy multi-byte Korean encodings" 762 | }, 763 | { 764 | "encodings": [ 765 | { 766 | "labels": [ 767 | "csiso2022kr", 768 | "hz-gb-2312", 769 | "iso-2022-cn", 770 | "iso-2022-cn-ext", 771 | "iso-2022-kr" 772 | ], 773 | "name": "replacement" 774 | }, 775 | { 776 | "labels": [ 777 | "utf-16be" 778 | ], 779 | "name": "UTF-16BE" 780 | }, 781 | { 782 | "labels": [ 783 | "utf-16", 784 | "utf-16le" 785 | ], 786 | "name": "UTF-16LE" 787 | }, 788 | { 789 | "labels": [ 790 | "x-user-defined" 791 | ], 792 | "name": "x-user-defined" 793 | } 794 | ], 795 | "heading": "Legacy miscellaneous encodings" 796 | } 797 | ]; 798 | 799 | // Label to encoding registry. 800 | /** @type {Object.}>} */ 801 | var label_to_encoding = {}; 802 | encodings.forEach(function(category) { 803 | category.encodings.forEach(function(encoding) { 804 | encoding.labels.forEach(function(label) { 805 | label_to_encoding[label] = encoding; 806 | }); 807 | }); 808 | }); 809 | 810 | // Registry of of encoder/decoder factories, by encoding name. 811 | /** @type {Object.} */ 812 | var encoders = {}; 813 | /** @type {Object.} */ 814 | var decoders = {}; 815 | 816 | // 817 | // 6. Indexes 818 | // 819 | 820 | /** 821 | * @param {number} pointer The |pointer| to search for. 822 | * @param {(!Array.|undefined)} index The |index| to search within. 823 | * @return {?number} The code point corresponding to |pointer| in |index|, 824 | * or null if |code point| is not in |index|. 825 | */ 826 | function indexCodePointFor(pointer, index) { 827 | if (!index) return null; 828 | return index[pointer] || null; 829 | } 830 | 831 | /** 832 | * @param {number} code_point The |code point| to search for. 833 | * @param {!Array.} index The |index| to search within. 834 | * @return {?number} The first pointer corresponding to |code point| in 835 | * |index|, or null if |code point| is not in |index|. 836 | */ 837 | function indexPointerFor(code_point, index) { 838 | var pointer = index.indexOf(code_point); 839 | return pointer === -1 ? null : pointer; 840 | } 841 | 842 | /** 843 | * @param {string} name Name of the index. 844 | * @return {(!Array.|!Array.>)} 845 | * */ 846 | function index(name) { 847 | if (!('encoding-indexes' in global)) { 848 | throw Error("Indexes missing." + 849 | " Did you forget to include encoding-indexes.js first?"); 850 | } 851 | return global['encoding-indexes'][name]; 852 | } 853 | 854 | /** 855 | * @param {number} pointer The |pointer| to search for in the gb18030 index. 856 | * @return {?number} The code point corresponding to |pointer| in |index|, 857 | * or null if |code point| is not in the gb18030 index. 858 | */ 859 | function indexGB18030RangesCodePointFor(pointer) { 860 | // 1. If pointer is greater than 39419 and less than 189000, or 861 | // pointer is greater than 1237575, return null. 862 | if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575)) 863 | return null; 864 | 865 | // 2. If pointer is 7457, return code point U+E7C7. 866 | if (pointer === 7457) return 0xE7C7; 867 | 868 | // 3. Let offset be the last pointer in index gb18030 ranges that 869 | // is equal to or less than pointer and let code point offset be 870 | // its corresponding code point. 871 | var offset = 0; 872 | var code_point_offset = 0; 873 | var idx = index('gb18030-ranges'); 874 | var i; 875 | for (i = 0; i < idx.length; ++i) { 876 | /** @type {!Array.} */ 877 | var entry = idx[i]; 878 | if (entry[0] <= pointer) { 879 | offset = entry[0]; 880 | code_point_offset = entry[1]; 881 | } else { 882 | break; 883 | } 884 | } 885 | 886 | // 4. Return a code point whose value is code point offset + 887 | // pointer − offset. 888 | return code_point_offset + pointer - offset; 889 | } 890 | 891 | /** 892 | * @param {number} code_point The |code point| to locate in the gb18030 index. 893 | * @return {number} The first pointer corresponding to |code point| in the 894 | * gb18030 index. 895 | */ 896 | function indexGB18030RangesPointerFor(code_point) { 897 | // 1. If code point is U+E7C7, return pointer 7457. 898 | if (code_point === 0xE7C7) return 7457; 899 | 900 | // 2. Let offset be the last code point in index gb18030 ranges 901 | // that is equal to or less than code point and let pointer offset 902 | // be its corresponding pointer. 903 | var offset = 0; 904 | var pointer_offset = 0; 905 | var idx = index('gb18030-ranges'); 906 | var i; 907 | for (i = 0; i < idx.length; ++i) { 908 | /** @type {!Array.} */ 909 | var entry = idx[i]; 910 | if (entry[1] <= code_point) { 911 | offset = entry[1]; 912 | pointer_offset = entry[0]; 913 | } else { 914 | break; 915 | } 916 | } 917 | 918 | // 3. Return a pointer whose value is pointer offset + code point 919 | // − offset. 920 | return pointer_offset + code_point - offset; 921 | } 922 | 923 | /** 924 | * @param {number} code_point The |code_point| to search for in the Shift_JIS 925 | * index. 926 | * @return {?number} The code point corresponding to |pointer| in |index|, 927 | * or null if |code point| is not in the Shift_JIS index. 928 | */ 929 | function indexShiftJISPointerFor(code_point) { 930 | // 1. Let index be index jis0208 excluding all entries whose 931 | // pointer is in the range 8272 to 8835, inclusive. 932 | shift_jis_index = shift_jis_index || 933 | index('jis0208').map(function(code_point, pointer) { 934 | return inRange(pointer, 8272, 8835) ? null : code_point; 935 | }); 936 | var index_ = shift_jis_index; 937 | 938 | // 2. Return the index pointer for code point in index. 939 | return index_.indexOf(code_point); 940 | } 941 | var shift_jis_index; 942 | 943 | /** 944 | * @param {number} code_point The |code_point| to search for in the big5 945 | * index. 946 | * @return {?number} The code point corresponding to |pointer| in |index|, 947 | * or null if |code point| is not in the big5 index. 948 | */ 949 | function indexBig5PointerFor(code_point) { 950 | // 1. Let index be index Big5 excluding all entries whose pointer 951 | big5_index_no_hkscs = big5_index_no_hkscs || 952 | index('big5').map(function(code_point, pointer) { 953 | return (pointer < (0xA1 - 0x81) * 157) ? null : code_point; 954 | }); 955 | var index_ = big5_index_no_hkscs; 956 | 957 | // 2. If code point is U+2550, U+255E, U+2561, U+256A, U+5341, or 958 | // U+5345, return the last pointer corresponding to code point in 959 | // index. 960 | if (code_point === 0x2550 || code_point === 0x255E || 961 | code_point === 0x2561 || code_point === 0x256A || 962 | code_point === 0x5341 || code_point === 0x5345) { 963 | return index_.lastIndexOf(code_point); 964 | } 965 | 966 | // 3. Return the index pointer for code point in index. 967 | return indexPointerFor(code_point, index_); 968 | } 969 | var big5_index_no_hkscs; 970 | 971 | // 972 | // 8. API 973 | // 974 | 975 | /** @const */ var DEFAULT_ENCODING = 'utf-8'; 976 | 977 | // 8.1 Interface TextDecoder 978 | 979 | /** 980 | * @constructor 981 | * @param {string=} label The label of the encoding; 982 | * defaults to 'utf-8'. 983 | * @param {Object=} options 984 | */ 985 | function TextDecoder(label, options) { 986 | // Web IDL conventions 987 | if (!(this instanceof TextDecoder)) 988 | throw TypeError('Called as a function. Did you forget \'new\'?'); 989 | label = label !== undefined ? String(label) : DEFAULT_ENCODING; 990 | options = ToDictionary(options); 991 | 992 | // A TextDecoder object has an associated encoding, decoder, 993 | // stream, ignore BOM flag (initially unset), BOM seen flag 994 | // (initially unset), error mode (initially replacement), and do 995 | // not flush flag (initially unset). 996 | 997 | /** @private */ 998 | this._encoding = null; 999 | /** @private @type {?Decoder} */ 1000 | this._decoder = null; 1001 | /** @private @type {boolean} */ 1002 | this._ignoreBOM = false; 1003 | /** @private @type {boolean} */ 1004 | this._BOMseen = false; 1005 | /** @private @type {string} */ 1006 | this._error_mode = 'replacement'; 1007 | /** @private @type {boolean} */ 1008 | this._do_not_flush = false; 1009 | 1010 | 1011 | // 1. Let encoding be the result of getting an encoding from 1012 | // label. 1013 | var encoding = getEncoding(label); 1014 | 1015 | // 2. If encoding is failure or replacement, throw a RangeError. 1016 | if (encoding === null || encoding.name === 'replacement') 1017 | throw RangeError('Unknown encoding: ' + label); 1018 | if (!decoders[encoding.name]) { 1019 | throw Error('Decoder not present.' + 1020 | ' Did you forget to include encoding-indexes.js first?'); 1021 | } 1022 | 1023 | // 3. Let dec be a new TextDecoder object. 1024 | var dec = this; 1025 | 1026 | // 4. Set dec's encoding to encoding. 1027 | dec._encoding = encoding; 1028 | 1029 | // 5. If options's fatal member is true, set dec's error mode to 1030 | // fatal. 1031 | if (Boolean(options['fatal'])) 1032 | dec._error_mode = 'fatal'; 1033 | 1034 | // 6. If options's ignoreBOM member is true, set dec's ignore BOM 1035 | // flag. 1036 | if (Boolean(options['ignoreBOM'])) 1037 | dec._ignoreBOM = true; 1038 | 1039 | // For pre-ES5 runtimes: 1040 | if (!Object.defineProperty) { 1041 | this.encoding = dec._encoding.name.toLowerCase(); 1042 | this.fatal = dec._error_mode === 'fatal'; 1043 | this.ignoreBOM = dec._ignoreBOM; 1044 | } 1045 | 1046 | // 7. Return dec. 1047 | return dec; 1048 | } 1049 | 1050 | if (Object.defineProperty) { 1051 | // The encoding attribute's getter must return encoding's name. 1052 | Object.defineProperty(TextDecoder.prototype, 'encoding', { 1053 | /** @this {TextDecoder} */ 1054 | get: function() { return this._encoding.name.toLowerCase(); } 1055 | }); 1056 | 1057 | // The fatal attribute's getter must return true if error mode 1058 | // is fatal, and false otherwise. 1059 | Object.defineProperty(TextDecoder.prototype, 'fatal', { 1060 | /** @this {TextDecoder} */ 1061 | get: function() { return this._error_mode === 'fatal'; } 1062 | }); 1063 | 1064 | // The ignoreBOM attribute's getter must return true if ignore 1065 | // BOM flag is set, and false otherwise. 1066 | Object.defineProperty(TextDecoder.prototype, 'ignoreBOM', { 1067 | /** @this {TextDecoder} */ 1068 | get: function() { return this._ignoreBOM; } 1069 | }); 1070 | } 1071 | 1072 | /** 1073 | * @param {BufferSource=} input The buffer of bytes to decode. 1074 | * @param {Object=} options 1075 | * @return {string} The decoded string. 1076 | */ 1077 | TextDecoder.prototype.decode = function decode(input, options) { 1078 | var bytes; 1079 | if (typeof input === 'object' && input instanceof ArrayBuffer) { 1080 | bytes = new Uint8Array(input); 1081 | } else if (typeof input === 'object' && 'buffer' in input && 1082 | input.buffer instanceof ArrayBuffer) { 1083 | bytes = new Uint8Array(input.buffer, 1084 | input.byteOffset, 1085 | input.byteLength); 1086 | } else { 1087 | bytes = new Uint8Array(0); 1088 | } 1089 | 1090 | options = ToDictionary(options); 1091 | 1092 | // 1. If the do not flush flag is unset, set decoder to a new 1093 | // encoding's decoder, set stream to a new stream, and unset the 1094 | // BOM seen flag. 1095 | if (!this._do_not_flush) { 1096 | this._decoder = decoders[this._encoding.name]({ 1097 | fatal: this._error_mode === 'fatal'}); 1098 | this._BOMseen = false; 1099 | } 1100 | 1101 | // 2. If options's stream is true, set the do not flush flag, and 1102 | // unset the do not flush flag otherwise. 1103 | this._do_not_flush = Boolean(options['stream']); 1104 | 1105 | // 3. If input is given, push a copy of input to stream. 1106 | // TODO: Align with spec algorithm - maintain stream on instance. 1107 | var input_stream = new Stream(bytes); 1108 | 1109 | // 4. Let output be a new stream. 1110 | var output = []; 1111 | 1112 | /** @type {?(number|!Array.)} */ 1113 | var result; 1114 | 1115 | // 5. While true: 1116 | while (true) { 1117 | // 1. Let token be the result of reading from stream. 1118 | var token = input_stream.read(); 1119 | 1120 | // 2. If token is end-of-stream and the do not flush flag is 1121 | // set, return output, serialized. 1122 | // TODO: Align with spec algorithm. 1123 | if (token === end_of_stream) 1124 | break; 1125 | 1126 | // 3. Otherwise, run these subsubsteps: 1127 | 1128 | // 1. Let result be the result of processing token for decoder, 1129 | // stream, output, and error mode. 1130 | result = this._decoder.handler(input_stream, token); 1131 | 1132 | // 2. If result is finished, return output, serialized. 1133 | if (result === finished) 1134 | break; 1135 | 1136 | if (result !== null) { 1137 | if (Array.isArray(result)) 1138 | output.push.apply(output, /**@type {!Array.}*/(result)); 1139 | else 1140 | output.push(result); 1141 | } 1142 | 1143 | // 3. Otherwise, if result is error, throw a TypeError. 1144 | // (Thrown in handler) 1145 | 1146 | // 4. Otherwise, do nothing. 1147 | } 1148 | // TODO: Align with spec algorithm. 1149 | if (!this._do_not_flush) { 1150 | do { 1151 | result = this._decoder.handler(input_stream, input_stream.read()); 1152 | if (result === finished) 1153 | break; 1154 | if (result === null) 1155 | continue; 1156 | if (Array.isArray(result)) 1157 | output.push.apply(output, /**@type {!Array.}*/(result)); 1158 | else 1159 | output.push(result); 1160 | } while (!input_stream.endOfStream()); 1161 | this._decoder = null; 1162 | } 1163 | 1164 | // A TextDecoder object also has an associated serialize stream 1165 | // algorithm... 1166 | /** 1167 | * @param {!Array.} stream 1168 | * @return {string} 1169 | * @this {TextDecoder} 1170 | */ 1171 | function serializeStream(stream) { 1172 | // 1. Let token be the result of reading from stream. 1173 | // (Done in-place on array, rather than as a stream) 1174 | 1175 | // 2. If encoding is UTF-8, UTF-16BE, or UTF-16LE, and ignore 1176 | // BOM flag and BOM seen flag are unset, run these subsubsteps: 1177 | if (includes(['UTF-8', 'UTF-16LE', 'UTF-16BE'], this._encoding.name) && 1178 | !this._ignoreBOM && !this._BOMseen) { 1179 | if (stream.length > 0 && stream[0] === 0xFEFF) { 1180 | // 1. If token is U+FEFF, set BOM seen flag. 1181 | this._BOMseen = true; 1182 | stream.shift(); 1183 | } else if (stream.length > 0) { 1184 | // 2. Otherwise, if token is not end-of-stream, set BOM seen 1185 | // flag and append token to stream. 1186 | this._BOMseen = true; 1187 | } else { 1188 | // 3. Otherwise, if token is not end-of-stream, append token 1189 | // to output. 1190 | // (no-op) 1191 | } 1192 | } 1193 | // 4. Otherwise, return output. 1194 | return codePointsToString(stream); 1195 | } 1196 | 1197 | return serializeStream.call(this, output); 1198 | }; 1199 | 1200 | // 8.2 Interface TextEncoder 1201 | 1202 | /** 1203 | * @constructor 1204 | * @param {string=} label The label of the encoding. NONSTANDARD. 1205 | * @param {Object=} options NONSTANDARD. 1206 | */ 1207 | function TextEncoder(label, options) { 1208 | // Web IDL conventions 1209 | if (!(this instanceof TextEncoder)) 1210 | throw TypeError('Called as a function. Did you forget \'new\'?'); 1211 | options = ToDictionary(options); 1212 | 1213 | // A TextEncoder object has an associated encoding and encoder. 1214 | 1215 | /** @private */ 1216 | this._encoding = null; 1217 | /** @private @type {?Encoder} */ 1218 | this._encoder = null; 1219 | 1220 | // Non-standard 1221 | /** @private @type {boolean} */ 1222 | this._do_not_flush = false; 1223 | /** @private @type {string} */ 1224 | this._fatal = Boolean(options['fatal']) ? 'fatal' : 'replacement'; 1225 | 1226 | // 1. Let enc be a new TextEncoder object. 1227 | var enc = this; 1228 | 1229 | // 2. Set enc's encoding to UTF-8's encoder. 1230 | if (Boolean(options['NONSTANDARD_allowLegacyEncoding'])) { 1231 | // NONSTANDARD behavior. 1232 | label = label !== undefined ? String(label) : DEFAULT_ENCODING; 1233 | var encoding = getEncoding(label); 1234 | if (encoding === null || encoding.name === 'replacement') 1235 | throw RangeError('Unknown encoding: ' + label); 1236 | if (!encoders[encoding.name]) { 1237 | throw Error('Encoder not present.' + 1238 | ' Did you forget to include encoding-indexes.js first?'); 1239 | } 1240 | enc._encoding = encoding; 1241 | } else { 1242 | // Standard behavior. 1243 | enc._encoding = getEncoding('utf-8'); 1244 | 1245 | if (label !== undefined && 'console' in global) { 1246 | console.warn('TextEncoder constructor called with encoding label, ' 1247 | + 'which is ignored.'); 1248 | } 1249 | } 1250 | 1251 | // For pre-ES5 runtimes: 1252 | if (!Object.defineProperty) 1253 | this.encoding = enc._encoding.name.toLowerCase(); 1254 | 1255 | // 3. Return enc. 1256 | return enc; 1257 | } 1258 | 1259 | if (Object.defineProperty) { 1260 | // The encoding attribute's getter must return encoding's name. 1261 | Object.defineProperty(TextEncoder.prototype, 'encoding', { 1262 | /** @this {TextEncoder} */ 1263 | get: function() { return this._encoding.name.toLowerCase(); } 1264 | }); 1265 | } 1266 | 1267 | /** 1268 | * @param {string=} opt_string The string to encode. 1269 | * @param {Object=} options 1270 | * @return {!Uint8Array} Encoded bytes, as a Uint8Array. 1271 | */ 1272 | TextEncoder.prototype.encode = function encode(opt_string, options) { 1273 | opt_string = opt_string === undefined ? '' : String(opt_string); 1274 | options = ToDictionary(options); 1275 | 1276 | // NOTE: This option is nonstandard. None of the encodings 1277 | // permitted for encoding (i.e. UTF-8, UTF-16) are stateful when 1278 | // the input is a USVString so streaming is not necessary. 1279 | if (!this._do_not_flush) 1280 | this._encoder = encoders[this._encoding.name]({ 1281 | fatal: this._fatal === 'fatal'}); 1282 | this._do_not_flush = Boolean(options['stream']); 1283 | 1284 | // 1. Convert input to a stream. 1285 | var input = new Stream(stringToCodePoints(opt_string)); 1286 | 1287 | // 2. Let output be a new stream 1288 | var output = []; 1289 | 1290 | /** @type {?(number|!Array.)} */ 1291 | var result; 1292 | // 3. While true, run these substeps: 1293 | while (true) { 1294 | // 1. Let token be the result of reading from input. 1295 | var token = input.read(); 1296 | if (token === end_of_stream) 1297 | break; 1298 | // 2. Let result be the result of processing token for encoder, 1299 | // input, output. 1300 | result = this._encoder.handler(input, token); 1301 | if (result === finished) 1302 | break; 1303 | if (Array.isArray(result)) 1304 | output.push.apply(output, /**@type {!Array.}*/(result)); 1305 | else 1306 | output.push(result); 1307 | } 1308 | // TODO: Align with spec algorithm. 1309 | if (!this._do_not_flush) { 1310 | while (true) { 1311 | result = this._encoder.handler(input, input.read()); 1312 | if (result === finished) 1313 | break; 1314 | if (Array.isArray(result)) 1315 | output.push.apply(output, /**@type {!Array.}*/(result)); 1316 | else 1317 | output.push(result); 1318 | } 1319 | this._encoder = null; 1320 | } 1321 | // 3. If result is finished, convert output into a byte sequence, 1322 | // and then return a Uint8Array object wrapping an ArrayBuffer 1323 | // containing output. 1324 | return new Uint8Array(output); 1325 | }; 1326 | 1327 | 1328 | // 1329 | // 9. The encoding 1330 | // 1331 | 1332 | // 9.1 utf-8 1333 | 1334 | // 9.1.1 utf-8 decoder 1335 | /** 1336 | * @constructor 1337 | * @implements {Decoder} 1338 | * @param {{fatal: boolean}} options 1339 | */ 1340 | function UTF8Decoder(options) { 1341 | var fatal = options.fatal; 1342 | 1343 | // utf-8's decoder's has an associated utf-8 code point, utf-8 1344 | // bytes seen, and utf-8 bytes needed (all initially 0), a utf-8 1345 | // lower boundary (initially 0x80), and a utf-8 upper boundary 1346 | // (initially 0xBF). 1347 | var /** @type {number} */ utf8_code_point = 0, 1348 | /** @type {number} */ utf8_bytes_seen = 0, 1349 | /** @type {number} */ utf8_bytes_needed = 0, 1350 | /** @type {number} */ utf8_lower_boundary = 0x80, 1351 | /** @type {number} */ utf8_upper_boundary = 0xBF; 1352 | 1353 | /** 1354 | * @param {Stream} stream The stream of bytes being decoded. 1355 | * @param {number} bite The next byte read from the stream. 1356 | * @return {?(number|!Array.)} The next code point(s) 1357 | * decoded, or null if not enough data exists in the input 1358 | * stream to decode a complete code point. 1359 | */ 1360 | this.handler = function(stream, bite) { 1361 | // 1. If byte is end-of-stream and utf-8 bytes needed is not 0, 1362 | // set utf-8 bytes needed to 0 and return error. 1363 | if (bite === end_of_stream && utf8_bytes_needed !== 0) { 1364 | utf8_bytes_needed = 0; 1365 | return decoderError(fatal); 1366 | } 1367 | 1368 | // 2. If byte is end-of-stream, return finished. 1369 | if (bite === end_of_stream) 1370 | return finished; 1371 | 1372 | // 3. If utf-8 bytes needed is 0, based on byte: 1373 | if (utf8_bytes_needed === 0) { 1374 | 1375 | // 0x00 to 0x7F 1376 | if (inRange(bite, 0x00, 0x7F)) { 1377 | // Return a code point whose value is byte. 1378 | return bite; 1379 | } 1380 | 1381 | // 0xC2 to 0xDF 1382 | else if (inRange(bite, 0xC2, 0xDF)) { 1383 | // 1. Set utf-8 bytes needed to 1. 1384 | utf8_bytes_needed = 1; 1385 | 1386 | // 2. Set UTF-8 code point to byte & 0x1F. 1387 | utf8_code_point = bite & 0x1F; 1388 | } 1389 | 1390 | // 0xE0 to 0xEF 1391 | else if (inRange(bite, 0xE0, 0xEF)) { 1392 | // 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0. 1393 | if (bite === 0xE0) 1394 | utf8_lower_boundary = 0xA0; 1395 | // 2. If byte is 0xED, set utf-8 upper boundary to 0x9F. 1396 | if (bite === 0xED) 1397 | utf8_upper_boundary = 0x9F; 1398 | // 3. Set utf-8 bytes needed to 2. 1399 | utf8_bytes_needed = 2; 1400 | // 4. Set UTF-8 code point to byte & 0xF. 1401 | utf8_code_point = bite & 0xF; 1402 | } 1403 | 1404 | // 0xF0 to 0xF4 1405 | else if (inRange(bite, 0xF0, 0xF4)) { 1406 | // 1. If byte is 0xF0, set utf-8 lower boundary to 0x90. 1407 | if (bite === 0xF0) 1408 | utf8_lower_boundary = 0x90; 1409 | // 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F. 1410 | if (bite === 0xF4) 1411 | utf8_upper_boundary = 0x8F; 1412 | // 3. Set utf-8 bytes needed to 3. 1413 | utf8_bytes_needed = 3; 1414 | // 4. Set UTF-8 code point to byte & 0x7. 1415 | utf8_code_point = bite & 0x7; 1416 | } 1417 | 1418 | // Otherwise 1419 | else { 1420 | // Return error. 1421 | return decoderError(fatal); 1422 | } 1423 | 1424 | // Return continue. 1425 | return null; 1426 | } 1427 | 1428 | // 4. If byte is not in the range utf-8 lower boundary to utf-8 1429 | // upper boundary, inclusive, run these substeps: 1430 | if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) { 1431 | 1432 | // 1. Set utf-8 code point, utf-8 bytes needed, and utf-8 1433 | // bytes seen to 0, set utf-8 lower boundary to 0x80, and set 1434 | // utf-8 upper boundary to 0xBF. 1435 | utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; 1436 | utf8_lower_boundary = 0x80; 1437 | utf8_upper_boundary = 0xBF; 1438 | 1439 | // 2. Prepend byte to stream. 1440 | stream.prepend(bite); 1441 | 1442 | // 3. Return error. 1443 | return decoderError(fatal); 1444 | } 1445 | 1446 | // 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary 1447 | // to 0xBF. 1448 | utf8_lower_boundary = 0x80; 1449 | utf8_upper_boundary = 0xBF; 1450 | 1451 | // 6. Set UTF-8 code point to (UTF-8 code point << 6) | (byte & 1452 | // 0x3F) 1453 | utf8_code_point = (utf8_code_point << 6) | (bite & 0x3F); 1454 | 1455 | // 7. Increase utf-8 bytes seen by one. 1456 | utf8_bytes_seen += 1; 1457 | 1458 | // 8. If utf-8 bytes seen is not equal to utf-8 bytes needed, 1459 | // continue. 1460 | if (utf8_bytes_seen !== utf8_bytes_needed) 1461 | return null; 1462 | 1463 | // 9. Let code point be utf-8 code point. 1464 | var code_point = utf8_code_point; 1465 | 1466 | // 10. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes 1467 | // seen to 0. 1468 | utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; 1469 | 1470 | // 11. Return a code point whose value is code point. 1471 | return code_point; 1472 | }; 1473 | } 1474 | 1475 | // 9.1.2 utf-8 encoder 1476 | /** 1477 | * @constructor 1478 | * @implements {Encoder} 1479 | * @param {{fatal: boolean}} options 1480 | */ 1481 | function UTF8Encoder(options) { 1482 | var fatal = options.fatal; 1483 | /** 1484 | * @param {Stream} stream Input stream. 1485 | * @param {number} code_point Next code point read from the stream. 1486 | * @return {(number|!Array.)} Byte(s) to emit. 1487 | */ 1488 | this.handler = function(stream, code_point) { 1489 | // 1. If code point is end-of-stream, return finished. 1490 | if (code_point === end_of_stream) 1491 | return finished; 1492 | 1493 | // 2. If code point is an ASCII code point, return a byte whose 1494 | // value is code point. 1495 | if (isASCIICodePoint(code_point)) 1496 | return code_point; 1497 | 1498 | // 3. Set count and offset based on the range code point is in: 1499 | var count, offset; 1500 | // U+0080 to U+07FF, inclusive: 1501 | if (inRange(code_point, 0x0080, 0x07FF)) { 1502 | // 1 and 0xC0 1503 | count = 1; 1504 | offset = 0xC0; 1505 | } 1506 | // U+0800 to U+FFFF, inclusive: 1507 | else if (inRange(code_point, 0x0800, 0xFFFF)) { 1508 | // 2 and 0xE0 1509 | count = 2; 1510 | offset = 0xE0; 1511 | } 1512 | // U+10000 to U+10FFFF, inclusive: 1513 | else if (inRange(code_point, 0x10000, 0x10FFFF)) { 1514 | // 3 and 0xF0 1515 | count = 3; 1516 | offset = 0xF0; 1517 | } 1518 | 1519 | // 4. Let bytes be a byte sequence whose first byte is (code 1520 | // point >> (6 × count)) + offset. 1521 | var bytes = [(code_point >> (6 * count)) + offset]; 1522 | 1523 | // 5. Run these substeps while count is greater than 0: 1524 | while (count > 0) { 1525 | 1526 | // 1. Set temp to code point >> (6 × (count − 1)). 1527 | var temp = code_point >> (6 * (count - 1)); 1528 | 1529 | // 2. Append to bytes 0x80 | (temp & 0x3F). 1530 | bytes.push(0x80 | (temp & 0x3F)); 1531 | 1532 | // 3. Decrease count by one. 1533 | count -= 1; 1534 | } 1535 | 1536 | // 6. Return bytes bytes, in order. 1537 | return bytes; 1538 | }; 1539 | } 1540 | 1541 | /** @param {{fatal: boolean}} options */ 1542 | encoders['UTF-8'] = function(options) { 1543 | return new UTF8Encoder(options); 1544 | }; 1545 | /** @param {{fatal: boolean}} options */ 1546 | decoders['UTF-8'] = function(options) { 1547 | return new UTF8Decoder(options); 1548 | }; 1549 | 1550 | // 1551 | // 10. Legacy single-byte encodings 1552 | // 1553 | 1554 | // 10.1 single-byte decoder 1555 | /** 1556 | * @constructor 1557 | * @implements {Decoder} 1558 | * @param {!Array.} index The encoding index. 1559 | * @param {{fatal: boolean}} options 1560 | */ 1561 | function SingleByteDecoder(index, options) { 1562 | var fatal = options.fatal; 1563 | /** 1564 | * @param {Stream} stream The stream of bytes being decoded. 1565 | * @param {number} bite The next byte read from the stream. 1566 | * @return {?(number|!Array.)} The next code point(s) 1567 | * decoded, or null if not enough data exists in the input 1568 | * stream to decode a complete code point. 1569 | */ 1570 | this.handler = function(stream, bite) { 1571 | // 1. If byte is end-of-stream, return finished. 1572 | if (bite === end_of_stream) 1573 | return finished; 1574 | 1575 | // 2. If byte is an ASCII byte, return a code point whose value 1576 | // is byte. 1577 | if (isASCIIByte(bite)) 1578 | return bite; 1579 | 1580 | // 3. Let code point be the index code point for byte − 0x80 in 1581 | // index single-byte. 1582 | var code_point = index[bite - 0x80]; 1583 | 1584 | // 4. If code point is null, return error. 1585 | if (code_point === null) 1586 | return decoderError(fatal); 1587 | 1588 | // 5. Return a code point whose value is code point. 1589 | return code_point; 1590 | }; 1591 | } 1592 | 1593 | // 10.2 single-byte encoder 1594 | /** 1595 | * @constructor 1596 | * @implements {Encoder} 1597 | * @param {!Array.} index The encoding index. 1598 | * @param {{fatal: boolean}} options 1599 | */ 1600 | function SingleByteEncoder(index, options) { 1601 | var fatal = options.fatal; 1602 | /** 1603 | * @param {Stream} stream Input stream. 1604 | * @param {number} code_point Next code point read from the stream. 1605 | * @return {(number|!Array.)} Byte(s) to emit. 1606 | */ 1607 | this.handler = function(stream, code_point) { 1608 | // 1. If code point is end-of-stream, return finished. 1609 | if (code_point === end_of_stream) 1610 | return finished; 1611 | 1612 | // 2. If code point is an ASCII code point, return a byte whose 1613 | // value is code point. 1614 | if (isASCIICodePoint(code_point)) 1615 | return code_point; 1616 | 1617 | // 3. Let pointer be the index pointer for code point in index 1618 | // single-byte. 1619 | var pointer = indexPointerFor(code_point, index); 1620 | 1621 | // 4. If pointer is null, return error with code point. 1622 | if (pointer === null) 1623 | encoderError(code_point); 1624 | 1625 | // 5. Return a byte whose value is pointer + 0x80. 1626 | return pointer + 0x80; 1627 | }; 1628 | } 1629 | 1630 | (function() { 1631 | if (!('encoding-indexes' in global)) 1632 | return; 1633 | encodings.forEach(function(category) { 1634 | if (category.heading !== 'Legacy single-byte encodings') 1635 | return; 1636 | category.encodings.forEach(function(encoding) { 1637 | var name = encoding.name; 1638 | var idx = index(name.toLowerCase()); 1639 | /** @param {{fatal: boolean}} options */ 1640 | decoders[name] = function(options) { 1641 | return new SingleByteDecoder(idx, options); 1642 | }; 1643 | /** @param {{fatal: boolean}} options */ 1644 | encoders[name] = function(options) { 1645 | return new SingleByteEncoder(idx, options); 1646 | }; 1647 | }); 1648 | }); 1649 | }()); 1650 | 1651 | // 1652 | // 11. Legacy multi-byte Chinese (simplified) encodings 1653 | // 1654 | 1655 | // 11.1 gbk 1656 | 1657 | // 11.1.1 gbk decoder 1658 | // gbk's decoder is gb18030's decoder. 1659 | /** @param {{fatal: boolean}} options */ 1660 | decoders['GBK'] = function(options) { 1661 | return new GB18030Decoder(options); 1662 | }; 1663 | 1664 | // 11.1.2 gbk encoder 1665 | // gbk's encoder is gb18030's encoder with its gbk flag set. 1666 | /** @param {{fatal: boolean}} options */ 1667 | encoders['GBK'] = function(options) { 1668 | return new GB18030Encoder(options, true); 1669 | }; 1670 | 1671 | // 11.2 gb18030 1672 | 1673 | // 11.2.1 gb18030 decoder 1674 | /** 1675 | * @constructor 1676 | * @implements {Decoder} 1677 | * @param {{fatal: boolean}} options 1678 | */ 1679 | function GB18030Decoder(options) { 1680 | var fatal = options.fatal; 1681 | // gb18030's decoder has an associated gb18030 first, gb18030 1682 | // second, and gb18030 third (all initially 0x00). 1683 | var /** @type {number} */ gb18030_first = 0x00, 1684 | /** @type {number} */ gb18030_second = 0x00, 1685 | /** @type {number} */ gb18030_third = 0x00; 1686 | /** 1687 | * @param {Stream} stream The stream of bytes being decoded. 1688 | * @param {number} bite The next byte read from the stream. 1689 | * @return {?(number|!Array.)} The next code point(s) 1690 | * decoded, or null if not enough data exists in the input 1691 | * stream to decode a complete code point. 1692 | */ 1693 | this.handler = function(stream, bite) { 1694 | // 1. If byte is end-of-stream and gb18030 first, gb18030 1695 | // second, and gb18030 third are 0x00, return finished. 1696 | if (bite === end_of_stream && gb18030_first === 0x00 && 1697 | gb18030_second === 0x00 && gb18030_third === 0x00) { 1698 | return finished; 1699 | } 1700 | // 2. If byte is end-of-stream, and gb18030 first, gb18030 1701 | // second, or gb18030 third is not 0x00, set gb18030 first, 1702 | // gb18030 second, and gb18030 third to 0x00, and return error. 1703 | if (bite === end_of_stream && 1704 | (gb18030_first !== 0x00 || gb18030_second !== 0x00 || 1705 | gb18030_third !== 0x00)) { 1706 | gb18030_first = 0x00; 1707 | gb18030_second = 0x00; 1708 | gb18030_third = 0x00; 1709 | decoderError(fatal); 1710 | } 1711 | var code_point; 1712 | // 3. If gb18030 third is not 0x00, run these substeps: 1713 | if (gb18030_third !== 0x00) { 1714 | // 1. Let code point be null. 1715 | code_point = null; 1716 | // 2. If byte is in the range 0x30 to 0x39, inclusive, set 1717 | // code point to the index gb18030 ranges code point for 1718 | // (((gb18030 first − 0x81) × 10 + gb18030 second − 0x30) × 1719 | // 126 + gb18030 third − 0x81) × 10 + byte − 0x30. 1720 | if (inRange(bite, 0x30, 0x39)) { 1721 | code_point = indexGB18030RangesCodePointFor( 1722 | (((gb18030_first - 0x81) * 10 + gb18030_second - 0x30) * 126 + 1723 | gb18030_third - 0x81) * 10 + bite - 0x30); 1724 | } 1725 | 1726 | // 3. Let buffer be a byte sequence consisting of gb18030 1727 | // second, gb18030 third, and byte, in order. 1728 | var buffer = [gb18030_second, gb18030_third, bite]; 1729 | 1730 | // 4. Set gb18030 first, gb18030 second, and gb18030 third to 1731 | // 0x00. 1732 | gb18030_first = 0x00; 1733 | gb18030_second = 0x00; 1734 | gb18030_third = 0x00; 1735 | 1736 | // 5. If code point is null, prepend buffer to stream and 1737 | // return error. 1738 | if (code_point === null) { 1739 | stream.prepend(buffer); 1740 | return decoderError(fatal); 1741 | } 1742 | 1743 | // 6. Return a code point whose value is code point. 1744 | return code_point; 1745 | } 1746 | 1747 | // 4. If gb18030 second is not 0x00, run these substeps: 1748 | if (gb18030_second !== 0x00) { 1749 | 1750 | // 1. If byte is in the range 0x81 to 0xFE, inclusive, set 1751 | // gb18030 third to byte and return continue. 1752 | if (inRange(bite, 0x81, 0xFE)) { 1753 | gb18030_third = bite; 1754 | return null; 1755 | } 1756 | 1757 | // 2. Prepend gb18030 second followed by byte to stream, set 1758 | // gb18030 first and gb18030 second to 0x00, and return error. 1759 | stream.prepend([gb18030_second, bite]); 1760 | gb18030_first = 0x00; 1761 | gb18030_second = 0x00; 1762 | return decoderError(fatal); 1763 | } 1764 | 1765 | // 5. If gb18030 first is not 0x00, run these substeps: 1766 | if (gb18030_first !== 0x00) { 1767 | 1768 | // 1. If byte is in the range 0x30 to 0x39, inclusive, set 1769 | // gb18030 second to byte and return continue. 1770 | if (inRange(bite, 0x30, 0x39)) { 1771 | gb18030_second = bite; 1772 | return null; 1773 | } 1774 | 1775 | // 2. Let lead be gb18030 first, let pointer be null, and set 1776 | // gb18030 first to 0x00. 1777 | var lead = gb18030_first; 1778 | var pointer = null; 1779 | gb18030_first = 0x00; 1780 | 1781 | // 3. Let offset be 0x40 if byte is less than 0x7F and 0x41 1782 | // otherwise. 1783 | var offset = bite < 0x7F ? 0x40 : 0x41; 1784 | 1785 | // 4. If byte is in the range 0x40 to 0x7E, inclusive, or 0x80 1786 | // to 0xFE, inclusive, set pointer to (lead − 0x81) × 190 + 1787 | // (byte − offset). 1788 | if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE)) 1789 | pointer = (lead - 0x81) * 190 + (bite - offset); 1790 | 1791 | // 5. Let code point be null if pointer is null and the index 1792 | // code point for pointer in index gb18030 otherwise. 1793 | code_point = pointer === null ? null : 1794 | indexCodePointFor(pointer, index('gb18030')); 1795 | 1796 | // 6. If code point is null and byte is an ASCII byte, prepend 1797 | // byte to stream. 1798 | if (code_point === null && isASCIIByte(bite)) 1799 | stream.prepend(bite); 1800 | 1801 | // 7. If code point is null, return error. 1802 | if (code_point === null) 1803 | return decoderError(fatal); 1804 | 1805 | // 8. Return a code point whose value is code point. 1806 | return code_point; 1807 | } 1808 | 1809 | // 6. If byte is an ASCII byte, return a code point whose value 1810 | // is byte. 1811 | if (isASCIIByte(bite)) 1812 | return bite; 1813 | 1814 | // 7. If byte is 0x80, return code point U+20AC. 1815 | if (bite === 0x80) 1816 | return 0x20AC; 1817 | 1818 | // 8. If byte is in the range 0x81 to 0xFE, inclusive, set 1819 | // gb18030 first to byte and return continue. 1820 | if (inRange(bite, 0x81, 0xFE)) { 1821 | gb18030_first = bite; 1822 | return null; 1823 | } 1824 | 1825 | // 9. Return error. 1826 | return decoderError(fatal); 1827 | }; 1828 | } 1829 | 1830 | // 11.2.2 gb18030 encoder 1831 | /** 1832 | * @constructor 1833 | * @implements {Encoder} 1834 | * @param {{fatal: boolean}} options 1835 | * @param {boolean=} gbk_flag 1836 | */ 1837 | function GB18030Encoder(options, gbk_flag) { 1838 | var fatal = options.fatal; 1839 | // gb18030's decoder has an associated gbk flag (initially unset). 1840 | /** 1841 | * @param {Stream} stream Input stream. 1842 | * @param {number} code_point Next code point read from the stream. 1843 | * @return {(number|!Array.)} Byte(s) to emit. 1844 | */ 1845 | this.handler = function(stream, code_point) { 1846 | // 1. If code point is end-of-stream, return finished. 1847 | if (code_point === end_of_stream) 1848 | return finished; 1849 | 1850 | // 2. If code point is an ASCII code point, return a byte whose 1851 | // value is code point. 1852 | if (isASCIICodePoint(code_point)) 1853 | return code_point; 1854 | 1855 | // 3. If code point is U+E5E5, return error with code point. 1856 | if (code_point === 0xE5E5) 1857 | return encoderError(code_point); 1858 | 1859 | // 4. If the gbk flag is set and code point is U+20AC, return 1860 | // byte 0x80. 1861 | if (gbk_flag && code_point === 0x20AC) 1862 | return 0x80; 1863 | 1864 | // 5. Let pointer be the index pointer for code point in index 1865 | // gb18030. 1866 | var pointer = indexPointerFor(code_point, index('gb18030')); 1867 | 1868 | // 6. If pointer is not null, run these substeps: 1869 | if (pointer !== null) { 1870 | 1871 | // 1. Let lead be floor(pointer / 190) + 0x81. 1872 | var lead = floor(pointer / 190) + 0x81; 1873 | 1874 | // 2. Let trail be pointer % 190. 1875 | var trail = pointer % 190; 1876 | 1877 | // 3. Let offset be 0x40 if trail is less than 0x3F and 0x41 otherwise. 1878 | var offset = trail < 0x3F ? 0x40 : 0x41; 1879 | 1880 | // 4. Return two bytes whose values are lead and trail + offset. 1881 | return [lead, trail + offset]; 1882 | } 1883 | 1884 | // 7. If gbk flag is set, return error with code point. 1885 | if (gbk_flag) 1886 | return encoderError(code_point); 1887 | 1888 | // 8. Set pointer to the index gb18030 ranges pointer for code 1889 | // point. 1890 | pointer = indexGB18030RangesPointerFor(code_point); 1891 | 1892 | // 9. Let byte1 be floor(pointer / 10 / 126 / 10). 1893 | var byte1 = floor(pointer / 10 / 126 / 10); 1894 | 1895 | // 10. Set pointer to pointer − byte1 × 10 × 126 × 10. 1896 | pointer = pointer - byte1 * 10 * 126 * 10; 1897 | 1898 | // 11. Let byte2 be floor(pointer / 10 / 126). 1899 | var byte2 = floor(pointer / 10 / 126); 1900 | 1901 | // 12. Set pointer to pointer − byte2 × 10 × 126. 1902 | pointer = pointer - byte2 * 10 * 126; 1903 | 1904 | // 13. Let byte3 be floor(pointer / 10). 1905 | var byte3 = floor(pointer / 10); 1906 | 1907 | // 14. Let byte4 be pointer − byte3 × 10. 1908 | var byte4 = pointer - byte3 * 10; 1909 | 1910 | // 15. Return four bytes whose values are byte1 + 0x81, byte2 + 1911 | // 0x30, byte3 + 0x81, byte4 + 0x30. 1912 | return [byte1 + 0x81, 1913 | byte2 + 0x30, 1914 | byte3 + 0x81, 1915 | byte4 + 0x30]; 1916 | }; 1917 | } 1918 | 1919 | /** @param {{fatal: boolean}} options */ 1920 | encoders['gb18030'] = function(options) { 1921 | return new GB18030Encoder(options); 1922 | }; 1923 | /** @param {{fatal: boolean}} options */ 1924 | decoders['gb18030'] = function(options) { 1925 | return new GB18030Decoder(options); 1926 | }; 1927 | 1928 | 1929 | // 1930 | // 12. Legacy multi-byte Chinese (traditional) encodings 1931 | // 1932 | 1933 | // 12.1 Big5 1934 | 1935 | // 12.1.1 Big5 decoder 1936 | /** 1937 | * @constructor 1938 | * @implements {Decoder} 1939 | * @param {{fatal: boolean}} options 1940 | */ 1941 | function Big5Decoder(options) { 1942 | var fatal = options.fatal; 1943 | // Big5's decoder has an associated Big5 lead (initially 0x00). 1944 | var /** @type {number} */ Big5_lead = 0x00; 1945 | 1946 | /** 1947 | * @param {Stream} stream The stream of bytes being decoded. 1948 | * @param {number} bite The next byte read from the stream. 1949 | * @return {?(number|!Array.)} The next code point(s) 1950 | * decoded, or null if not enough data exists in the input 1951 | * stream to decode a complete code point. 1952 | */ 1953 | this.handler = function(stream, bite) { 1954 | // 1. If byte is end-of-stream and Big5 lead is not 0x00, set 1955 | // Big5 lead to 0x00 and return error. 1956 | if (bite === end_of_stream && Big5_lead !== 0x00) { 1957 | Big5_lead = 0x00; 1958 | return decoderError(fatal); 1959 | } 1960 | 1961 | // 2. If byte is end-of-stream and Big5 lead is 0x00, return 1962 | // finished. 1963 | if (bite === end_of_stream && Big5_lead === 0x00) 1964 | return finished; 1965 | 1966 | // 3. If Big5 lead is not 0x00, let lead be Big5 lead, let 1967 | // pointer be null, set Big5 lead to 0x00, and then run these 1968 | // substeps: 1969 | if (Big5_lead !== 0x00) { 1970 | var lead = Big5_lead; 1971 | var pointer = null; 1972 | Big5_lead = 0x00; 1973 | 1974 | // 1. Let offset be 0x40 if byte is less than 0x7F and 0x62 1975 | // otherwise. 1976 | var offset = bite < 0x7F ? 0x40 : 0x62; 1977 | 1978 | // 2. If byte is in the range 0x40 to 0x7E, inclusive, or 0xA1 1979 | // to 0xFE, inclusive, set pointer to (lead − 0x81) × 157 + 1980 | // (byte − offset). 1981 | if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE)) 1982 | pointer = (lead - 0x81) * 157 + (bite - offset); 1983 | 1984 | // 3. If there is a row in the table below whose first column 1985 | // is pointer, return the two code points listed in its second 1986 | // column 1987 | // Pointer | Code points 1988 | // --------+-------------- 1989 | // 1133 | U+00CA U+0304 1990 | // 1135 | U+00CA U+030C 1991 | // 1164 | U+00EA U+0304 1992 | // 1166 | U+00EA U+030C 1993 | switch (pointer) { 1994 | case 1133: return [0x00CA, 0x0304]; 1995 | case 1135: return [0x00CA, 0x030C]; 1996 | case 1164: return [0x00EA, 0x0304]; 1997 | case 1166: return [0x00EA, 0x030C]; 1998 | } 1999 | 2000 | // 4. Let code point be null if pointer is null and the index 2001 | // code point for pointer in index Big5 otherwise. 2002 | var code_point = (pointer === null) ? null : 2003 | indexCodePointFor(pointer, index('big5')); 2004 | 2005 | // 5. If code point is null and byte is an ASCII byte, prepend 2006 | // byte to stream. 2007 | if (code_point === null && isASCIIByte(bite)) 2008 | stream.prepend(bite); 2009 | 2010 | // 6. If code point is null, return error. 2011 | if (code_point === null) 2012 | return decoderError(fatal); 2013 | 2014 | // 7. Return a code point whose value is code point. 2015 | return code_point; 2016 | } 2017 | 2018 | // 4. If byte is an ASCII byte, return a code point whose value 2019 | // is byte. 2020 | if (isASCIIByte(bite)) 2021 | return bite; 2022 | 2023 | // 5. If byte is in the range 0x81 to 0xFE, inclusive, set Big5 2024 | // lead to byte and return continue. 2025 | if (inRange(bite, 0x81, 0xFE)) { 2026 | Big5_lead = bite; 2027 | return null; 2028 | } 2029 | 2030 | // 6. Return error. 2031 | return decoderError(fatal); 2032 | }; 2033 | } 2034 | 2035 | // 12.1.2 Big5 encoder 2036 | /** 2037 | * @constructor 2038 | * @implements {Encoder} 2039 | * @param {{fatal: boolean}} options 2040 | */ 2041 | function Big5Encoder(options) { 2042 | var fatal = options.fatal; 2043 | /** 2044 | * @param {Stream} stream Input stream. 2045 | * @param {number} code_point Next code point read from the stream. 2046 | * @return {(number|!Array.)} Byte(s) to emit. 2047 | */ 2048 | this.handler = function(stream, code_point) { 2049 | // 1. If code point is end-of-stream, return finished. 2050 | if (code_point === end_of_stream) 2051 | return finished; 2052 | 2053 | // 2. If code point is an ASCII code point, return a byte whose 2054 | // value is code point. 2055 | if (isASCIICodePoint(code_point)) 2056 | return code_point; 2057 | 2058 | // 3. Let pointer be the index Big5 pointer for code point. 2059 | var pointer = indexBig5PointerFor(code_point); 2060 | 2061 | // 4. If pointer is null, return error with code point. 2062 | if (pointer === null) 2063 | return encoderError(code_point); 2064 | 2065 | // 5. Let lead be floor(pointer / 157) + 0x81. 2066 | var lead = floor(pointer / 157) + 0x81; 2067 | 2068 | // 6. If lead is less than 0xA1, return error with code point. 2069 | if (lead < 0xA1) 2070 | return encoderError(code_point); 2071 | 2072 | // 7. Let trail be pointer % 157. 2073 | var trail = pointer % 157; 2074 | 2075 | // 8. Let offset be 0x40 if trail is less than 0x3F and 0x62 2076 | // otherwise. 2077 | var offset = trail < 0x3F ? 0x40 : 0x62; 2078 | 2079 | // Return two bytes whose values are lead and trail + offset. 2080 | return [lead, trail + offset]; 2081 | }; 2082 | } 2083 | 2084 | /** @param {{fatal: boolean}} options */ 2085 | encoders['Big5'] = function(options) { 2086 | return new Big5Encoder(options); 2087 | }; 2088 | /** @param {{fatal: boolean}} options */ 2089 | decoders['Big5'] = function(options) { 2090 | return new Big5Decoder(options); 2091 | }; 2092 | 2093 | 2094 | // 2095 | // 13. Legacy multi-byte Japanese encodings 2096 | // 2097 | 2098 | // 13.1 euc-jp 2099 | 2100 | // 13.1.1 euc-jp decoder 2101 | /** 2102 | * @constructor 2103 | * @implements {Decoder} 2104 | * @param {{fatal: boolean}} options 2105 | */ 2106 | function EUCJPDecoder(options) { 2107 | var fatal = options.fatal; 2108 | 2109 | // euc-jp's decoder has an associated euc-jp jis0212 flag 2110 | // (initially unset) and euc-jp lead (initially 0x00). 2111 | var /** @type {boolean} */ eucjp_jis0212_flag = false, 2112 | /** @type {number} */ eucjp_lead = 0x00; 2113 | 2114 | /** 2115 | * @param {Stream} stream The stream of bytes being decoded. 2116 | * @param {number} bite The next byte read from the stream. 2117 | * @return {?(number|!Array.)} The next code point(s) 2118 | * decoded, or null if not enough data exists in the input 2119 | * stream to decode a complete code point. 2120 | */ 2121 | this.handler = function(stream, bite) { 2122 | // 1. If byte is end-of-stream and euc-jp lead is not 0x00, set 2123 | // euc-jp lead to 0x00, and return error. 2124 | if (bite === end_of_stream && eucjp_lead !== 0x00) { 2125 | eucjp_lead = 0x00; 2126 | return decoderError(fatal); 2127 | } 2128 | 2129 | // 2. If byte is end-of-stream and euc-jp lead is 0x00, return 2130 | // finished. 2131 | if (bite === end_of_stream && eucjp_lead === 0x00) 2132 | return finished; 2133 | 2134 | // 3. If euc-jp lead is 0x8E and byte is in the range 0xA1 to 2135 | // 0xDF, inclusive, set euc-jp lead to 0x00 and return a code 2136 | // point whose value is 0xFF61 − 0xA1 + byte. 2137 | if (eucjp_lead === 0x8E && inRange(bite, 0xA1, 0xDF)) { 2138 | eucjp_lead = 0x00; 2139 | return 0xFF61 - 0xA1 + bite; 2140 | } 2141 | 2142 | // 4. If euc-jp lead is 0x8F and byte is in the range 0xA1 to 2143 | // 0xFE, inclusive, set the euc-jp jis0212 flag, set euc-jp lead 2144 | // to byte, and return continue. 2145 | if (eucjp_lead === 0x8F && inRange(bite, 0xA1, 0xFE)) { 2146 | eucjp_jis0212_flag = true; 2147 | eucjp_lead = bite; 2148 | return null; 2149 | } 2150 | 2151 | // 5. If euc-jp lead is not 0x00, let lead be euc-jp lead, set 2152 | // euc-jp lead to 0x00, and run these substeps: 2153 | if (eucjp_lead !== 0x00) { 2154 | var lead = eucjp_lead; 2155 | eucjp_lead = 0x00; 2156 | 2157 | // 1. Let code point be null. 2158 | var code_point = null; 2159 | 2160 | // 2. If lead and byte are both in the range 0xA1 to 0xFE, 2161 | // inclusive, set code point to the index code point for (lead 2162 | // − 0xA1) × 94 + byte − 0xA1 in index jis0208 if the euc-jp 2163 | // jis0212 flag is unset and in index jis0212 otherwise. 2164 | if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) { 2165 | code_point = indexCodePointFor( 2166 | (lead - 0xA1) * 94 + (bite - 0xA1), 2167 | index(!eucjp_jis0212_flag ? 'jis0208' : 'jis0212')); 2168 | } 2169 | 2170 | // 3. Unset the euc-jp jis0212 flag. 2171 | eucjp_jis0212_flag = false; 2172 | 2173 | // 4. If byte is not in the range 0xA1 to 0xFE, inclusive, 2174 | // prepend byte to stream. 2175 | if (!inRange(bite, 0xA1, 0xFE)) 2176 | stream.prepend(bite); 2177 | 2178 | // 5. If code point is null, return error. 2179 | if (code_point === null) 2180 | return decoderError(fatal); 2181 | 2182 | // 6. Return a code point whose value is code point. 2183 | return code_point; 2184 | } 2185 | 2186 | // 6. If byte is an ASCII byte, return a code point whose value 2187 | // is byte. 2188 | if (isASCIIByte(bite)) 2189 | return bite; 2190 | 2191 | // 7. If byte is 0x8E, 0x8F, or in the range 0xA1 to 0xFE, 2192 | // inclusive, set euc-jp lead to byte and return continue. 2193 | if (bite === 0x8E || bite === 0x8F || inRange(bite, 0xA1, 0xFE)) { 2194 | eucjp_lead = bite; 2195 | return null; 2196 | } 2197 | 2198 | // 8. Return error. 2199 | return decoderError(fatal); 2200 | }; 2201 | } 2202 | 2203 | // 13.1.2 euc-jp encoder 2204 | /** 2205 | * @constructor 2206 | * @implements {Encoder} 2207 | * @param {{fatal: boolean}} options 2208 | */ 2209 | function EUCJPEncoder(options) { 2210 | var fatal = options.fatal; 2211 | /** 2212 | * @param {Stream} stream Input stream. 2213 | * @param {number} code_point Next code point read from the stream. 2214 | * @return {(number|!Array.)} Byte(s) to emit. 2215 | */ 2216 | this.handler = function(stream, code_point) { 2217 | // 1. If code point is end-of-stream, return finished. 2218 | if (code_point === end_of_stream) 2219 | return finished; 2220 | 2221 | // 2. If code point is an ASCII code point, return a byte whose 2222 | // value is code point. 2223 | if (isASCIICodePoint(code_point)) 2224 | return code_point; 2225 | 2226 | // 3. If code point is U+00A5, return byte 0x5C. 2227 | if (code_point === 0x00A5) 2228 | return 0x5C; 2229 | 2230 | // 4. If code point is U+203E, return byte 0x7E. 2231 | if (code_point === 0x203E) 2232 | return 0x7E; 2233 | 2234 | // 5. If code point is in the range U+FF61 to U+FF9F, inclusive, 2235 | // return two bytes whose values are 0x8E and code point − 2236 | // 0xFF61 + 0xA1. 2237 | if (inRange(code_point, 0xFF61, 0xFF9F)) 2238 | return [0x8E, code_point - 0xFF61 + 0xA1]; 2239 | 2240 | // 6. If code point is U+2212, set it to U+FF0D. 2241 | if (code_point === 0x2212) 2242 | code_point = 0xFF0D; 2243 | 2244 | // 7. Let pointer be the index pointer for code point in index 2245 | // jis0208. 2246 | var pointer = indexPointerFor(code_point, index('jis0208')); 2247 | 2248 | // 8. If pointer is null, return error with code point. 2249 | if (pointer === null) 2250 | return encoderError(code_point); 2251 | 2252 | // 9. Let lead be floor(pointer / 94) + 0xA1. 2253 | var lead = floor(pointer / 94) + 0xA1; 2254 | 2255 | // 10. Let trail be pointer % 94 + 0xA1. 2256 | var trail = pointer % 94 + 0xA1; 2257 | 2258 | // 11. Return two bytes whose values are lead and trail. 2259 | return [lead, trail]; 2260 | }; 2261 | } 2262 | 2263 | /** @param {{fatal: boolean}} options */ 2264 | encoders['EUC-JP'] = function(options) { 2265 | return new EUCJPEncoder(options); 2266 | }; 2267 | /** @param {{fatal: boolean}} options */ 2268 | decoders['EUC-JP'] = function(options) { 2269 | return new EUCJPDecoder(options); 2270 | }; 2271 | 2272 | // 13.2 iso-2022-jp 2273 | 2274 | // 13.2.1 iso-2022-jp decoder 2275 | /** 2276 | * @constructor 2277 | * @implements {Decoder} 2278 | * @param {{fatal: boolean}} options 2279 | */ 2280 | function ISO2022JPDecoder(options) { 2281 | var fatal = options.fatal; 2282 | /** @enum */ 2283 | var states = { 2284 | ASCII: 0, 2285 | Roman: 1, 2286 | Katakana: 2, 2287 | LeadByte: 3, 2288 | TrailByte: 4, 2289 | EscapeStart: 5, 2290 | Escape: 6 2291 | }; 2292 | // iso-2022-jp's decoder has an associated iso-2022-jp decoder 2293 | // state (initially ASCII), iso-2022-jp decoder output state 2294 | // (initially ASCII), iso-2022-jp lead (initially 0x00), and 2295 | // iso-2022-jp output flag (initially unset). 2296 | var /** @type {number} */ iso2022jp_decoder_state = states.ASCII, 2297 | /** @type {number} */ iso2022jp_decoder_output_state = states.ASCII, 2298 | /** @type {number} */ iso2022jp_lead = 0x00, 2299 | /** @type {boolean} */ iso2022jp_output_flag = false; 2300 | /** 2301 | * @param {Stream} stream The stream of bytes being decoded. 2302 | * @param {number} bite The next byte read from the stream. 2303 | * @return {?(number|!Array.)} The next code point(s) 2304 | * decoded, or null if not enough data exists in the input 2305 | * stream to decode a complete code point. 2306 | */ 2307 | this.handler = function(stream, bite) { 2308 | // switching on iso-2022-jp decoder state: 2309 | switch (iso2022jp_decoder_state) { 2310 | default: 2311 | case states.ASCII: 2312 | // ASCII 2313 | // Based on byte: 2314 | 2315 | // 0x1B 2316 | if (bite === 0x1B) { 2317 | // Set iso-2022-jp decoder state to escape start and return 2318 | // continue. 2319 | iso2022jp_decoder_state = states.EscapeStart; 2320 | return null; 2321 | } 2322 | 2323 | // 0x00 to 0x7F, excluding 0x0E, 0x0F, and 0x1B 2324 | if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E 2325 | && bite !== 0x0F && bite !== 0x1B) { 2326 | // Unset the iso-2022-jp output flag and return a code point 2327 | // whose value is byte. 2328 | iso2022jp_output_flag = false; 2329 | return bite; 2330 | } 2331 | 2332 | // end-of-stream 2333 | if (bite === end_of_stream) { 2334 | // Return finished. 2335 | return finished; 2336 | } 2337 | 2338 | // Otherwise 2339 | // Unset the iso-2022-jp output flag and return error. 2340 | iso2022jp_output_flag = false; 2341 | return decoderError(fatal); 2342 | 2343 | case states.Roman: 2344 | // Roman 2345 | // Based on byte: 2346 | 2347 | // 0x1B 2348 | if (bite === 0x1B) { 2349 | // Set iso-2022-jp decoder state to escape start and return 2350 | // continue. 2351 | iso2022jp_decoder_state = states.EscapeStart; 2352 | return null; 2353 | } 2354 | 2355 | // 0x5C 2356 | if (bite === 0x5C) { 2357 | // Unset the iso-2022-jp output flag and return code point 2358 | // U+00A5. 2359 | iso2022jp_output_flag = false; 2360 | return 0x00A5; 2361 | } 2362 | 2363 | // 0x7E 2364 | if (bite === 0x7E) { 2365 | // Unset the iso-2022-jp output flag and return code point 2366 | // U+203E. 2367 | iso2022jp_output_flag = false; 2368 | return 0x203E; 2369 | } 2370 | 2371 | // 0x00 to 0x7F, excluding 0x0E, 0x0F, 0x1B, 0x5C, and 0x7E 2372 | if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E && bite !== 0x0F 2373 | && bite !== 0x1B && bite !== 0x5C && bite !== 0x7E) { 2374 | // Unset the iso-2022-jp output flag and return a code point 2375 | // whose value is byte. 2376 | iso2022jp_output_flag = false; 2377 | return bite; 2378 | } 2379 | 2380 | // end-of-stream 2381 | if (bite === end_of_stream) { 2382 | // Return finished. 2383 | return finished; 2384 | } 2385 | 2386 | // Otherwise 2387 | // Unset the iso-2022-jp output flag and return error. 2388 | iso2022jp_output_flag = false; 2389 | return decoderError(fatal); 2390 | 2391 | case states.Katakana: 2392 | // Katakana 2393 | // Based on byte: 2394 | 2395 | // 0x1B 2396 | if (bite === 0x1B) { 2397 | // Set iso-2022-jp decoder state to escape start and return 2398 | // continue. 2399 | iso2022jp_decoder_state = states.EscapeStart; 2400 | return null; 2401 | } 2402 | 2403 | // 0x21 to 0x5F 2404 | if (inRange(bite, 0x21, 0x5F)) { 2405 | // Unset the iso-2022-jp output flag and return a code point 2406 | // whose value is 0xFF61 − 0x21 + byte. 2407 | iso2022jp_output_flag = false; 2408 | return 0xFF61 - 0x21 + bite; 2409 | } 2410 | 2411 | // end-of-stream 2412 | if (bite === end_of_stream) { 2413 | // Return finished. 2414 | return finished; 2415 | } 2416 | 2417 | // Otherwise 2418 | // Unset the iso-2022-jp output flag and return error. 2419 | iso2022jp_output_flag = false; 2420 | return decoderError(fatal); 2421 | 2422 | case states.LeadByte: 2423 | // Lead byte 2424 | // Based on byte: 2425 | 2426 | // 0x1B 2427 | if (bite === 0x1B) { 2428 | // Set iso-2022-jp decoder state to escape start and return 2429 | // continue. 2430 | iso2022jp_decoder_state = states.EscapeStart; 2431 | return null; 2432 | } 2433 | 2434 | // 0x21 to 0x7E 2435 | if (inRange(bite, 0x21, 0x7E)) { 2436 | // Unset the iso-2022-jp output flag, set iso-2022-jp lead 2437 | // to byte, iso-2022-jp decoder state to trail byte, and 2438 | // return continue. 2439 | iso2022jp_output_flag = false; 2440 | iso2022jp_lead = bite; 2441 | iso2022jp_decoder_state = states.TrailByte; 2442 | return null; 2443 | } 2444 | 2445 | // end-of-stream 2446 | if (bite === end_of_stream) { 2447 | // Return finished. 2448 | return finished; 2449 | } 2450 | 2451 | // Otherwise 2452 | // Unset the iso-2022-jp output flag and return error. 2453 | iso2022jp_output_flag = false; 2454 | return decoderError(fatal); 2455 | 2456 | case states.TrailByte: 2457 | // Trail byte 2458 | // Based on byte: 2459 | 2460 | // 0x1B 2461 | if (bite === 0x1B) { 2462 | // Set iso-2022-jp decoder state to escape start and return 2463 | // continue. 2464 | iso2022jp_decoder_state = states.EscapeStart; 2465 | return decoderError(fatal); 2466 | } 2467 | 2468 | // 0x21 to 0x7E 2469 | if (inRange(bite, 0x21, 0x7E)) { 2470 | // 1. Set the iso-2022-jp decoder state to lead byte. 2471 | iso2022jp_decoder_state = states.LeadByte; 2472 | 2473 | // 2. Let pointer be (iso-2022-jp lead − 0x21) × 94 + byte − 0x21. 2474 | var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21; 2475 | 2476 | // 3. Let code point be the index code point for pointer in 2477 | // index jis0208. 2478 | var code_point = indexCodePointFor(pointer, index('jis0208')); 2479 | 2480 | // 4. If code point is null, return error. 2481 | if (code_point === null) 2482 | return decoderError(fatal); 2483 | 2484 | // 5. Return a code point whose value is code point. 2485 | return code_point; 2486 | } 2487 | 2488 | // end-of-stream 2489 | if (bite === end_of_stream) { 2490 | // Set the iso-2022-jp decoder state to lead byte, prepend 2491 | // byte to stream, and return error. 2492 | iso2022jp_decoder_state = states.LeadByte; 2493 | stream.prepend(bite); 2494 | return decoderError(fatal); 2495 | } 2496 | 2497 | // Otherwise 2498 | // Set iso-2022-jp decoder state to lead byte and return 2499 | // error. 2500 | iso2022jp_decoder_state = states.LeadByte; 2501 | return decoderError(fatal); 2502 | 2503 | case states.EscapeStart: 2504 | // Escape start 2505 | 2506 | // 1. If byte is either 0x24 or 0x28, set iso-2022-jp lead to 2507 | // byte, iso-2022-jp decoder state to escape, and return 2508 | // continue. 2509 | if (bite === 0x24 || bite === 0x28) { 2510 | iso2022jp_lead = bite; 2511 | iso2022jp_decoder_state = states.Escape; 2512 | return null; 2513 | } 2514 | 2515 | // 2. Prepend byte to stream. 2516 | stream.prepend(bite); 2517 | 2518 | // 3. Unset the iso-2022-jp output flag, set iso-2022-jp 2519 | // decoder state to iso-2022-jp decoder output state, and 2520 | // return error. 2521 | iso2022jp_output_flag = false; 2522 | iso2022jp_decoder_state = iso2022jp_decoder_output_state; 2523 | return decoderError(fatal); 2524 | 2525 | case states.Escape: 2526 | // Escape 2527 | 2528 | // 1. Let lead be iso-2022-jp lead and set iso-2022-jp lead to 2529 | // 0x00. 2530 | var lead = iso2022jp_lead; 2531 | iso2022jp_lead = 0x00; 2532 | 2533 | // 2. Let state be null. 2534 | var state = null; 2535 | 2536 | // 3. If lead is 0x28 and byte is 0x42, set state to ASCII. 2537 | if (lead === 0x28 && bite === 0x42) 2538 | state = states.ASCII; 2539 | 2540 | // 4. If lead is 0x28 and byte is 0x4A, set state to Roman. 2541 | if (lead === 0x28 && bite === 0x4A) 2542 | state = states.Roman; 2543 | 2544 | // 5. If lead is 0x28 and byte is 0x49, set state to Katakana. 2545 | if (lead === 0x28 && bite === 0x49) 2546 | state = states.Katakana; 2547 | 2548 | // 6. If lead is 0x24 and byte is either 0x40 or 0x42, set 2549 | // state to lead byte. 2550 | if (lead === 0x24 && (bite === 0x40 || bite === 0x42)) 2551 | state = states.LeadByte; 2552 | 2553 | // 7. If state is non-null, run these substeps: 2554 | if (state !== null) { 2555 | // 1. Set iso-2022-jp decoder state and iso-2022-jp decoder 2556 | // output state to states. 2557 | iso2022jp_decoder_state = iso2022jp_decoder_state = state; 2558 | 2559 | // 2. Let output flag be the iso-2022-jp output flag. 2560 | var output_flag = iso2022jp_output_flag; 2561 | 2562 | // 3. Set the iso-2022-jp output flag. 2563 | iso2022jp_output_flag = true; 2564 | 2565 | // 4. Return continue, if output flag is unset, and error 2566 | // otherwise. 2567 | return !output_flag ? null : decoderError(fatal); 2568 | } 2569 | 2570 | // 8. Prepend lead and byte to stream. 2571 | stream.prepend([lead, bite]); 2572 | 2573 | // 9. Unset the iso-2022-jp output flag, set iso-2022-jp 2574 | // decoder state to iso-2022-jp decoder output state and 2575 | // return error. 2576 | iso2022jp_output_flag = false; 2577 | iso2022jp_decoder_state = iso2022jp_decoder_output_state; 2578 | return decoderError(fatal); 2579 | } 2580 | }; 2581 | } 2582 | 2583 | // 13.2.2 iso-2022-jp encoder 2584 | /** 2585 | * @constructor 2586 | * @implements {Encoder} 2587 | * @param {{fatal: boolean}} options 2588 | */ 2589 | function ISO2022JPEncoder(options) { 2590 | var fatal = options.fatal; 2591 | // iso-2022-jp's encoder has an associated iso-2022-jp encoder 2592 | // state which is one of ASCII, Roman, and jis0208 (initially 2593 | // ASCII). 2594 | /** @enum */ 2595 | var states = { 2596 | ASCII: 0, 2597 | Roman: 1, 2598 | jis0208: 2 2599 | }; 2600 | var /** @type {number} */ iso2022jp_state = states.ASCII; 2601 | /** 2602 | * @param {Stream} stream Input stream. 2603 | * @param {number} code_point Next code point read from the stream. 2604 | * @return {(number|!Array.)} Byte(s) to emit. 2605 | */ 2606 | this.handler = function(stream, code_point) { 2607 | // 1. If code point is end-of-stream and iso-2022-jp encoder 2608 | // state is not ASCII, prepend code point to stream, set 2609 | // iso-2022-jp encoder state to ASCII, and return three bytes 2610 | // 0x1B 0x28 0x42. 2611 | if (code_point === end_of_stream && 2612 | iso2022jp_state !== states.ASCII) { 2613 | stream.prepend(code_point); 2614 | iso2022jp_state = states.ASCII; 2615 | return [0x1B, 0x28, 0x42]; 2616 | } 2617 | 2618 | // 2. If code point is end-of-stream and iso-2022-jp encoder 2619 | // state is ASCII, return finished. 2620 | if (code_point === end_of_stream && iso2022jp_state === states.ASCII) 2621 | return finished; 2622 | 2623 | // 3. If ISO-2022-JP encoder state is ASCII or Roman, and code 2624 | // point is U+000E, U+000F, or U+001B, return error with U+FFFD. 2625 | if ((iso2022jp_state === states.ASCII || 2626 | iso2022jp_state === states.Roman) && 2627 | (code_point === 0x000E || code_point === 0x000F || 2628 | code_point === 0x001B)) { 2629 | return encoderError(0xFFFD); 2630 | } 2631 | 2632 | // 4. If iso-2022-jp encoder state is ASCII and code point is an 2633 | // ASCII code point, return a byte whose value is code point. 2634 | if (iso2022jp_state === states.ASCII && 2635 | isASCIICodePoint(code_point)) 2636 | return code_point; 2637 | 2638 | // 5. If iso-2022-jp encoder state is Roman and code point is an 2639 | // ASCII code point, excluding U+005C and U+007E, or is U+00A5 2640 | // or U+203E, run these substeps: 2641 | if (iso2022jp_state === states.Roman && 2642 | ((isASCIICodePoint(code_point) && 2643 | code_point !== 0x005C && code_point !== 0x007E) || 2644 | (code_point == 0x00A5 || code_point == 0x203E))) { 2645 | 2646 | // 1. If code point is an ASCII code point, return a byte 2647 | // whose value is code point. 2648 | if (isASCIICodePoint(code_point)) 2649 | return code_point; 2650 | 2651 | // 2. If code point is U+00A5, return byte 0x5C. 2652 | if (code_point === 0x00A5) 2653 | return 0x5C; 2654 | 2655 | // 3. If code point is U+203E, return byte 0x7E. 2656 | if (code_point === 0x203E) 2657 | return 0x7E; 2658 | } 2659 | 2660 | // 6. If code point is an ASCII code point, and iso-2022-jp 2661 | // encoder state is not ASCII, prepend code point to stream, set 2662 | // iso-2022-jp encoder state to ASCII, and return three bytes 2663 | // 0x1B 0x28 0x42. 2664 | if (isASCIICodePoint(code_point) && 2665 | iso2022jp_state !== states.ASCII) { 2666 | stream.prepend(code_point); 2667 | iso2022jp_state = states.ASCII; 2668 | return [0x1B, 0x28, 0x42]; 2669 | } 2670 | 2671 | // 7. If code point is either U+00A5 or U+203E, and iso-2022-jp 2672 | // encoder state is not Roman, prepend code point to stream, set 2673 | // iso-2022-jp encoder state to Roman, and return three bytes 2674 | // 0x1B 0x28 0x4A. 2675 | if ((code_point === 0x00A5 || code_point === 0x203E) && 2676 | iso2022jp_state !== states.Roman) { 2677 | stream.prepend(code_point); 2678 | iso2022jp_state = states.Roman; 2679 | return [0x1B, 0x28, 0x4A]; 2680 | } 2681 | 2682 | // 8. If code point is U+2212, set it to U+FF0D. 2683 | if (code_point === 0x2212) 2684 | code_point = 0xFF0D; 2685 | 2686 | // 9. Let pointer be the index pointer for code point in index 2687 | // jis0208. 2688 | var pointer = indexPointerFor(code_point, index('jis0208')); 2689 | 2690 | // 10. If pointer is null, return error with code point. 2691 | if (pointer === null) 2692 | return encoderError(code_point); 2693 | 2694 | // 11. If iso-2022-jp encoder state is not jis0208, prepend code 2695 | // point to stream, set iso-2022-jp encoder state to jis0208, 2696 | // and return three bytes 0x1B 0x24 0x42. 2697 | if (iso2022jp_state !== states.jis0208) { 2698 | stream.prepend(code_point); 2699 | iso2022jp_state = states.jis0208; 2700 | return [0x1B, 0x24, 0x42]; 2701 | } 2702 | 2703 | // 12. Let lead be floor(pointer / 94) + 0x21. 2704 | var lead = floor(pointer / 94) + 0x21; 2705 | 2706 | // 13. Let trail be pointer % 94 + 0x21. 2707 | var trail = pointer % 94 + 0x21; 2708 | 2709 | // 14. Return two bytes whose values are lead and trail. 2710 | return [lead, trail]; 2711 | }; 2712 | } 2713 | 2714 | /** @param {{fatal: boolean}} options */ 2715 | encoders['ISO-2022-JP'] = function(options) { 2716 | return new ISO2022JPEncoder(options); 2717 | }; 2718 | /** @param {{fatal: boolean}} options */ 2719 | decoders['ISO-2022-JP'] = function(options) { 2720 | return new ISO2022JPDecoder(options); 2721 | }; 2722 | 2723 | // 13.3 Shift_JIS 2724 | 2725 | // 13.3.1 Shift_JIS decoder 2726 | /** 2727 | * @constructor 2728 | * @implements {Decoder} 2729 | * @param {{fatal: boolean}} options 2730 | */ 2731 | function ShiftJISDecoder(options) { 2732 | var fatal = options.fatal; 2733 | // Shift_JIS's decoder has an associated Shift_JIS lead (initially 2734 | // 0x00). 2735 | var /** @type {number} */ Shift_JIS_lead = 0x00; 2736 | /** 2737 | * @param {Stream} stream The stream of bytes being decoded. 2738 | * @param {number} bite The next byte read from the stream. 2739 | * @return {?(number|!Array.)} The next code point(s) 2740 | * decoded, or null if not enough data exists in the input 2741 | * stream to decode a complete code point. 2742 | */ 2743 | this.handler = function(stream, bite) { 2744 | // 1. If byte is end-of-stream and Shift_JIS lead is not 0x00, 2745 | // set Shift_JIS lead to 0x00 and return error. 2746 | if (bite === end_of_stream && Shift_JIS_lead !== 0x00) { 2747 | Shift_JIS_lead = 0x00; 2748 | return decoderError(fatal); 2749 | } 2750 | 2751 | // 2. If byte is end-of-stream and Shift_JIS lead is 0x00, 2752 | // return finished. 2753 | if (bite === end_of_stream && Shift_JIS_lead === 0x00) 2754 | return finished; 2755 | 2756 | // 3. If Shift_JIS lead is not 0x00, let lead be Shift_JIS lead, 2757 | // let pointer be null, set Shift_JIS lead to 0x00, and then run 2758 | // these substeps: 2759 | if (Shift_JIS_lead !== 0x00) { 2760 | var lead = Shift_JIS_lead; 2761 | var pointer = null; 2762 | Shift_JIS_lead = 0x00; 2763 | 2764 | // 1. Let offset be 0x40, if byte is less than 0x7F, and 0x41 2765 | // otherwise. 2766 | var offset = (bite < 0x7F) ? 0x40 : 0x41; 2767 | 2768 | // 2. Let lead offset be 0x81, if lead is less than 0xA0, and 2769 | // 0xC1 otherwise. 2770 | var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1; 2771 | 2772 | // 3. If byte is in the range 0x40 to 0x7E, inclusive, or 0x80 2773 | // to 0xFC, inclusive, set pointer to (lead − lead offset) × 2774 | // 188 + byte − offset. 2775 | if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC)) 2776 | pointer = (lead - lead_offset) * 188 + bite - offset; 2777 | 2778 | // 4. If pointer is in the range 8836 to 10715, inclusive, 2779 | // return a code point whose value is 0xE000 − 8836 + pointer. 2780 | if (inRange(pointer, 8836, 10715)) 2781 | return 0xE000 - 8836 + pointer; 2782 | 2783 | // 5. Let code point be null, if pointer is null, and the 2784 | // index code point for pointer in index jis0208 otherwise. 2785 | var code_point = (pointer === null) ? null : 2786 | indexCodePointFor(pointer, index('jis0208')); 2787 | 2788 | // 6. If code point is null and byte is an ASCII byte, prepend 2789 | // byte to stream. 2790 | if (code_point === null && isASCIIByte(bite)) 2791 | stream.prepend(bite); 2792 | 2793 | // 7. If code point is null, return error. 2794 | if (code_point === null) 2795 | return decoderError(fatal); 2796 | 2797 | // 8. Return a code point whose value is code point. 2798 | return code_point; 2799 | } 2800 | 2801 | // 4. If byte is an ASCII byte or 0x80, return a code point 2802 | // whose value is byte. 2803 | if (isASCIIByte(bite) || bite === 0x80) 2804 | return bite; 2805 | 2806 | // 5. If byte is in the range 0xA1 to 0xDF, inclusive, return a 2807 | // code point whose value is 0xFF61 − 0xA1 + byte. 2808 | if (inRange(bite, 0xA1, 0xDF)) 2809 | return 0xFF61 - 0xA1 + bite; 2810 | 2811 | // 6. If byte is in the range 0x81 to 0x9F, inclusive, or 0xE0 2812 | // to 0xFC, inclusive, set Shift_JIS lead to byte and return 2813 | // continue. 2814 | if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) { 2815 | Shift_JIS_lead = bite; 2816 | return null; 2817 | } 2818 | 2819 | // 7. Return error. 2820 | return decoderError(fatal); 2821 | }; 2822 | } 2823 | 2824 | // 13.3.2 Shift_JIS encoder 2825 | /** 2826 | * @constructor 2827 | * @implements {Encoder} 2828 | * @param {{fatal: boolean}} options 2829 | */ 2830 | function ShiftJISEncoder(options) { 2831 | var fatal = options.fatal; 2832 | /** 2833 | * @param {Stream} stream Input stream. 2834 | * @param {number} code_point Next code point read from the stream. 2835 | * @return {(number|!Array.)} Byte(s) to emit. 2836 | */ 2837 | this.handler = function(stream, code_point) { 2838 | // 1. If code point is end-of-stream, return finished. 2839 | if (code_point === end_of_stream) 2840 | return finished; 2841 | 2842 | // 2. If code point is an ASCII code point or U+0080, return a 2843 | // byte whose value is code point. 2844 | if (isASCIICodePoint(code_point) || code_point === 0x0080) 2845 | return code_point; 2846 | 2847 | // 3. If code point is U+00A5, return byte 0x5C. 2848 | if (code_point === 0x00A5) 2849 | return 0x5C; 2850 | 2851 | // 4. If code point is U+203E, return byte 0x7E. 2852 | if (code_point === 0x203E) 2853 | return 0x7E; 2854 | 2855 | // 5. If code point is in the range U+FF61 to U+FF9F, inclusive, 2856 | // return a byte whose value is code point − 0xFF61 + 0xA1. 2857 | if (inRange(code_point, 0xFF61, 0xFF9F)) 2858 | return code_point - 0xFF61 + 0xA1; 2859 | 2860 | // 6. If code point is U+2212, set it to U+FF0D. 2861 | if (code_point === 0x2212) 2862 | code_point = 0xFF0D; 2863 | 2864 | // 7. Let pointer be the index Shift_JIS pointer for code point. 2865 | var pointer = indexShiftJISPointerFor(code_point); 2866 | 2867 | // 8. If pointer is null, return error with code point. 2868 | if (pointer === null) 2869 | return encoderError(code_point); 2870 | 2871 | // 9. Let lead be floor(pointer / 188). 2872 | var lead = floor(pointer / 188); 2873 | 2874 | // 10. Let lead offset be 0x81, if lead is less than 0x1F, and 2875 | // 0xC1 otherwise. 2876 | var lead_offset = (lead < 0x1F) ? 0x81 : 0xC1; 2877 | 2878 | // 11. Let trail be pointer % 188. 2879 | var trail = pointer % 188; 2880 | 2881 | // 12. Let offset be 0x40, if trail is less than 0x3F, and 0x41 2882 | // otherwise. 2883 | var offset = (trail < 0x3F) ? 0x40 : 0x41; 2884 | 2885 | // 13. Return two bytes whose values are lead + lead offset and 2886 | // trail + offset. 2887 | return [lead + lead_offset, trail + offset]; 2888 | }; 2889 | } 2890 | 2891 | /** @param {{fatal: boolean}} options */ 2892 | encoders['Shift_JIS'] = function(options) { 2893 | return new ShiftJISEncoder(options); 2894 | }; 2895 | /** @param {{fatal: boolean}} options */ 2896 | decoders['Shift_JIS'] = function(options) { 2897 | return new ShiftJISDecoder(options); 2898 | }; 2899 | 2900 | // 2901 | // 14. Legacy multi-byte Korean encodings 2902 | // 2903 | 2904 | // 14.1 euc-kr 2905 | 2906 | // 14.1.1 euc-kr decoder 2907 | /** 2908 | * @constructor 2909 | * @implements {Decoder} 2910 | * @param {{fatal: boolean}} options 2911 | */ 2912 | function EUCKRDecoder(options) { 2913 | var fatal = options.fatal; 2914 | 2915 | // euc-kr's decoder has an associated euc-kr lead (initially 0x00). 2916 | var /** @type {number} */ euckr_lead = 0x00; 2917 | /** 2918 | * @param {Stream} stream The stream of bytes being decoded. 2919 | * @param {number} bite The next byte read from the stream. 2920 | * @return {?(number|!Array.)} The next code point(s) 2921 | * decoded, or null if not enough data exists in the input 2922 | * stream to decode a complete code point. 2923 | */ 2924 | this.handler = function(stream, bite) { 2925 | // 1. If byte is end-of-stream and euc-kr lead is not 0x00, set 2926 | // euc-kr lead to 0x00 and return error. 2927 | if (bite === end_of_stream && euckr_lead !== 0) { 2928 | euckr_lead = 0x00; 2929 | return decoderError(fatal); 2930 | } 2931 | 2932 | // 2. If byte is end-of-stream and euc-kr lead is 0x00, return 2933 | // finished. 2934 | if (bite === end_of_stream && euckr_lead === 0) 2935 | return finished; 2936 | 2937 | // 3. If euc-kr lead is not 0x00, let lead be euc-kr lead, let 2938 | // pointer be null, set euc-kr lead to 0x00, and then run these 2939 | // substeps: 2940 | if (euckr_lead !== 0x00) { 2941 | var lead = euckr_lead; 2942 | var pointer = null; 2943 | euckr_lead = 0x00; 2944 | 2945 | // 1. If byte is in the range 0x41 to 0xFE, inclusive, set 2946 | // pointer to (lead − 0x81) × 190 + (byte − 0x41). 2947 | if (inRange(bite, 0x41, 0xFE)) 2948 | pointer = (lead - 0x81) * 190 + (bite - 0x41); 2949 | 2950 | // 2. Let code point be null, if pointer is null, and the 2951 | // index code point for pointer in index euc-kr otherwise. 2952 | var code_point = (pointer === null) 2953 | ? null : indexCodePointFor(pointer, index('euc-kr')); 2954 | 2955 | // 3. If code point is null and byte is an ASCII byte, prepend 2956 | // byte to stream. 2957 | if (pointer === null && isASCIIByte(bite)) 2958 | stream.prepend(bite); 2959 | 2960 | // 4. If code point is null, return error. 2961 | if (code_point === null) 2962 | return decoderError(fatal); 2963 | 2964 | // 5. Return a code point whose value is code point. 2965 | return code_point; 2966 | } 2967 | 2968 | // 4. If byte is an ASCII byte, return a code point whose value 2969 | // is byte. 2970 | if (isASCIIByte(bite)) 2971 | return bite; 2972 | 2973 | // 5. If byte is in the range 0x81 to 0xFE, inclusive, set 2974 | // euc-kr lead to byte and return continue. 2975 | if (inRange(bite, 0x81, 0xFE)) { 2976 | euckr_lead = bite; 2977 | return null; 2978 | } 2979 | 2980 | // 6. Return error. 2981 | return decoderError(fatal); 2982 | }; 2983 | } 2984 | 2985 | // 14.1.2 euc-kr encoder 2986 | /** 2987 | * @constructor 2988 | * @implements {Encoder} 2989 | * @param {{fatal: boolean}} options 2990 | */ 2991 | function EUCKREncoder(options) { 2992 | var fatal = options.fatal; 2993 | /** 2994 | * @param {Stream} stream Input stream. 2995 | * @param {number} code_point Next code point read from the stream. 2996 | * @return {(number|!Array.)} Byte(s) to emit. 2997 | */ 2998 | this.handler = function(stream, code_point) { 2999 | // 1. If code point is end-of-stream, return finished. 3000 | if (code_point === end_of_stream) 3001 | return finished; 3002 | 3003 | // 2. If code point is an ASCII code point, return a byte whose 3004 | // value is code point. 3005 | if (isASCIICodePoint(code_point)) 3006 | return code_point; 3007 | 3008 | // 3. Let pointer be the index pointer for code point in index 3009 | // euc-kr. 3010 | var pointer = indexPointerFor(code_point, index('euc-kr')); 3011 | 3012 | // 4. If pointer is null, return error with code point. 3013 | if (pointer === null) 3014 | return encoderError(code_point); 3015 | 3016 | // 5. Let lead be floor(pointer / 190) + 0x81. 3017 | var lead = floor(pointer / 190) + 0x81; 3018 | 3019 | // 6. Let trail be pointer % 190 + 0x41. 3020 | var trail = (pointer % 190) + 0x41; 3021 | 3022 | // 7. Return two bytes whose values are lead and trail. 3023 | return [lead, trail]; 3024 | }; 3025 | } 3026 | 3027 | /** @param {{fatal: boolean}} options */ 3028 | encoders['EUC-KR'] = function(options) { 3029 | return new EUCKREncoder(options); 3030 | }; 3031 | /** @param {{fatal: boolean}} options */ 3032 | decoders['EUC-KR'] = function(options) { 3033 | return new EUCKRDecoder(options); 3034 | }; 3035 | 3036 | 3037 | // 3038 | // 15. Legacy miscellaneous encodings 3039 | // 3040 | 3041 | // 15.1 replacement 3042 | 3043 | // Not needed - API throws RangeError 3044 | 3045 | // 15.2 Common infrastructure for utf-16be and utf-16le 3046 | 3047 | /** 3048 | * @param {number} code_unit 3049 | * @param {boolean} utf16be 3050 | * @return {!Array.} bytes 3051 | */ 3052 | function convertCodeUnitToBytes(code_unit, utf16be) { 3053 | // 1. Let byte1 be code unit >> 8. 3054 | var byte1 = code_unit >> 8; 3055 | 3056 | // 2. Let byte2 be code unit & 0x00FF. 3057 | var byte2 = code_unit & 0x00FF; 3058 | 3059 | // 3. Then return the bytes in order: 3060 | // utf-16be flag is set: byte1, then byte2. 3061 | if (utf16be) 3062 | return [byte1, byte2]; 3063 | // utf-16be flag is unset: byte2, then byte1. 3064 | return [byte2, byte1]; 3065 | } 3066 | 3067 | // 15.2.1 shared utf-16 decoder 3068 | /** 3069 | * @constructor 3070 | * @implements {Decoder} 3071 | * @param {boolean} utf16_be True if big-endian, false if little-endian. 3072 | * @param {{fatal: boolean}} options 3073 | */ 3074 | function UTF16Decoder(utf16_be, options) { 3075 | var fatal = options.fatal; 3076 | var /** @type {?number} */ utf16_lead_byte = null, 3077 | /** @type {?number} */ utf16_lead_surrogate = null; 3078 | /** 3079 | * @param {Stream} stream The stream of bytes being decoded. 3080 | * @param {number} bite The next byte read from the stream. 3081 | * @return {?(number|!Array.)} The next code point(s) 3082 | * decoded, or null if not enough data exists in the input 3083 | * stream to decode a complete code point. 3084 | */ 3085 | this.handler = function(stream, bite) { 3086 | // 1. If byte is end-of-stream and either utf-16 lead byte or 3087 | // utf-16 lead surrogate is not null, set utf-16 lead byte and 3088 | // utf-16 lead surrogate to null, and return error. 3089 | if (bite === end_of_stream && (utf16_lead_byte !== null || 3090 | utf16_lead_surrogate !== null)) { 3091 | return decoderError(fatal); 3092 | } 3093 | 3094 | // 2. If byte is end-of-stream and utf-16 lead byte and utf-16 3095 | // lead surrogate are null, return finished. 3096 | if (bite === end_of_stream && utf16_lead_byte === null && 3097 | utf16_lead_surrogate === null) { 3098 | return finished; 3099 | } 3100 | 3101 | // 3. If utf-16 lead byte is null, set utf-16 lead byte to byte 3102 | // and return continue. 3103 | if (utf16_lead_byte === null) { 3104 | utf16_lead_byte = bite; 3105 | return null; 3106 | } 3107 | 3108 | // 4. Let code unit be the result of: 3109 | var code_unit; 3110 | if (utf16_be) { 3111 | // utf-16be decoder flag is set 3112 | // (utf-16 lead byte << 8) + byte. 3113 | code_unit = (utf16_lead_byte << 8) + bite; 3114 | } else { 3115 | // utf-16be decoder flag is unset 3116 | // (byte << 8) + utf-16 lead byte. 3117 | code_unit = (bite << 8) + utf16_lead_byte; 3118 | } 3119 | // Then set utf-16 lead byte to null. 3120 | utf16_lead_byte = null; 3121 | 3122 | // 5. If utf-16 lead surrogate is not null, let lead surrogate 3123 | // be utf-16 lead surrogate, set utf-16 lead surrogate to null, 3124 | // and then run these substeps: 3125 | if (utf16_lead_surrogate !== null) { 3126 | var lead_surrogate = utf16_lead_surrogate; 3127 | utf16_lead_surrogate = null; 3128 | 3129 | // 1. If code unit is in the range U+DC00 to U+DFFF, 3130 | // inclusive, return a code point whose value is 0x10000 + 3131 | // ((lead surrogate − 0xD800) << 10) + (code unit − 0xDC00). 3132 | if (inRange(code_unit, 0xDC00, 0xDFFF)) { 3133 | return 0x10000 + (lead_surrogate - 0xD800) * 0x400 + 3134 | (code_unit - 0xDC00); 3135 | } 3136 | 3137 | // 2. Prepend the sequence resulting of converting code unit 3138 | // to bytes using utf-16be decoder flag to stream and return 3139 | // error. 3140 | stream.prepend(convertCodeUnitToBytes(code_unit, utf16_be)); 3141 | return decoderError(fatal); 3142 | } 3143 | 3144 | // 6. If code unit is in the range U+D800 to U+DBFF, inclusive, 3145 | // set utf-16 lead surrogate to code unit and return continue. 3146 | if (inRange(code_unit, 0xD800, 0xDBFF)) { 3147 | utf16_lead_surrogate = code_unit; 3148 | return null; 3149 | } 3150 | 3151 | // 7. If code unit is in the range U+DC00 to U+DFFF, inclusive, 3152 | // return error. 3153 | if (inRange(code_unit, 0xDC00, 0xDFFF)) 3154 | return decoderError(fatal); 3155 | 3156 | // 8. Return code point code unit. 3157 | return code_unit; 3158 | }; 3159 | } 3160 | 3161 | // 15.2.2 shared utf-16 encoder 3162 | /** 3163 | * @constructor 3164 | * @implements {Encoder} 3165 | * @param {boolean} utf16_be True if big-endian, false if little-endian. 3166 | * @param {{fatal: boolean}} options 3167 | */ 3168 | function UTF16Encoder(utf16_be, options) { 3169 | var fatal = options.fatal; 3170 | /** 3171 | * @param {Stream} stream Input stream. 3172 | * @param {number} code_point Next code point read from the stream. 3173 | * @return {(number|!Array.)} Byte(s) to emit. 3174 | */ 3175 | this.handler = function(stream, code_point) { 3176 | // 1. If code point is end-of-stream, return finished. 3177 | if (code_point === end_of_stream) 3178 | return finished; 3179 | 3180 | // 2. If code point is in the range U+0000 to U+FFFF, inclusive, 3181 | // return the sequence resulting of converting code point to 3182 | // bytes using utf-16be encoder flag. 3183 | if (inRange(code_point, 0x0000, 0xFFFF)) 3184 | return convertCodeUnitToBytes(code_point, utf16_be); 3185 | 3186 | // 3. Let lead be ((code point − 0x10000) >> 10) + 0xD800, 3187 | // converted to bytes using utf-16be encoder flag. 3188 | var lead = convertCodeUnitToBytes( 3189 | ((code_point - 0x10000) >> 10) + 0xD800, utf16_be); 3190 | 3191 | // 4. Let trail be ((code point − 0x10000) & 0x3FF) + 0xDC00, 3192 | // converted to bytes using utf-16be encoder flag. 3193 | var trail = convertCodeUnitToBytes( 3194 | ((code_point - 0x10000) & 0x3FF) + 0xDC00, utf16_be); 3195 | 3196 | // 5. Return a byte sequence of lead followed by trail. 3197 | return lead.concat(trail); 3198 | }; 3199 | } 3200 | 3201 | // 15.3 utf-16be 3202 | // 15.3.1 utf-16be decoder 3203 | /** @param {{fatal: boolean}} options */ 3204 | encoders['UTF-16BE'] = function(options) { 3205 | return new UTF16Encoder(true, options); 3206 | }; 3207 | // 15.3.2 utf-16be encoder 3208 | /** @param {{fatal: boolean}} options */ 3209 | decoders['UTF-16BE'] = function(options) { 3210 | return new UTF16Decoder(true, options); 3211 | }; 3212 | 3213 | // 15.4 utf-16le 3214 | // 15.4.1 utf-16le decoder 3215 | /** @param {{fatal: boolean}} options */ 3216 | encoders['UTF-16LE'] = function(options) { 3217 | return new UTF16Encoder(false, options); 3218 | }; 3219 | // 15.4.2 utf-16le encoder 3220 | /** @param {{fatal: boolean}} options */ 3221 | decoders['UTF-16LE'] = function(options) { 3222 | return new UTF16Decoder(false, options); 3223 | }; 3224 | 3225 | // 15.5 x-user-defined 3226 | 3227 | // 15.5.1 x-user-defined decoder 3228 | /** 3229 | * @constructor 3230 | * @implements {Decoder} 3231 | * @param {{fatal: boolean}} options 3232 | */ 3233 | function XUserDefinedDecoder(options) { 3234 | var fatal = options.fatal; 3235 | /** 3236 | * @param {Stream} stream The stream of bytes being decoded. 3237 | * @param {number} bite The next byte read from the stream. 3238 | * @return {?(number|!Array.)} The next code point(s) 3239 | * decoded, or null if not enough data exists in the input 3240 | * stream to decode a complete code point. 3241 | */ 3242 | this.handler = function(stream, bite) { 3243 | // 1. If byte is end-of-stream, return finished. 3244 | if (bite === end_of_stream) 3245 | return finished; 3246 | 3247 | // 2. If byte is an ASCII byte, return a code point whose value 3248 | // is byte. 3249 | if (isASCIIByte(bite)) 3250 | return bite; 3251 | 3252 | // 3. Return a code point whose value is 0xF780 + byte − 0x80. 3253 | return 0xF780 + bite - 0x80; 3254 | }; 3255 | } 3256 | 3257 | // 15.5.2 x-user-defined encoder 3258 | /** 3259 | * @constructor 3260 | * @implements {Encoder} 3261 | * @param {{fatal: boolean}} options 3262 | */ 3263 | function XUserDefinedEncoder(options) { 3264 | var fatal = options.fatal; 3265 | /** 3266 | * @param {Stream} stream Input stream. 3267 | * @param {number} code_point Next code point read from the stream. 3268 | * @return {(number|!Array.)} Byte(s) to emit. 3269 | */ 3270 | this.handler = function(stream, code_point) { 3271 | // 1.If code point is end-of-stream, return finished. 3272 | if (code_point === end_of_stream) 3273 | return finished; 3274 | 3275 | // 2. If code point is an ASCII code point, return a byte whose 3276 | // value is code point. 3277 | if (isASCIICodePoint(code_point)) 3278 | return code_point; 3279 | 3280 | // 3. If code point is in the range U+F780 to U+F7FF, inclusive, 3281 | // return a byte whose value is code point − 0xF780 + 0x80. 3282 | if (inRange(code_point, 0xF780, 0xF7FF)) 3283 | return code_point - 0xF780 + 0x80; 3284 | 3285 | // 4. Return error with code point. 3286 | return encoderError(code_point); 3287 | }; 3288 | } 3289 | 3290 | /** @param {{fatal: boolean}} options */ 3291 | encoders['x-user-defined'] = function(options) { 3292 | return new XUserDefinedEncoder(options); 3293 | }; 3294 | /** @param {{fatal: boolean}} options */ 3295 | decoders['x-user-defined'] = function(options) { 3296 | return new XUserDefinedDecoder(options); 3297 | }; 3298 | 3299 | if (!global['TextEncoder']) 3300 | global['TextEncoder'] = TextEncoder; 3301 | if (!global['TextDecoder']) 3302 | global['TextDecoder'] = TextDecoder; 3303 | 3304 | if (typeof module !== "undefined" && module.exports) { 3305 | module.exports = { 3306 | TextEncoder: global['TextEncoder'], 3307 | TextDecoder: global['TextDecoder'], 3308 | EncodingIndexes: global["encoding-indexes"] 3309 | }; 3310 | } 3311 | 3312 | // For strict environments where `this` inside the global scope 3313 | // is `undefined`, take a pure object instead 3314 | }(this || {})); -------------------------------------------------------------------------------- /program/utils/gifmaker/gifmaker.js: -------------------------------------------------------------------------------- 1 | 2 | var encoding = require('../encoding'); 3 | 4 | var TextDecoder = TextDecoder?TextDecoder:encoding.TextDecoder; 5 | 6 | var TextEncoder = TextEncoder?TextEncoder:encoding.TextEncoder; 7 | 8 | let wasm; 9 | 10 | const heap = new Array(32).fill(undefined); 11 | 12 | heap.push(undefined, null, true, false); 13 | 14 | function getObject(idx) { return heap[idx]; } 15 | 16 | let heap_next = heap.length; 17 | 18 | function dropObject(idx) { 19 | if (idx < 36) return; 20 | heap[idx] = heap_next; 21 | heap_next = idx; 22 | } 23 | 24 | function takeObject(idx) { 25 | const ret = getObject(idx); 26 | dropObject(idx); 27 | return ret; 28 | } 29 | 30 | function addHeapObject(obj) { 31 | if (heap_next === heap.length) heap.push(heap.length + 1); 32 | const idx = heap_next; 33 | heap_next = heap[idx]; 34 | 35 | heap[idx] = obj; 36 | return idx; 37 | } 38 | 39 | const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); 40 | 41 | cachedTextDecoder.decode(); 42 | 43 | let cachegetUint8Memory0 = null; 44 | function getUint8Memory0() { 45 | if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { 46 | cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); 47 | } 48 | return cachegetUint8Memory0; 49 | } 50 | 51 | function getStringFromWasm0(ptr, len) { 52 | return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 53 | } 54 | 55 | let cachegetInt32Memory0 = null; 56 | function getInt32Memory0() { 57 | if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { 58 | cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); 59 | } 60 | return cachegetInt32Memory0; 61 | } 62 | /** 63 | * 初始化Gif编码器 64 | * @param {number} width 65 | * @param {number} height 66 | * @param {number} fps 67 | */ 68 | export function create(width, height, fps) { 69 | try { 70 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 71 | wasm.create(retptr, width, height, fps); 72 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 73 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 74 | if (r1) { 75 | throw takeObject(r0); 76 | } 77 | } finally { 78 | wasm.__wbindgen_add_to_stack_pointer(16); 79 | } 80 | } 81 | 82 | /** 83 | * 添加图片(png文件) 84 | * @param {Uint8ClampedArray} src 85 | */ 86 | export function addPng(src) { 87 | try { 88 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 89 | wasm.addPng(retptr, addHeapObject(src)); 90 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 91 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 92 | if (r1) { 93 | throw takeObject(r0); 94 | } 95 | } finally { 96 | wasm.__wbindgen_add_to_stack_pointer(16); 97 | } 98 | } 99 | 100 | /** 101 | * 生成gif 102 | * @returns {Uint8ClampedArray} 103 | */ 104 | export function getFile() { 105 | try { 106 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 107 | wasm.getFile(retptr); 108 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 109 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 110 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 111 | if (r2) { 112 | throw takeObject(r1); 113 | } 114 | return takeObject(r0); 115 | } finally { 116 | wasm.__wbindgen_add_to_stack_pointer(16); 117 | } 118 | } 119 | 120 | /** 121 | * 生成验证API网关的密钥Header 122 | * @param {number} timestamp 123 | * @returns {object} 124 | */ 125 | export function generateHeaders(timestamp) { 126 | try { 127 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 128 | wasm.generateHeaders(retptr, timestamp); 129 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 130 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 131 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 132 | if (r2) { 133 | throw takeObject(r1); 134 | } 135 | return takeObject(r0); 136 | } finally { 137 | wasm.__wbindgen_add_to_stack_pointer(16); 138 | } 139 | } 140 | 141 | /** 142 | */ 143 | export function run() { 144 | wasm.run(); 145 | } 146 | 147 | function handleError(f, args) { 148 | try { 149 | return f.apply(this, args); 150 | } catch (e) { 151 | wasm.__wbindgen_exn_store(addHeapObject(e)); 152 | } 153 | } 154 | async function load(module, imports) { 155 | const instance = await WXWebAssembly.instantiate(module, imports); 156 | return instance; 157 | } 158 | 159 | async function init(input) { 160 | // if (typeof input === 'undefined') { 161 | // input = new URL('gifmaker_bg.wasm', import.meta.url); 162 | // } 163 | const imports = {}; 164 | imports.wbg = {}; 165 | imports.wbg.__wbindgen_object_drop_ref = function(arg0) { 166 | takeObject(arg0); 167 | }; 168 | imports.wbg.__wbg_log_d5bfa7ff2fe7bf7a = function(arg0, arg1) { 169 | console.log(getStringFromWasm0(arg0, arg1)); 170 | }; 171 | imports.wbg.__wbg_length_03aa6b704dddbb65 = function(arg0) { 172 | const ret = getObject(arg0).length; 173 | return ret; 174 | }; 175 | imports.wbg.__wbindgen_memory = function() { 176 | const ret = wasm.memory; 177 | return addHeapObject(ret); 178 | }; 179 | imports.wbg.__wbg_buffer_7af23f65f6c64548 = function(arg0) { 180 | const ret = getObject(arg0).buffer; 181 | return addHeapObject(ret); 182 | }; 183 | imports.wbg.__wbg_new_29c1fbb3653d149b = function(arg0) { 184 | const ret = new Uint8ClampedArray(getObject(arg0)); 185 | return addHeapObject(ret); 186 | }; 187 | imports.wbg.__wbg_set_be99deedf864f8ce = function(arg0, arg1, arg2) { 188 | getObject(arg0).set(getObject(arg1), arg2 >>> 0); 189 | }; 190 | imports.wbg.__wbg_newwithlength_a5f4b145df9c7ffe = function(arg0) { 191 | const ret = new Uint8ClampedArray(arg0 >>> 0); 192 | return addHeapObject(ret); 193 | }; 194 | imports.wbg.__wbg_newwithbyteoffsetandlength_deb5bc15ce3ebd3e = function(arg0, arg1, arg2) { 195 | const ret = new Uint8ClampedArray(getObject(arg0), arg1 >>> 0, arg2 >>> 0); 196 | return addHeapObject(ret); 197 | }; 198 | imports.wbg.__wbg_new_215d09e61e981309 = function() { 199 | const ret = new Map(); 200 | return addHeapObject(ret); 201 | }; 202 | imports.wbg.__wbindgen_string_new = function(arg0, arg1) { 203 | const ret = getStringFromWasm0(arg0, arg1); 204 | return addHeapObject(ret); 205 | }; 206 | imports.wbg.__wbg_set_90aa7d9acde85660 = function(arg0, arg1, arg2) { 207 | const ret = getObject(arg0).set(getObject(arg1), getObject(arg2)); 208 | return addHeapObject(ret); 209 | }; 210 | imports.wbg.__wbg_fromEntries_753a5d6f3530e028 = function() { return handleError(function (arg0) { 211 | const ret = Object.fromEntries(getObject(arg0)); 212 | return addHeapObject(ret); 213 | }, arguments) }; 214 | imports.wbg.__wbindgen_throw = function(arg0, arg1) { 215 | throw new Error(getStringFromWasm0(arg0, arg1)); 216 | }; 217 | 218 | // if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { 219 | // input = fetch(input); 220 | // } 221 | 222 | 223 | 224 | const { instance, module } = await load(await input, imports); 225 | 226 | wasm = instance.exports; 227 | init.__wbindgen_wasm_module = module; 228 | wasm.__wbindgen_start(); 229 | return wasm; 230 | } 231 | 232 | export default init; 233 | 234 | -------------------------------------------------------------------------------- /program/utils/gifmaker/gifmaker_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/program/utils/gifmaker/gifmaker_bg.wasm -------------------------------------------------------------------------------- /program/utils/img_sec_check.js: -------------------------------------------------------------------------------- 1 | import { generateHeaders } from './gifmaker/gifmaker' 2 | 3 | function getHeader(){ 4 | return new Promise((resolve, reject) => { 5 | wx.request({ 6 | url: 'https://www.ccfish.run/serverless/server-utc-now', 7 | fail(res){ 8 | reject(); 9 | }, 10 | success (res) { 11 | var time = res.data; 12 | let headers = generateHeaders(time); 13 | resolve(headers); 14 | } 15 | }); 16 | }); 17 | } 18 | 19 | module.exports = { 20 | //文字审查 21 | checkText(msg){ 22 | return new Promise(async (resolve, reject) => { 23 | let header = await getHeader(); 24 | wx.request({ 25 | url: 'https://www.ccfish.run/serverless/wx-sec-check?type=msg', 26 | method: 'POST', 27 | data: msg, 28 | header, 29 | fail(res){ 30 | console.error('文字审核失败:', res); 31 | reject(); 32 | }, 33 | success (res) { 34 | console.log('文字审核结果',res.data); 35 | if(res.data.errcode == 0){ 36 | resolve(); 37 | }else{ 38 | reject(); 39 | } 40 | } 41 | }); 42 | }); 43 | }, 44 | 45 | //图片审查 46 | checkImage(filePath) { 47 | console.log("checkImage", filePath); 48 | return new Promise(async (resolve, reject) => { 49 | let header = await getHeader(); 50 | //文件转base64 51 | var fs = wx.getFileSystemManager(); 52 | fs.readFile({ 53 | filePath, 54 | // encoding: 'base64', 55 | success(res) { 56 | console.log('审查图片大小:'+res.data.byteLength); 57 | wx.request({ 58 | url: 'https://www.ccfish.run/serverless/wx-sec-check?type=img', 59 | method: 'POST', 60 | data: res.data, 61 | header, 62 | fail(res){ 63 | console.error('图片审核失败:', res); 64 | reject(); 65 | }, 66 | success (checkRes) { 67 | console.log('图片审核结果',checkRes.data); 68 | if(checkRes.data.errcode == 0){ 69 | resolve(); 70 | }else{ 71 | reject(); 72 | } 73 | } 74 | }); 75 | }, 76 | fail(res) { 77 | console.error('文件读取失败', res); 78 | reject(); 79 | } 80 | }) 81 | }); 82 | } 83 | }; -------------------------------------------------------------------------------- /program/utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : '0' + n 15 | } 16 | 17 | module.exports = { 18 | formatTime: formatTime 19 | } 20 | -------------------------------------------------------------------------------- /screenrecorder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/screenrecorder.gif -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/screenshot.jpg -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # server 2 | 3 | 图片审查server端代码 基于[fermyon/spin](https://github.com/fermyon/spin) Serverless -------------------------------------------------------------------------------- /server/server_utc_now/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasi" 3 | -------------------------------------------------------------------------------- /server/server_utc_now/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /Cargo.lock 3 | /*.txt -------------------------------------------------------------------------------- /server/server_utc_now/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server-utc-now" 3 | authors = ["JiaYe "] 4 | description = "server-utc-now" 5 | version = "1.0.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | # Useful crate to handle errors. 13 | anyhow = "1" 14 | # Crate to simplify working with bytes. 15 | bytes = "1" 16 | # General-purpose crate with common HTTP types. 17 | http = "0.2" 18 | # The Spin SDK. 19 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v0.6.0" } 20 | # Crate that generates Rust Wasm bindings from a WebAssembly interface. 21 | wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" } 22 | 23 | serde_json = "1" 24 | serde = { version = "1", features = ["derive"] } 25 | chrono = "0.4.22" 26 | 27 | [workspace] 28 | exclude = ["test_client"] 29 | 30 | [profile.release] 31 | lto = true 32 | opt-level = 'z' 33 | codegen-units = 1 34 | # panic = 'abort' -------------------------------------------------------------------------------- /server/server_utc_now/build-run.cmd: -------------------------------------------------------------------------------- 1 | spin build --up --log-dir ./ -------------------------------------------------------------------------------- /server/server_utc_now/run.cmd: -------------------------------------------------------------------------------- 1 | spin up --log-dir ./ -------------------------------------------------------------------------------- /server/server_utc_now/spin.toml: -------------------------------------------------------------------------------- 1 | spin_version = "1" 2 | authors = ["JiaYe "] 3 | description = "spin-server" 4 | name = "spin-server" 5 | trigger = { type = "http", base = "/" } 6 | version = "1.0.0" 7 | 8 | [[component]] 9 | id = "server-utc-now" 10 | source = "target/wasm32-wasi/release/server_utc_now.wasm" 11 | allowed_http_hosts = ["insecure:allow-all"] 12 | [component.trigger] 13 | route = "/server-utc-now" 14 | [component.build] 15 | command = "cargo build --target wasm32-wasi --release" 16 | -------------------------------------------------------------------------------- /server/server_utc_now/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use chrono::{Utc, Local}; 3 | use serde_json::json; 4 | use spin_sdk::{ 5 | http::{Request, Response}, 6 | http_component, 7 | }; 8 | 9 | #[http_component] 10 | fn server_utc_now(_req: Request) -> Result { 11 | let now = Utc::now(); 12 | println!("server_utc_now: {:?} 本地时间:{}", now, Local::now()); 13 | let now_timestamp = now.timestamp_millis(); 14 | let res = json!(now_timestamp); 15 | let res_data = serde_json::to_string(&res)?; 16 | Ok(http::Response::builder() 17 | .status(200) 18 | .body(Some(res_data.into()))?) 19 | } -------------------------------------------------------------------------------- /server/wx_sec_check/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasi" 3 | -------------------------------------------------------------------------------- /server/wx_sec_check/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /Cargo.lock 3 | /*.txt -------------------------------------------------------------------------------- /server/wx_sec_check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wx-sec-check" 3 | authors = ["JiaYe "] 4 | description = "wx-sec-check" 5 | version = "1.0.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | # Useful crate to handle errors. 13 | anyhow = "1" 14 | # Crate to simplify working with bytes. 15 | bytes = "1" 16 | # General-purpose crate with common HTTP types. 17 | http = "0.2" 18 | # The Spin SDK. 19 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v0.6.0" } 20 | # Crate that generates Rust Wasm bindings from a WebAssembly interface. 21 | wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" } 22 | 23 | tokio = { version = "1.21.2", features = ["sync", "macros" , "io-util", "rt" , "time"] } 24 | serde_json = "1" 25 | multipart = "0.18" 26 | log = "0.4" 27 | env_logger = "0.9.1" 28 | base64 = "0.20.0-alpha.1" 29 | serde = { version = "1", features = ["derive"] } 30 | chrono = "0.4.22" 31 | magic-crypt = "3.1.12" 32 | mime_guess = "2.0.4" 33 | 34 | [workspace] 35 | exclude = ["test_client"] -------------------------------------------------------------------------------- /server/wx_sec_check/build-run.cmd: -------------------------------------------------------------------------------- 1 | spin build --up --log-dir ./ -------------------------------------------------------------------------------- /server/wx_sec_check/run.cmd: -------------------------------------------------------------------------------- 1 | spin up --log-dir ./ -------------------------------------------------------------------------------- /server/wx_sec_check/spin.toml: -------------------------------------------------------------------------------- 1 | spin_version = "1" 2 | authors = ["JiaYe "] 3 | description = "spin-server" 4 | name = "spin-server" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "wx-sec-check" 10 | source = "target/wasm32-wasi/release/wx_sec_check.wasm" 11 | allowed_http_hosts = ["insecure:allow-all"] 12 | [component.trigger] 13 | route = "/wx-sec-check" 14 | [component.build] 15 | command = "cargo build --target wasm32-wasi --release" 16 | -------------------------------------------------------------------------------- /server/wx_sec_check/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use env_logger::{Builder, Target}; 3 | use sec_check::{msg_sec_check, CheckResult, img_sec_check}; 4 | use spin_sdk::{ 5 | http::{Request, Response}, 6 | http_component, 7 | }; 8 | 9 | use tools::{bytes_to_string, bytes_to_vec}; 10 | mod tools; 11 | mod sec_check; 12 | mod secret; 13 | mod token; 14 | 15 | #[http_component] 16 | fn wx_sec_check(req: Request) -> Result { 17 | 18 | let mut builder = Builder::from_default_env(); 19 | builder.target(Target::Stdout); 20 | builder.filter_level(log::LevelFilter::Info); 21 | builder.init(); 22 | 23 | tokio::runtime::Builder::new_current_thread() 24 | .enable_all() 25 | .build() 26 | .unwrap() 27 | .block_on(async { 28 | let res = match main(req).await{ 29 | Err(err) => { 30 | CheckResult{ 31 | errcode: -1, 32 | errmsg: format!("{:?}", err) 33 | } 34 | } 35 | Ok(res) => res 36 | }; 37 | let res_data = serde_json::to_string(&res)?; 38 | return Ok(http::Response::builder() 39 | .status(200) 40 | .body(Some(res_data.into()))?); 41 | }) 42 | } 43 | 44 | async fn main(req: Request) -> Result{ 45 | 46 | let _ = secret::check_secret(req.headers())?; 47 | 48 | let query_str = format!("info={:?}", req.uri().query()); 49 | 50 | if req.body().is_none(){ 51 | return Ok(CheckResult{ 52 | errcode: -1, 53 | errmsg: format!("body is none") 54 | }); 55 | } 56 | 57 | if query_str.contains("type=msg"){ 58 | let msg = bytes_to_string(req.body())?; 59 | msg_sec_check(&msg).await 60 | }else{ 61 | let img = bytes_to_vec(req.body())?; 62 | img_sec_check(img).await 63 | } 64 | } -------------------------------------------------------------------------------- /server/wx_sec_check/src/sec_check.rs: -------------------------------------------------------------------------------- 1 | use std::{time::Instant, io::Read}; 2 | 3 | use crate::{token::*, tools::bytes_to_string}; 4 | use anyhow::Result; 5 | use bytes::Bytes; 6 | use log::info; 7 | use multipart::client::lazy::Multipart; 8 | use serde::{Deserialize, Serialize}; 9 | use serde_json::json; 10 | #[derive(Debug, Serialize, Deserialize)] 11 | pub struct CheckResult { 12 | pub(crate) errcode: i32, 13 | pub(crate) errmsg: String, 14 | } 15 | 16 | #[derive(Debug, Serialize, Deserialize)] 17 | pub struct WXUser { 18 | pub openid: String, 19 | } 20 | #[derive(Debug, Serialize, Deserialize)] 21 | pub struct WxSession { 22 | pub openid: String, 23 | pub session_key: String, 24 | } 25 | /// FormData 26 | #[derive(Debug, Clone, Serialize, Deserialize)] 27 | pub struct FormData { 28 | pub js_code: Option, 29 | pub openid: Option, 30 | } 31 | 32 | /// 小程序登陆 33 | #[derive(Debug, Clone, Serialize, Deserialize)] 34 | pub struct LoginWx { 35 | pub js_code: String, 36 | pub encrypted_data: String, 37 | pub iv: String, 38 | } 39 | 40 | /// 审核图片 41 | pub async fn _img_sec_check_base64(img: &str) -> Result { 42 | let bytes = base64::decode(img)?; 43 | Ok(img_sec_check(bytes).await?) 44 | } 45 | 46 | /// 审核图片 47 | pub async fn img_sec_check(image: Vec) -> Result { 48 | //获取token 49 | let access_token = get_token().await?; 50 | 51 | let url = format!("https://api.weixin.qq.com/wxa/img_sec_check?access_token={access_token}"); 52 | 53 | info!("img_sec_check url={url}"); 54 | let now = Instant::now(); 55 | 56 | let mut form = Multipart::new(); 57 | form.add_stream("media", &*image, Some("media".to_string()), None); 58 | let mut parpared = form.prepare()?; 59 | let boundaray = parpared.boundary().to_string(); 60 | let mut form_data = Vec::new(); 61 | parpared.read_to_end(&mut form_data)?; 62 | let data_bytes = Bytes::from(form_data); 63 | 64 | 65 | let res = spin_sdk::http::send( 66 | http::Request::builder() 67 | .method("POST") 68 | .uri(url) 69 | .header("content-type", &format!("multipart/form-data; boundary={boundaray}")) 70 | .body(Some(data_bytes))?, 71 | )?; 72 | let json = bytes_to_string(res.body())?; 73 | 74 | info!("img_sec_check调用耗时: {}ms", now.elapsed().as_millis()); 75 | 76 | Ok(serde_json::from_str(&json)?) 77 | } 78 | 79 | ///审核文本 80 | /// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.msgSecCheck.html 81 | /// scene 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志) 82 | /// https://zhidao.baidu.com/question/687447786330732684.html 83 | pub async fn msg_sec_check(content: &str) -> Result { 84 | //获取token 85 | let access_token = get_token().await?; 86 | 87 | let url = format!("https://api.weixin.qq.com/wxa/msg_sec_check?access_token={access_token}"); 88 | 89 | info!("msg_sec_check url={url}"); 90 | 91 | let now = Instant::now(); 92 | 93 | let json_data = json!({ "content": content }).to_string(); 94 | let data_bytes = Bytes::from(json_data.as_bytes().to_vec()); 95 | 96 | let res = spin_sdk::http::send( 97 | http::Request::builder() 98 | .method("POST") 99 | .uri(url) 100 | .body(Some(data_bytes))?, 101 | )?; 102 | let json = bytes_to_string(res.body())?; 103 | 104 | info!("img_sec_check调用耗时: {}ms", now.elapsed().as_millis()); 105 | 106 | Ok(serde_json::from_str(&json)?) 107 | } -------------------------------------------------------------------------------- /server/wx_sec_check/src/secret.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use chrono::Utc; 3 | use http::HeaderMap; 4 | use log::info; 5 | use magic_crypt::{new_magic_crypt, MagicCryptTrait}; 6 | 7 | pub const APPID: &str = env!("gm_app_id"); 8 | pub const APPSECRET: &str = env!("gm_app_key"); 9 | pub const SECRET_KEY: &str = env!("gm_secret_key"); 10 | pub const SECRET_IV: &str = env!("gm_secret_iv"); 11 | 12 | pub fn check_secret(headers: &HeaderMap) -> anyhow::Result<()>{ 13 | match headers.get("secret"){ 14 | None => { 15 | Err(anyhow::anyhow!("no secret!")) 16 | }, 17 | Some(secret) => { 18 | // secret: 当前时间戳字符串 -> bytes -> 加密 -> base64 19 | let secret = secret.to_str()?; 20 | info!("secret={}", secret); 21 | 22 | let crypt = new_magic_crypt!(SECRET_KEY, 128, SECRET_IV); 23 | 24 | let timestamp_str = crypt.decrypt_base64_to_string(secret)?; 25 | 26 | let timestamp:i64 = timestamp_str.parse()?; 27 | 28 | let now = Utc::now().timestamp_millis(); 29 | 30 | // 5秒之内 31 | if (timestamp - now).abs() > 5000{ 32 | return Err(anyhow::anyhow!("secret error! secret={}", secret)); 33 | } 34 | 35 | Ok(()) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /server/wx_sec_check/src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::{secret::{APPID, APPSECRET}, tools::bytes_to_string}; 2 | use anyhow::{anyhow, Result}; 3 | use chrono::prelude::*; 4 | use log::info; 5 | use serde::{Deserialize, Serialize}; 6 | use std::sync::Mutex; 7 | 8 | static TOKEN: Mutex> = Mutex::new(None); 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub struct Token { 12 | access_token: String, 13 | expires_in: i64, 14 | #[serde(default = "default_create_time")] 15 | create_time: i64, 16 | } 17 | 18 | impl Token { 19 | fn is_expired(&self) -> bool { 20 | //秒 21 | let now = Local::now().timestamp(); 22 | //提前一分钟刷新token 23 | let expires_in = self.create_time + self.expires_in - 60; 24 | now > expires_in 25 | } 26 | } 27 | 28 | fn default_create_time() -> i64 { 29 | Local::now().timestamp() 30 | } 31 | 32 | //获取可用的token 33 | pub async fn get_token() -> Result { 34 | match TOKEN.lock() { 35 | Err(err) => Err(anyhow!("{:?}", err)), 36 | Ok(mut token) => { 37 | if token.is_none() { 38 | token.replace(request_token().await?); 39 | } 40 | 41 | if token.as_ref().unwrap().is_expired() { 42 | token.replace(request_token().await?); 43 | } 44 | 45 | let token = token.as_ref().unwrap(); 46 | 47 | Ok(token.access_token.clone()) 48 | } 49 | } 50 | } 51 | 52 | //刷新token 53 | pub async fn _refresh_token() -> Result { 54 | match TOKEN.lock() { 55 | Err(err) => Err(anyhow!("{:?}", err)), 56 | Ok(mut token) => { 57 | token.replace(request_token().await?); 58 | let token = token.as_mut().unwrap(); 59 | Ok(token.access_token.clone()) 60 | } 61 | } 62 | } 63 | 64 | pub async fn request_token() -> Result { 65 | info!("request_token"); 66 | let res = spin_sdk::http::send( 67 | http::Request::builder() 68 | .method("GET") 69 | .uri(format!("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={APPID}&secret={APPSECRET}")) 70 | .body(None)?, 71 | )?; 72 | let json = bytes_to_string(res.body())?; 73 | 74 | Ok(serde_json::from_str(&json)?) 75 | } 76 | -------------------------------------------------------------------------------- /server/wx_sec_check/src/tools.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use bytes::Bytes; 5 | 6 | pub fn bytes_to_string(bytes: &Option) -> Result{ 7 | if bytes.is_none(){ 8 | return Err(anyhow!("bytes is null")); 9 | } 10 | 11 | let mut json = String::new(); 12 | let bytes = bytes.as_ref().unwrap(); 13 | bytes.as_ref().read_to_string(&mut json)?; 14 | Ok(json) 15 | } 16 | 17 | pub fn bytes_to_vec(bytes: &Option) -> Result>{ 18 | if bytes.is_none(){ 19 | return Err(anyhow!("bytes is null")); 20 | } 21 | 22 | let mut data = Vec::new(); 23 | let bytes = bytes.as_ref().unwrap(); 24 | bytes.as_ref().read_to_end(&mut data)?; 25 | Ok(data) 26 | } -------------------------------------------------------------------------------- /server/wx_sec_check/test_client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | /pkg/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | /.history -------------------------------------------------------------------------------- /server/wx_sec_check/test_client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | reqwest = { version = "0.11", default-features=false, features = ["json", "multipart", "rustls-tls"] } 8 | tokio = { version = "1", features = ["full"] } 9 | serde_json = "1" 10 | anyhow = "1" 11 | rand = "0.8.5" 12 | base64 = "0.20.0-alpha.1" 13 | chrono = "0.4.22" 14 | magic-crypt = "3.1.10" -------------------------------------------------------------------------------- /server/wx_sec_check/test_client/src/main.rs: -------------------------------------------------------------------------------- 1 | use chrono::Local; 2 | use magic_crypt::{new_magic_crypt, MagicCryptTrait}; 3 | use reqwest::{header::{HeaderMap, HeaderValue}, Body}; 4 | use anyhow::Result; 5 | use serde_json::Value; 6 | 7 | const SECRET_KEY: &str = env!("gm_secret_key"); 8 | const SECRET_IV: &str = env!("gm_secret_iv"); 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | println!("SECRET_KEY={SECRET_KEY}"); 13 | println!("SECRET_IV={SECRET_IV}"); 14 | 15 | let client = reqwest::Client::new(); 16 | 17 | let current_time = Local::now().timestamp_millis(); 18 | 19 | println!("current_time={current_time}"); 20 | 21 | let crypt = new_magic_crypt!(SECRET_KEY, 128, SECRET_IV); 22 | 23 | let encrypted_time_str = crypt.encrypt_str_to_base64(format!("{current_time}")); 24 | 25 | println!("encrypted_time_str={encrypted_time_str}"); 26 | 27 | // let server = "http://127.0.0.1:3000/"; 28 | let server = "https://www.ccfish.run/serverless/"; 29 | 30 | let mut headers = HeaderMap::new(); 31 | headers.append("secret", HeaderValue::from_str(&encrypted_time_str)?); 32 | 33 | let img = include_bytes!("../test.png").to_vec(); 34 | 35 | let res:Value = client 36 | .post(format!("{server}wx-sec-check?type=img")) 37 | .headers(headers.clone()) 38 | .body(Body::from(img)) 39 | .send() 40 | .await? 41 | .json() 42 | .await?; 43 | 44 | println!("图片审查结果:{:?}", res); 45 | 46 | let msg = "hello!"; 47 | 48 | let res:Value = client 49 | .post(format!("{server}wx-sec-check?type=msg")) 50 | .headers(headers) 51 | .body(msg) 52 | .send() 53 | .await? 54 | .json() 55 | .await?; 56 | 57 | println!("文字审查结果:{:?}", res); 58 | 59 | Ok(()) 60 | } -------------------------------------------------------------------------------- /server/wx_sec_check/test_client/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planet0104/miniprogram-gifmaker/cf6e8702d24dec9ba15552073d06ff0467a5a456/server/wx_sec_check/test_client/test.png --------------------------------------------------------------------------------