diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..d5876c1
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["es2015", "stage-0"],
+ "plugins": ["transform-runtime"]
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..9d08a1a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..6ec882d
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+app/node_modules/**
+app/dist/**
+app/electron.js
+app/src/qrcode/**
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..0cacc67
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,19 @@
+module.exports = {
+ root: true,
+ // https://github.com/vuejs/eslint-config-vue
+ extends: 'vue',
+ // required to lint *.vue files
+ plugins: [
+ 'html'
+ ],
+ // add your custom rules here
+ 'rules': {
+ "no-sequences": [0],
+ "no-debugger": process.env.NODE_ENV === 'production' ? 2 : 0
+ },
+ globals: {
+ "Notification": true,
+ "it": true,
+ "describe": true
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fe3875d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+app/dist/index.html
+app/dist/build.js
+builds/*
+node_modules
+npm-debug.log
+npm-debug.log.*
+thumbs.db
+!.gitkeep
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..ffd0c07
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,44 @@
+osx_image: xcode7.3
+
+sudo: required
+dist: trusty
+
+language: c
+
+matrix:
+ include:
+ - os: osx
+ - os: linux
+ env: CC=clang CXX=clang++ npm_config_clang=1
+ compiler: clang
+
+addons:
+ apt:
+ sources:
+ - ubuntu-toolchain-r-test
+ packages:
+ - icnsutils
+ - graphicsmagick
+ - xz-utils
+ - rpm
+
+cache:
+ directories:
+ - node_modules
+ - app/node_modules
+ - $HOME/.electron
+ - $HOME/.cache
+
+before_install:
+ - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull
+
+install:
+- nvm install 6
+- npm install
+
+script:
+- npm run build
+
+branches:
+ only:
+ - master
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..8efa392
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ // Use IntelliSense to learn about possible Node.js debug attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "启动程序",
+ "program": "${workspaceRoot}/app/electron.js",
+ "cwd": "${workspaceRoot}",
+ "stopOnEntry": false,
+ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..92bfe77
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,21 @@
+FROM ubuntu-upstart
+COPY app/ /home/electron-ssr/app
+COPY build/ /home/electron-ssr/build
+COPY tasks/ /home/electron-ssr/tasks
+COPY config.js /home/electron-ssr/config.js
+COPY package.json /home/electron-ssr/package.json
+COPY webpack.config.js /home/electron-ssr/webpack.config.js
+COPY .babelrc /home/electron-ssr/.babelrc
+RUN \
+ sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \
+ && apt-get update -y \
+ && apt-get install curl icnsutils graphicsmagick xz-utils rpm --no-install-recommends -y \
+ && curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash \
+ && export NVM_DIR="$HOME/.nvm" \
+ && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" \
+ && nvm install node \
+ && node -v && npm -v \
+ && cd /home/electron-ssr \
+ && npm i \
+ && npm run build:linux
+VOLUME ["/home/electron-ssr/builds"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..04a776b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 erguotou520
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.en.md b/README.en.md
new file mode 100644
index 0000000..843794d
--- /dev/null
+++ b/README.en.md
@@ -0,0 +1,68 @@
+# electron-ssr
+Shadowsocksr client that coding with electron and vue
+[中文版说明](./README.md)
+
+## Feature
+- Close to tray
+- Crash Reporting
+- Update and launched notification
+- Support all of the shadowsocksr python version's function
+
+## Pre-requirement
+- [python](https://www.python.org/downloads/) to be installed
+
+## Screenshot
+
+
+
+
+## Download
+Go to [release](https://github.com/erguotou520/electron-ssr/releases)
+
+## Update history
+- 0.1.1
+ * use `child_process` to run `python` command for all platform as `root` permission is required by `-d` mode in linux
+ * fix some config bug and logical issue
+ * hide icon in dock for mac
+ * open config file from tray
+- 0.1.0
+ * Support to open log file
+ * Deamon mode for unix system using `-d` while child process for windows
+ * Support `ssr://` and `ss://` conversion to each other
+ * Support scan screen qrcode
+ * Support to add ssr methods and protocols and obfses
+ * Now need to set `shadowsocksr` python path
+
+## Config and log file path
+* windows: `C:\Users\{your username}\AppData\Local\Programs\electron-ssr`
+* linux: `~/.config/electron-ssr`
+* mac: `~/Library/Application Support/electron-ssr`
+
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:9080
+npm run dev
+
+# lint all JS/Vue component files in `app/src`
+npm run lint
+
+# run webpack in production
+npm run pack
+
+# build electron app for production
+npm run build
+
+# remove all files under builds
+npm run build:clean
+
+# qrcode scheme unit test
+npm run test:unit
+```
+
+## ShadowsocksR Doc Reference
+- [Python client setup (Mult language)](https://github.com/breakwa11/shadowsocks-rss/wiki/Python-client-setup-(Mult-language))
+- [SSR QRcode scheme](https://github.com/breakwa11/shadowsocks-rss/wiki/SSR-QRcode-scheme)
diff --git a/README.md b/README.md
index a073100..30c4f29 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,67 @@
-Say goodbye to yesterday.
+# electron-ss
+使用`electron`和`vue`开发的跨平台`shadowsocksr`客户端
+English document [here](./README.en.md)
-Say hello to tomorrow.
+## 功能
+- 跨平台功能支持
+- 支持python版`shadowsocksr`的所有功能
+- 当有可更新版本时给出通知
-Remember you can be better.
+## 环境要求
+- 只需要系统已安装 [python](https://www.python.org/downloads/) 可正常执行`shadowsocksr`的python版即可
-Be yourself and do yourself.
+## 页面截图
+
+
+
-Year by year, day by day.
+## 下载
+请前往 [release](https://github.com/erguotou520/electron-ssr/releases) 下载
-Enhance yourself to meet challenges.
+## 更新历史
+- 0.1.1
+ * 统一使用`child_process`运行`python`命令,linux系统下`-d`模式需要`root`权限
+ * 修改一些配置文件代码的逻辑问题
+ * mac版在dock上不显示图标
+ * 支持从菜单中打开配置文件
+- 0.1.0
+ * 支持从托盘中打开日志文件
+ * unix系统使用`-d`模式运行,windows系统使用非`-d`模式运行
+ * 支持 `ssr://` 和 `ss://` 链接直接的互换
+ * 支持扫描屏幕二维码(但需要确保屏幕上只能有一个二维码,即使是本软件的默认二维码也不能出现在屏幕中)
+ * 支持在工具初始化时新增和删除加密方法、协议以及混淆协议,初始化完成后请直接修改配置文件(修改后需重启生效)
+ * 初始化时要求选择python版 `shadowsocksr` 的目录地址
+
+## 配置文件和日志的目录
+* windows: `C:\Users\{your username}\AppData\Local\Programs\electron-ssr`
+* linux: `~/.config/electron-ssr`
+* mac: `~/Library/Application Support/electron-ssr`
+
+## 开发和构建命令
+
+``` bash
+# 安装依赖
+npm install
+
+# 启动一个支持热重载的服务 localhost:9080
+npm run dev
+
+# 检查 `app/src` 目录下所有 JS/Vue 代码
+npm run lint
+
+# 应用打包
+npm run pack
+
+# 应用构建
+npm run build
+
+# 删除构建的目录
+npm run build:clean
+
+# 单元测试
+npm run test:unit
+```
+
+## ShadowsocksR 参考文档
+- [Python client setup (Mult language)](https://github.com/breakwa11/shadowsocks-rss/wiki/Python-client-setup-(Mult-language))
+- [SSR QRcode scheme](https://github.com/breakwa11/shadowsocks-rss/wiki/SSR-QRcode-scheme)
diff --git a/app/build/config.gypi b/app/build/config.gypi
new file mode 100644
index 0000000..d33c8e7
--- /dev/null
+++ b/app/build/config.gypi
@@ -0,0 +1,61 @@
+# Do not edit. File was generated by node-gyp's "configure" step
+{
+ "target_defaults": {
+ "cflags": [],
+ "default_configuration": "Release",
+ "defines": [],
+ "include_dirs": [],
+ "libraries": []
+ },
+ "variables": {
+ "asan": 0,
+ "coverage": "false",
+ "debug_devtools": "node",
+ "force_dynamic_crt": 0,
+ "host_arch": "x64",
+ "icu_data_file": "icudt58l.dat",
+ "icu_data_in": "..\\..\\deps/icu-small\\source/data/in\\icudt58l.dat",
+ "icu_endianness": "l",
+ "icu_gyp_path": "tools/icu/icu-generic.gyp",
+ "icu_locales": "en,root",
+ "icu_path": "deps/icu-small",
+ "icu_small": "true",
+ "icu_ver_major": "58",
+ "node_byteorder": "little",
+ "node_enable_d8": "false",
+ "node_enable_v8_vtunejit": "false",
+ "node_install_npm": "true",
+ "node_module_version": 48,
+ "node_no_browser_globals": "false",
+ "node_prefix": "/usr/local",
+ "node_release_urlbase": "https://nodejs.org/download/release/",
+ "node_shared": "false",
+ "node_shared_cares": "false",
+ "node_shared_http_parser": "false",
+ "node_shared_libuv": "false",
+ "node_shared_openssl": "false",
+ "node_shared_zlib": "false",
+ "node_tag": "",
+ "node_use_bundled_v8": "true",
+ "node_use_dtrace": "false",
+ "node_use_etw": "true",
+ "node_use_lttng": "false",
+ "node_use_openssl": "true",
+ "node_use_perfctr": "true",
+ "node_use_v8_platform": "true",
+ "openssl_fips": "",
+ "openssl_no_asm": 0,
+ "shlib_suffix": "so.48",
+ "target_arch": "x64",
+ "v8_enable_gdbjit": 0,
+ "v8_enable_i18n_support": 1,
+ "v8_inspector": "true",
+ "v8_no_strict_aliasing": 1,
+ "v8_optimized_debug": 0,
+ "v8_random_seed": 0,
+ "v8_use_snapshot": "true",
+ "want_separate_host_toolset": 0,
+ "nodedir": "C:\\Users\\t2cloud\\.node-gyp\\6.10.1",
+ "standalone_static_library": 1
+ }
+}
diff --git a/app/client.js b/app/client.js
new file mode 100644
index 0000000..48e926e
--- /dev/null
+++ b/app/client.js
@@ -0,0 +1,62 @@
+const path = require('path')
+const fs = require('fs')
+const exec = require('child_process').exec
+const treeKill = require('tree-kill')
+const storage = require('./storage')
+const os = require('os')
+const EOL = os.EOL
+
+let localPyPath
+let child
+
+function now () {
+ const now = new Date()
+ return `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
+}
+
+function execCmd (command) {
+ child = exec(command)
+ child.stdout.on('data', data => {
+ storage.saveLogs(`${now()} stdout:${EOL}${data}`)
+ })
+ child.stderr.on('data', data => {
+ storage.saveLogs(`${now()} stderr:${EOL}${data}`)
+ })
+ child.on('close', code => {
+ console.log(`child process exist with code ${code}`)
+ })
+ return child
+}
+
+module.exports.setup = function (storePath, config) {
+ localPyPath = path.join(storePath, 'local.py')
+ console.log(localPyPath)
+ return fs.existsSync(localPyPath)
+}
+
+module.exports.stop = function () {
+ if (child && child.pid) {
+ console.log('kill python client')
+ treeKill(child.pid)
+ child = null
+ }
+}
+
+module.exports.run = function (enable, config) {
+ // stop preview
+ module.exports.stop()
+ if (enable) {
+ const params = []
+ params.push(`-s ${config.host}`)
+ params.push(`-p ${config.port}`)
+ params.push(`-b ${config.localAddr}`)
+ params.push(`-l ${config.localPort}`)
+ params.push(`-k ${config.password}`)
+ params.push(`-m ${config.method}`)
+ params.push(`-O ${config.protocol}`)
+ config.obfs && params.push(`-o ${config.obfs}`)
+ const command = `python ${localPyPath} ${params.join(' ')}`
+ console.log(command)
+ child = execCmd(command)
+ }
+}
diff --git a/app/dist/.gitkeep b/app/dist/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/electron.js b/app/electron.js
new file mode 100644
index 0000000..7aa4539
--- /dev/null
+++ b/app/electron.js
@@ -0,0 +1,229 @@
+'use strict'
+
+const { app, BrowserWindow, ipcMain, net, shell } = require('electron')
+const AutoLaunch = require('auto-launch')
+const { crashReporter } = require('electron')
+const path = require('path')
+const fsExtra = require('fs-extra')
+const tray = require('./tray.js')
+const client = require('./client.js')
+const storage = require('./storage.js')
+
+const AutoLauncher = new AutoLaunch({
+ name: 'ShadowsocksR Client',
+ isHidden: true,
+ mac: {
+ useLaunchAgent: true
+ }
+})
+
+let mainWindow
+let config = {}
+let trayEvent
+let appIcon = null
+let storedConfig
+let appConfigPath = app.getPath('userData')
+
+fsExtra.ensureDirSync(appConfigPath)
+
+if (process.env.NODE_ENV === 'development') {
+ config = require('../config')
+ config.url = `http://localhost:${config.port}`
+} else {
+ config.devtron = false
+ config.url = `file://${__dirname}/dist/index.html`
+}
+
+function createWindow () {
+ if (process.platform === 'darwin') {
+ app.dock.hide()
+ }
+ /**
+ * Initial window options
+ */
+ mainWindow = new BrowserWindow({
+ height: 420,
+ width: 880,
+ resizable: false,
+ minimizable: false,
+ maximizable: false,
+ show: false
+ })
+
+ mainWindow.setMenu(null)
+
+ mainWindow.loadURL(config.url)
+
+ if (process.env.NODE_ENV === 'development') {
+ BrowserWindow.addDevToolsExtension(path.join(__dirname, '../node_modules/devtron'))
+
+ let installExtension = require('electron-devtools-installer')
+
+ installExtension.default(installExtension.VUEJS_DEVTOOLS)
+ .then((name) => mainWindow.webContents.openDevTools())
+ .catch((err) => console.log('An error occurred: ', err))
+ }
+
+ // hide to tray when window closed
+ mainWindow.on('close', (e) => {
+ // e.preventDefault()
+ mainWindow.hide()
+ })
+
+ mainWindow.on('closed', () => {
+ mainWindow = null
+ })
+
+ console.log('mainWindow opened')
+}
+
+function showWindow() {
+ mainWindow.show()
+}
+
+function quitHandler() {
+ client.stop()
+ if (mainWindow) {
+ mainWindow.destroy()
+ }
+ tray.destroy()
+ app.quit()
+}
+
+function execHandler (args) {
+ mainWindow.webContents.send('exec-error', args)
+}
+
+app.on('ready', () => {
+ // crash report
+ crashReporter.start({
+ productName: 'ShadowsocksR Client',
+ companyName: 'erguotou520',
+ submitURL: 'https://ssr-crash-server.herokuapp.com/',
+ autoSubmit: true
+ })
+ // init storage
+ storage.setup(appConfigPath)
+ // get configs
+ storedConfig = storage.getConfig()
+ // setup client if inited
+ if (storedConfig.pyPath) {
+ client.setup(storedConfig.pyPath)
+ // if enable then start
+ if (storedConfig.enable && storedConfig.configs[storedConfig.selected]) {
+ client.run(storedConfig.enable, storedConfig.configs[storedConfig.selected])
+ }
+ }
+ // create main window
+ createWindow()
+ // init tray
+ trayEvent = tray.setup(storedConfig)
+ // tray event
+ trayEvent.on('change-enable', (enable) => {
+ storedConfig.enable = enable
+ storage.saveConfig()
+ const selectedConfigIndex = tray.getMenuConfig().selected
+ if (selectedConfigIndex > -1) {
+ // exec python command
+ client.run(enable, storedConfig.configs[selectedConfigIndex])
+ }
+ if (!enable) {
+ client.stop()
+ }
+ }).on('change-auto-launch', (isAutoLaunch) => {
+ storedConfig.autoLaunch = isAutoLaunch
+ storage.saveConfig()
+ if (isAutoLaunch) {
+ AutoLauncher.enable()
+ } else {
+ AutoLauncher.disable()
+ }
+ }).on('qr-scan', () => {
+ mainWindow.webContents.send('take-screencapture')
+ }).on('change-selected', (index) => {
+ storedConfig.selected = index
+ storage.saveConfig()
+ if (index > -1) {
+ client.run(storedConfig.enable, storedConfig.configs[index])
+ }
+ }).on('exit', quitHandler).on('open', showWindow).on('open-config', () => {
+ if (!shell.openItem(storage.getConfigPath())) {
+ console.error('Error to open config file.')
+ }
+ }).on('open-log', () => {
+ if (!shell.openItem(storage.getLogPath())) {
+ console.error('Error to open log file.')
+ }
+ })
+
+ // when loaded, init configs
+ mainWindow.webContents.once('did-finish-load', () => {
+ // has stored configs, then hide
+ if (storedConfig.configs.length > 0) {
+ mainWindow.hide()
+ } else {
+ mainWindow.show()
+ }
+ // init gui configs
+ mainWindow.webContents.send('init-config', storedConfig)
+ // version check
+ net.request('https://raw.githubusercontent.com/erguotou520/electron-ssr/master/app/package.json').on('response', (response) => {
+ response.on('data', (chunk) => {
+ const remotePkg = JSON.parse(chunk.toString())
+ const currentVersion = require('./package.json').version
+ if (remotePkg.version > currentVersion) {
+ mainWindow.webContents.send('new-version', currentVersion, remotePkg.version)
+ }
+ })
+ }).end()
+ })
+})
+
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') {
+ client.stop()
+ app.quit()
+ }
+})
+
+app.on('activate', () => {
+ if (mainWindow === null) {
+ createWindow()
+ }
+})
+
+// ipc channels
+ipcMain.on('resize', (e, width, height) => {
+ mainWindow.setSize(width, height)
+ mainWindow.center()
+}).on('set-py-path', (e, path) => {
+ e.returnValue = client.setup(path)
+}).on('update-config', (e, field, value) => {
+ // save config
+ storedConfig[field] = value
+ if (field === 'configs') {
+ // refresh tray menu list
+ tray.refreshConfigs(storedConfig.configs, storedConfig.selected)
+ if (value.length) {
+ if (storedConfig.selected < 0) {
+ storedConfig.selected = value.length - 1
+ }
+ client.run(storedConfig.enable, value[storedConfig.selected])
+ } else {
+ storedConfig.selected = -1
+ storedConfig.enable = false
+ client.stop()
+ }
+ }
+ storage.saveConfig()
+}).on('re-init', e => {
+ e.sender.send('init-config', storedConfig)
+}).on('scaned-config', (e, newConfig) => {
+ storedConfig.configs.push(newConfig)
+ storage.saveConfig()
+ tray.refreshConfigs(storedConfig.configs, storedConfig.selected)
+}).on('open-window', () => {
+ mainWindow.show()
+}).on('hide-window', () => {
+ mainWindow.hide()
+})
diff --git a/app/index.ejs b/app/index.ejs
new file mode 100644
index 0000000..a408ee3
--- /dev/null
+++ b/app/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
+
diff --git a/app/package.json b/app/package.json
new file mode 100644
index 0000000..1e2a238
--- /dev/null
+++ b/app/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "electron-ssr",
+ "version": "0.1.2",
+ "description": "ShadowsocksR client using electron",
+ "main": "electron.js",
+ "dependencies": {
+ "auto-launch": "^5.0.1",
+ "fs-extra": "^1.0.0",
+ "qr-image": "^3.2.0",
+ "raven-js": "^3.16.0",
+ "tree-kill": "^1.1.0",
+ "urlsafe-base64": "^1.0.0",
+ "vue": "^2.0.1",
+ "vue-electron": "^1.0.0"
+ },
+ "devDependencies": {},
+ "author": "erguotou "
+}
diff --git a/app/src/App.vue b/app/src/App.vue
new file mode 100644
index 0000000..1cfba2c
--- /dev/null
+++ b/app/src/App.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
diff --git a/app/src/Config.js b/app/src/Config.js
new file mode 100644
index 0000000..162bc14
--- /dev/null
+++ b/app/src/Config.js
@@ -0,0 +1,114 @@
+const Base64 = require('urlsafe-base64')
+
+function encode (str) {
+ return Base64.encode(Buffer.from(str, 'utf-8'))
+}
+
+function decode (str) {
+ return Base64.decode(str).toString('utf-8')
+}
+
+global.encode = encode
+global.decode = decode
+
+module.exports = class Config {
+ constructor (config) {
+ this.host = '127.0.0.1'
+ this.port = '8388'
+ this.password = '0'
+ this.method = 'aes-256-cfb'
+ this.protocol = 'origin'
+ this.obfs = 'plain'
+ this.obfsparam = ''
+ this.remark = ''
+ this.localAddr = '127.0.0.1'
+ this.localPort = '1080'
+ // this.udpport = false
+ // this.uot = false
+ Object.assign(this, config)
+ }
+
+ isValid () {
+ return !!(this.host && this.port && this.password && this.method && this.protocol && this.obfs)
+ }
+
+ getSSRLink () {
+ const required = [this.host, this.port, this.protocol, this.method, this.obfs, encode(this.password)]
+ const others = []
+ this.obfsparam && others.push(`obfsparam=${encode(this.obfsparam)}`)
+ this.remark && others.push(`remarks=${encode(this.remark)}`)
+ // this.udpport && others.push(`udpport=${this.udpport}`)
+ // this.uot && others.push(`uot=${this.uot}`)
+ const link = 'ssr://' + encode(required.join(':') + '/?' + others.join('&'))
+ return link
+ }
+
+ setSSRLink (link) {
+ if (link) {
+ try {
+ const body = link.substring(6)
+ const decoded = decode(body)
+ const _split = decoded.split('/?')
+ const required = _split[0]
+ const others = _split[1]
+ const requiredSplit = required.split(':')
+ const otherSplit = {}
+ others.split('&').forEach(item => {
+ const _params = item.split('=')
+ otherSplit[_params[0]] = _params[1]
+ })
+ this.host = requiredSplit[0]
+ this.port = requiredSplit[1]
+ this.protocol = requiredSplit[2]
+ this.method = requiredSplit[3]
+ this.obfs = requiredSplit[4]
+ this.password = decode(requiredSplit[5])
+ if (otherSplit.obfsparam) {
+ this.obfsparam = decode(otherSplit.obfsparam)
+ }
+ if (otherSplit.remarks) {
+ this.remark = decode(otherSplit.remarks)
+ }
+ // this.udpport = otherSplit.udpport
+ // this.uot = otherSplit.uot
+ } catch (e) {
+ console.error(e)
+ }
+ }
+ }
+
+ getSSLink () {
+ const link = `${this.method}:${this.password}@${this.host}:${this.port}`
+ const encoded = encode(link)
+ if (this.remark) {
+ return 'ss://' + encoded + '#' + this.remark
+ }
+ return 'ss://' + encoded
+ }
+
+ setSSLink (link) {
+ if (link) {
+ try {
+ let body = link.substring(5)
+ const remark = body.split('#')
+ if (remark[1]) {
+ this.remark = remark[1]
+ }
+ body = remark[0]
+ const decoded = decode(body)
+ const split1 = decoded.split('@')
+ const split2 = split1[0].split(':')
+ const split3 = split1[1].split(':')
+ this.method = split2[0]
+ this.password = split2[1]
+ this.host = split3[0]
+ this.port = split3[1]
+ this.protocol = 'origin'
+ this.obfs = 'plain'
+ this.obfsparam = ''
+ } catch (e) {
+ console.error(e)
+ }
+ }
+ }
+}
diff --git a/app/src/Homepage.vue b/app/src/Homepage.vue
new file mode 100644
index 0000000..0f4249a
--- /dev/null
+++ b/app/src/Homepage.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
diff --git a/app/src/Initialzation.vue b/app/src/Initialzation.vue
new file mode 100644
index 0000000..85635f5
--- /dev/null
+++ b/app/src/Initialzation.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+ updateConfig('methods',v)">
+
+
+
+ updateConfig('protocols',v)">
+
+
+
+ updateConfig('obfses',v)">
+
+
+
+
+
+
+
+
diff --git a/app/src/assets/flex.styl b/app/src/assets/flex.styl
new file mode 100644
index 0000000..785b94f
--- /dev/null
+++ b/app/src/assets/flex.styl
@@ -0,0 +1,20 @@
+.flex
+ display flex
+.flex-inline
+ display inline-flex
+.flex-1
+ flex 1
+.flex-row
+ flex-direction row
+.flex-column
+ flex-direction column
+.flex-main-center
+ justify-content center
+.flex-main-start
+ justify-content flex-start
+.flex-main-end
+ justify-content flex-end
+.flex-cross-center
+ align-items center
+.flex-between
+ justify-content space-between
diff --git a/app/src/components/FormItem.vue b/app/src/components/FormItem.vue
new file mode 100644
index 0000000..58dcf6a
--- /dev/null
+++ b/app/src/components/FormItem.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/components/Tags.vue b/app/src/components/Tags.vue
new file mode 100644
index 0000000..97c0b37
--- /dev/null
+++ b/app/src/components/Tags.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
diff --git a/app/src/main.js b/app/src/main.js
new file mode 100644
index 0000000..cfa1b34
--- /dev/null
+++ b/app/src/main.js
@@ -0,0 +1,18 @@
+import Vue from 'vue'
+import Electron from 'vue-electron'
+import Raven from 'raven-js'
+import RavenVue from 'raven-js/plugins/vue'
+
+Vue.use(Electron)
+Vue.config.debug = true
+
+Raven.config('https://038cef9bc38b46cf9518937bddd1605c@sentry.io/182272')
+ .addPlugin(RavenVue, Vue)
+ .install()
+
+import App from './App'
+
+/* eslint-disable no-new */
+new Vue({
+ ...App
+}).$mount('#app')
diff --git a/app/src/qrcode/index.js b/app/src/qrcode/index.js
new file mode 100644
index 0000000..c58f875
--- /dev/null
+++ b/app/src/qrcode/index.js
@@ -0,0 +1,3266 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var MIN_SKIP = 3;
+var MAX_MODULES = 57;
+var INTEGER_MATH_SHIFT = 8;
+var CENTER_QUORUM = 2;
+
+
+var FinderPattern = function(posX, posY, estimatedModuleSize) {
+ this.x = posX;
+ this.y = posY;
+ this.count = 1;
+ this.estimatedModuleSize = estimatedModuleSize;
+
+ this.__defineGetter__("EstimatedModuleSize", function() {
+ return this.estimatedModuleSize;
+ });
+ this.__defineGetter__("Count", function() {
+ return this.count;
+ });
+ this.__defineGetter__("X", function() {
+ return this.x;
+ });
+ this.__defineGetter__("Y", function() {
+ return this.y;
+ });
+ this.incrementCount = function() {
+ this.count++;
+ };
+ this.aboutEquals = function(moduleSize, i, j) {
+ if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) {
+ var moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize);
+ return moduleSizeDiff <= 1.0 || moduleSizeDiff / this.estimatedModuleSize <= 1.0;
+ }
+ return false;
+ };
+
+};
+
+var FinderPatternInfo = function(patternCenters) {
+ this.bottomLeft = patternCenters[0];
+ this.topLeft = patternCenters[1];
+ this.topRight = patternCenters[2];
+ this.__defineGetter__("BottomLeft", function() {
+ return this.bottomLeft;
+ });
+ this.__defineGetter__("TopLeft", function() {
+ return this.topLeft;
+ });
+ this.__defineGetter__("TopRight", function() {
+ return this.topRight;
+ });
+};
+
+var FinderPatternFinder = function() {
+ this.image = null;
+ this.possibleCenters = [];
+ this.hasSkipped = false;
+ this.crossCheckStateCount = new Array(0, 0, 0, 0, 0);
+ this.resultPointCallback = null;
+
+ this.__defineGetter__("CrossCheckStateCount", function() {
+ this.crossCheckStateCount[0] = 0;
+ this.crossCheckStateCount[1] = 0;
+ this.crossCheckStateCount[2] = 0;
+ this.crossCheckStateCount[3] = 0;
+ this.crossCheckStateCount[4] = 0;
+ return this.crossCheckStateCount;
+ });
+
+ this.foundPatternCross = function(stateCount) {
+ var totalModuleSize = 0;
+ for (var i = 0; i < 5; i++) {
+ var count = stateCount[i];
+ if (count === 0) {
+ return false;
+ }
+ totalModuleSize += count;
+ }
+ if (totalModuleSize < 7) {
+ return false;
+ }
+ var moduleSize = Math.floor((totalModuleSize << INTEGER_MATH_SHIFT) / 7);
+ var maxVariance = Math.floor(moduleSize / 2);
+ // Allow less than 50% variance from 1-1-3-1-1 proportions
+ return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance && Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
+ };
+ this.centerFromEnd = function(stateCount, end) {
+ return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0;
+ };
+ this.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) {
+ var image = this.image;
+
+ var maxI = qrcode.height;
+ var stateCount = this.CrossCheckStateCount;
+
+ // Start counting up from center
+ var i = startI;
+ while (i >= 0 && image[centerJ + i * qrcode.width]) {
+ stateCount[2]++;
+ i--;
+ }
+ if (i < 0) {
+ return NaN;
+ }
+ while (i >= 0 && !image[centerJ + i * qrcode.width] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i--;
+ }
+ // If already too many modules in this state or ran off the edge:
+ if (i < 0 || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (i >= 0 && image[centerJ + i * qrcode.width] && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i--;
+ }
+ if (stateCount[0] > maxCount) {
+ return NaN;
+ }
+
+ // Now also count down from center
+ i = startI + 1;
+ while (i < maxI && image[centerJ + i * qrcode.width]) {
+ stateCount[2]++;
+ i++;
+ }
+ if (i == maxI) {
+ return NaN;
+ }
+ while (i < maxI && !image[centerJ + i * qrcode.width] && stateCount[3] < maxCount) {
+ stateCount[3]++;
+ i++;
+ }
+ if (i == maxI || stateCount[3] >= maxCount) {
+ return NaN;
+ }
+ while (i < maxI && image[centerJ + i * qrcode.width] && stateCount[4] < maxCount) {
+ stateCount[4]++;
+ i++;
+ }
+ if (stateCount[4] >= maxCount) {
+ return NaN;
+ }
+
+ // If we found a finder-pattern-like section, but its size is more than 40% different than
+ // the original, assume it's a false positive
+ var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+ return NaN;
+ }
+
+ return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN;
+ };
+ this.crossCheckHorizontal = function(startJ, centerI, maxCount, originalStateCountTotal) {
+ var image = this.image;
+
+ var maxJ = qrcode.width;
+ var stateCount = this.CrossCheckStateCount;
+
+ var j = startJ;
+ while (j >= 0 && image[j + centerI * qrcode.width]) {
+ stateCount[2]++;
+ j--;
+ }
+ if (j < 0) {
+ return NaN;
+ }
+ while (j >= 0 && !image[j + centerI * qrcode.width] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ j--;
+ }
+ if (j < 0 || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (j >= 0 && image[j + centerI * qrcode.width] && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ j--;
+ }
+ if (stateCount[0] > maxCount) {
+ return NaN;
+ }
+
+ j = startJ + 1;
+ while (j < maxJ && image[j + centerI * qrcode.width]) {
+ stateCount[2]++;
+ j++;
+ }
+ if (j == maxJ) {
+ return NaN;
+ }
+ while (j < maxJ && !image[j + centerI * qrcode.width] && stateCount[3] < maxCount) {
+ stateCount[3]++;
+ j++;
+ }
+ if (j == maxJ || stateCount[3] >= maxCount) {
+ return NaN;
+ }
+ while (j < maxJ && image[j + centerI * qrcode.width] && stateCount[4] < maxCount) {
+ stateCount[4]++;
+ j++;
+ }
+ if (stateCount[4] >= maxCount) {
+ return NaN;
+ }
+
+ // If we found a finder-pattern-like section, but its size is significantly different than
+ // the original, assume it's a false positive
+ var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {
+ return NaN;
+ }
+
+ return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, j) : NaN;
+ };
+ this.handlePossibleCenter = function(stateCount, i, j) {
+ var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ var centerJ = this.centerFromEnd(stateCount, j); //float
+ var centerI = this.crossCheckVertical(i, Math.floor(centerJ), stateCount[2], stateCountTotal); //float
+ if (!isNaN(centerI)) {
+ // Re-cross check
+ centerJ = this.crossCheckHorizontal(Math.floor(centerJ), Math.floor(centerI), stateCount[2], stateCountTotal);
+ if (!isNaN(centerJ)) {
+ var estimatedModuleSize = stateCountTotal / 7.0;
+ var found = false;
+ var max = this.possibleCenters.length;
+ for (var index = 0; index < max; index++) {
+ var center = this.possibleCenters[index];
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ center.incrementCount();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ var point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
+ this.possibleCenters.push(point);
+ if (this.resultPointCallback !== null) {
+ this.resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ };
+
+ this.selectBestPatterns = function() {
+
+ var startSize = this.possibleCenters.length;
+ if (startSize < 3) {
+ // Couldn't find enough finder patterns
+ throw "Couldn't find enough finder patterns";
+ }
+
+ // Filter outlier possibilities whose module size is too different
+ if (startSize > 3) {
+ // But we can only afford to do so if we have at least 4 possibilities to choose from
+ var totalModuleSize = 0.0;
+ var square = 0.0;
+ for (var i = 0; i < startSize; i++) {
+ //totalModuleSize += this.possibleCenters[i].EstimatedModuleSize;
+ var centerValue = this.possibleCenters[i].EstimatedModuleSize;
+ totalModuleSize += centerValue;
+ square += (centerValue * centerValue);
+ }
+ var average = totalModuleSize / startSize;
+ this.possibleCenters.sort(function(center1, center2) {
+ var dA = Math.abs(center2.EstimatedModuleSize - average);
+ var dB = Math.abs(center1.EstimatedModuleSize - average);
+ if (dA < dB) {
+ return (-1);
+ } else if (dA == dB) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+
+ var stdDev = Math.sqrt(square / startSize - average * average);
+ var limit = Math.max(0.2 * average, stdDev);
+ for (var i = this.possibleCenters.length - 1; i >= 0; i--) {
+ var pattern = this.possibleCenters[i];
+ //if (Math.abs(pattern.EstimatedModuleSize - average) > 0.2 * average)
+ if (Math.abs(pattern.EstimatedModuleSize - average) > limit) {
+ this.possibleCenters.remove(i);
+ }
+ }
+ }
+
+ if (this.possibleCenters.length > 3) {
+ // Throw away all but those first size candidate points we found.
+ this.possibleCenters.sort(function(a, b) {
+ if (a.count > b.count) {
+ return -1;
+ }
+ if (a.count < b.count) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+
+ return new Array(this.possibleCenters[0], this.possibleCenters[1], this.possibleCenters[2]);
+ };
+
+ this.findRowSkip = function() {
+ var max = this.possibleCenters.length;
+ if (max <= 1) {
+ return 0;
+ }
+ var firstConfirmedCenter = null;
+ for (var i = 0; i < max; i++) {
+ var center = this.possibleCenters[i];
+ if (center.Count >= CENTER_QUORUM) {
+ if (firstConfirmedCenter === null) {
+ firstConfirmedCenter = center;
+ } else {
+ // We have two confirmed centers
+ // How far down can we skip before resuming looking for the next
+ // pattern? In the worst case, only the difference between the
+ // difference in the x / y coordinates of the two centers.
+ // This is the case where you find top left last.
+ this.hasSkipped = true;
+ return Math.floor((Math.abs(firstConfirmedCenter.X - center.X) - Math.abs(firstConfirmedCenter.Y - center.Y)) / 2);
+ }
+ }
+ }
+ return 0;
+ };
+
+ this.haveMultiplyConfirmedCenters = function() {
+ var confirmedCount = 0;
+ var totalModuleSize = 0.0;
+ var max = this.possibleCenters.length;
+ var pattern;
+ for (var i = 0; i < max; i++) {
+ pattern = this.possibleCenters[i];
+ if (pattern.Count >= CENTER_QUORUM) {
+ confirmedCount++;
+ totalModuleSize += pattern.EstimatedModuleSize;
+ }
+ }
+ if (confirmedCount < 3) {
+ return false;
+ }
+ // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
+ // and that we need to keep looking. We detect this by asking if the estimated module sizes
+ // vary too much. We arbitrarily say that when the total deviation from average exceeds
+ // 5% of the total module size estimates, it's too much.
+ var average = totalModuleSize / max;
+ var totalDeviation = 0.0;
+ for (var i = 0; i < max; i++) {
+ pattern = this.possibleCenters[i];
+ totalDeviation += Math.abs(pattern.EstimatedModuleSize - average);
+ }
+ return totalDeviation <= 0.05 * totalModuleSize;
+ };
+
+ this.findFinderPattern = function(image) {
+ var tryHarder = false;
+ this.image = image;
+ var maxI = qrcode.height;
+ var maxJ = qrcode.width;
+ var iSkip = Math.floor((3 * maxI) / (4 * MAX_MODULES));
+ if (iSkip < MIN_SKIP || tryHarder) {
+ iSkip = MIN_SKIP;
+ }
+
+ var done = false;
+ var stateCount = new Array(5);
+ for (var i = iSkip - 1; i < maxI && !done; i += iSkip) {
+ // Get a row of black/white values
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ var currentState = 0;
+ for (var j = 0; j < maxJ; j++) {
+ if (image[j + i * qrcode.width]) {
+ // Black pixel
+ if ((currentState & 1) == 1) {
+ // Counting white pixels
+ currentState++;
+ }
+ stateCount[currentState]++;
+ } else {
+ // White pixel
+ if ((currentState & 1) === 0) {
+ // Counting black pixels
+ if (currentState == 4) {
+ // A winner?
+ if (this.foundPatternCross(stateCount)) {
+ // Yes
+ var confirmed = this.handlePossibleCenter(stateCount, i, j);
+ if (confirmed) {
+ // Start examining every other line. Checking each line turned out to be too
+ // expensive and didn't improve performance.
+ iSkip = 2;
+ if (this.hasSkipped) {
+ done = this.haveMultiplyConfirmedCenters();
+ } else {
+ var rowSkip = this.findRowSkip();
+ if (rowSkip > stateCount[2]) {
+ // Skip rows between row of lower confirmed center
+ // and top of presumed third confirmed center
+ // but back up a bit to get a full chance of detecting
+ // it, entire width of center of finder pattern
+
+ // Skip by rowSkip, but back off by stateCount[2] (size of last center
+ // of pattern we saw) to be conservative, and also back off by iSkip which
+ // is about to be re-added
+ i += rowSkip - stateCount[2] - iSkip;
+ j = maxJ - 1;
+ }
+ }
+ } else {
+ // Advance to next black pixel
+ do {
+ j++;
+ }
+ while (j < maxJ && !image[j + i * qrcode.width]);
+ j--; // back up to that last white pixel
+ }
+ // Clear state to start looking again
+ currentState = 0;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ } else {
+ // No, shift counts back by two
+ stateCount[0] = stateCount[2];
+ stateCount[1] = stateCount[3];
+ stateCount[2] = stateCount[4];
+ stateCount[3] = 1;
+ stateCount[4] = 0;
+ currentState = 3;
+ }
+ } else {
+ stateCount[++currentState]++;
+ }
+ } else {
+ // Counting white pixels
+ stateCount[currentState]++;
+ }
+ }
+ }
+ if (this.foundPatternCross(stateCount)) {
+ var confirmed = this.handlePossibleCenter(stateCount, i, maxJ);
+ if (confirmed) {
+ iSkip = stateCount[0];
+ if (this.hasSkipped) {
+ // Found a third one
+ done = haveMultiplyConfirmedCenters();
+ }
+ }
+ }
+ }
+
+ var patternInfo = this.selectBestPatterns();
+ qrcode.orderBestPatterns(patternInfo);
+
+ return new FinderPatternInfo(patternInfo);
+ };
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var BitMatrix = function(width, height) {
+ if (!height)
+ height = width;
+ if (width < 1 || height < 1) {
+ throw "Both dimensions must be greater than 0";
+ }
+ this.width = width;
+ this.height = height;
+ var rowSize = width >> 5;
+ if ((width & 0x1f) !== 0) {
+ rowSize++;
+ }
+ this.rowSize = rowSize;
+ this.bits = new Array(rowSize * height);
+ for (var i = 0; i < this.bits.length; i++)
+ this.bits[i] = 0;
+
+ this.__defineGetter__("Width", function() {
+ return this.width;
+ });
+ this.__defineGetter__("Height", function() {
+ return this.height;
+ });
+ this.__defineGetter__("Dimension", function() {
+ if (this.width != this.height) {
+ throw "Can't call getDimension() on a non-square matrix";
+ }
+ return this.width;
+ });
+
+ this.get_Renamed = function(x, y) {
+ var offset = y * this.rowSize + (x >> 5);
+ return ((URShift(this.bits[offset], (x & 0x1f))) & 1) !== 0;
+ };
+ this.set_Renamed = function(x, y) {
+ var offset = y * this.rowSize + (x >> 5);
+ this.bits[offset] |= 1 << (x & 0x1f);
+ };
+ this.flip = function(x, y) {
+ var offset = y * this.rowSize + (x >> 5);
+ this.bits[offset] ^= 1 << (x & 0x1f);
+ };
+ this.clear = function() {
+ var max = this.bits.length;
+ for (var i = 0; i < max; i++) {
+ this.bits[i] = 0;
+ }
+ };
+ this.setRegion = function(left, top, width, height) {
+ if (top < 0 || left < 0) {
+ throw "Left and top must be nonnegative";
+ }
+ if (height < 1 || width < 1) {
+ throw "Height and width must be at least 1";
+ }
+ var right = left + width;
+ var bottom = top + height;
+ if (bottom > this.height || right > this.width) {
+ throw "The region must fit inside the matrix";
+ }
+ for (var y = top; y < bottom; y++) {
+ var offset = y * this.rowSize;
+ for (var x = left; x < right; x++) {
+ this.bits[offset + (x >> 5)] |= 1 << (x & 0x1f);
+ }
+ }
+ };
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ECB = function(count, dataCodewords) {
+ this.count = count;
+ this.dataCodewords = dataCodewords;
+
+ this.__defineGetter__("Count", function() {
+ return this.count;
+ });
+ this.__defineGetter__("DataCodewords", function() {
+ return this.dataCodewords;
+ });
+};
+
+var ECBlocks = function(ecCodewordsPerBlock, ecBlocks1, ecBlocks2) {
+ this.ecCodewordsPerBlock = ecCodewordsPerBlock;
+ if (ecBlocks2)
+ this.ecBlocks = new Array(ecBlocks1, ecBlocks2);
+ else
+ this.ecBlocks = new Array(ecBlocks1);
+
+ this.__defineGetter__("ECCodewordsPerBlock", function() {
+ return this.ecCodewordsPerBlock;
+ });
+
+ this.__defineGetter__("TotalECCodewords", function() {
+ return this.ecCodewordsPerBlock * this.NumBlocks;
+ });
+
+ this.__defineGetter__("NumBlocks", function() {
+ var total = 0;
+ for (var i = 0; i < this.ecBlocks.length; i++) {
+ total += this.ecBlocks[i].length;
+ }
+ return total;
+ });
+
+ this.getECBlocks = function() {
+ return this.ecBlocks;
+ };
+};
+
+var Version = function(versionNumber, alignmentPatternCenters, ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4) {
+ this.versionNumber = versionNumber;
+ this.alignmentPatternCenters = alignmentPatternCenters;
+ this.ecBlocks = new Array(ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4);
+
+ var total = 0;
+ var ecCodewords = ecBlocks1.ECCodewordsPerBlock;
+ var ecbArray = ecBlocks1.getECBlocks();
+ for (var i = 0; i < ecbArray.length; i++) {
+ var ecBlock = ecbArray[i];
+ total += ecBlock.Count * (ecBlock.DataCodewords + ecCodewords);
+ }
+ this.totalCodewords = total;
+
+ this.__defineGetter__("VersionNumber", function() {
+ return this.versionNumber;
+ });
+
+ this.__defineGetter__("AlignmentPatternCenters", function() {
+ return this.alignmentPatternCenters;
+ });
+ this.__defineGetter__("TotalCodewords", function() {
+ return this.totalCodewords;
+ });
+ this.__defineGetter__("DimensionForVersion", function() {
+ return 17 + 4 * this.versionNumber;
+ });
+
+ this.buildFunctionPattern = function() {
+ var dimension = this.DimensionForVersion;
+ var bitMatrix = new BitMatrix(dimension);
+
+ // Top left finder pattern + separator + format
+ bitMatrix.setRegion(0, 0, 9, 9);
+ // Top right finder pattern + separator + format
+ bitMatrix.setRegion(dimension - 8, 0, 8, 9);
+ // Bottom left finder pattern + separator + format
+ bitMatrix.setRegion(0, dimension - 8, 9, 8);
+
+ // Alignment patterns
+ var max = this.alignmentPatternCenters.length;
+ for (var x = 0; x < max; x++) {
+ var i = this.alignmentPatternCenters[x] - 2;
+ for (var y = 0; y < max; y++) {
+ if ((x === 0 && (y === 0 || y === max - 1)) || (x === max - 1 && y === 0)) {
+ // No alignment patterns near the three finder paterns
+ continue;
+ }
+ bitMatrix.setRegion(this.alignmentPatternCenters[y] - 2, i, 5, 5);
+ }
+ }
+
+ // Vertical timing pattern
+ bitMatrix.setRegion(6, 9, 1, dimension - 17);
+ // Horizontal timing pattern
+ bitMatrix.setRegion(9, 6, dimension - 17, 1);
+
+ if (this.versionNumber > 6) {
+ // Version info, top right
+ bitMatrix.setRegion(dimension - 11, 0, 3, 6);
+ // Version info, bottom left
+ bitMatrix.setRegion(0, dimension - 11, 6, 3);
+ }
+
+ return bitMatrix;
+ };
+ this.getECBlocksForLevel = function(ecLevel) {
+ return this.ecBlocks[ecLevel.ordinal()];
+ };
+};
+
+var buildVersions = function() {
+ return new Array(new Version(1, new Array(), new ECBlocks(7, new ECB(1, 19)), new ECBlocks(10, new ECB(1, 16)), new ECBlocks(13, new ECB(1, 13)), new ECBlocks(17, new ECB(1, 9))),
+ new Version(2, new Array(6, 18), new ECBlocks(10, new ECB(1, 34)), new ECBlocks(16, new ECB(1, 28)), new ECBlocks(22, new ECB(1, 22)), new ECBlocks(28, new ECB(1, 16))),
+ new Version(3, new Array(6, 22), new ECBlocks(15, new ECB(1, 55)), new ECBlocks(26, new ECB(1, 44)), new ECBlocks(18, new ECB(2, 17)), new ECBlocks(22, new ECB(2, 13))),
+ new Version(4, new Array(6, 26), new ECBlocks(20, new ECB(1, 80)), new ECBlocks(18, new ECB(2, 32)), new ECBlocks(26, new ECB(2, 24)), new ECBlocks(16, new ECB(4, 9))),
+ new Version(5, new Array(6, 30), new ECBlocks(26, new ECB(1, 108)), new ECBlocks(24, new ECB(2, 43)), new ECBlocks(18, new ECB(2, 15), new ECB(2, 16)), new ECBlocks(22, new ECB(2, 11), new ECB(2, 12))),
+ new Version(6, new Array(6, 34), new ECBlocks(18, new ECB(2, 68)), new ECBlocks(16, new ECB(4, 27)), new ECBlocks(24, new ECB(4, 19)), new ECBlocks(28, new ECB(4, 15))),
+ new Version(7, new Array(6, 22, 38), new ECBlocks(20, new ECB(2, 78)), new ECBlocks(18, new ECB(4, 31)), new ECBlocks(18, new ECB(2, 14), new ECB(4, 15)), new ECBlocks(26, new ECB(4, 13), new ECB(1, 14))),
+ new Version(8, new Array(6, 24, 42), new ECBlocks(24, new ECB(2, 97)), new ECBlocks(22, new ECB(2, 38), new ECB(2, 39)), new ECBlocks(22, new ECB(4, 18), new ECB(2, 19)), new ECBlocks(26, new ECB(4, 14), new ECB(2, 15))),
+ new Version(9, new Array(6, 26, 46), new ECBlocks(30, new ECB(2, 116)), new ECBlocks(22, new ECB(3, 36), new ECB(2, 37)), new ECBlocks(20, new ECB(4, 16), new ECB(4, 17)), new ECBlocks(24, new ECB(4, 12), new ECB(4, 13))),
+ new Version(10, new Array(6, 28, 50), new ECBlocks(18, new ECB(2, 68), new ECB(2, 69)), new ECBlocks(26, new ECB(4, 43), new ECB(1, 44)), new ECBlocks(24, new ECB(6, 19), new ECB(2, 20)), new ECBlocks(28, new ECB(6, 15), new ECB(2, 16))),
+ new Version(11, new Array(6, 30, 54), new ECBlocks(20, new ECB(4, 81)), new ECBlocks(30, new ECB(1, 50), new ECB(4, 51)), new ECBlocks(28, new ECB(4, 22), new ECB(4, 23)), new ECBlocks(24, new ECB(3, 12), new ECB(8, 13))),
+ new Version(12, new Array(6, 32, 58), new ECBlocks(24, new ECB(2, 92), new ECB(2, 93)), new ECBlocks(22, new ECB(6, 36), new ECB(2, 37)), new ECBlocks(26, new ECB(4, 20), new ECB(6, 21)), new ECBlocks(28, new ECB(7, 14), new ECB(4, 15))),
+ new Version(13, new Array(6, 34, 62), new ECBlocks(26, new ECB(4, 107)), new ECBlocks(22, new ECB(8, 37), new ECB(1, 38)), new ECBlocks(24, new ECB(8, 20), new ECB(4, 21)), new ECBlocks(22, new ECB(12, 11), new ECB(4, 12))),
+ new Version(14, new Array(6, 26, 46, 66), new ECBlocks(30, new ECB(3, 115), new ECB(1, 116)), new ECBlocks(24, new ECB(4, 40), new ECB(5, 41)), new ECBlocks(20, new ECB(11, 16), new ECB(5, 17)), new ECBlocks(24, new ECB(11, 12), new ECB(5, 13))),
+ new Version(15, new Array(6, 26, 48, 70), new ECBlocks(22, new ECB(5, 87), new ECB(1, 88)), new ECBlocks(24, new ECB(5, 41), new ECB(5, 42)), new ECBlocks(30, new ECB(5, 24), new ECB(7, 25)), new ECBlocks(24, new ECB(11, 12), new ECB(7, 13))),
+ new Version(16, new Array(6, 26, 50, 74), new ECBlocks(24, new ECB(5, 98), new ECB(1, 99)), new ECBlocks(28, new ECB(7, 45), new ECB(3, 46)), new ECBlocks(24, new ECB(15, 19), new ECB(2, 20)), new ECBlocks(30, new ECB(3, 15), new ECB(13, 16))),
+ new Version(17, new Array(6, 30, 54, 78), new ECBlocks(28, new ECB(1, 107), new ECB(5, 108)), new ECBlocks(28, new ECB(10, 46), new ECB(1, 47)), new ECBlocks(28, new ECB(1, 22), new ECB(15, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(17, 15))),
+ new Version(18, new Array(6, 30, 56, 82), new ECBlocks(30, new ECB(5, 120), new ECB(1, 121)), new ECBlocks(26, new ECB(9, 43), new ECB(4, 44)), new ECBlocks(28, new ECB(17, 22), new ECB(1, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(19, 15))),
+ new Version(19, new Array(6, 30, 58, 86), new ECBlocks(28, new ECB(3, 113), new ECB(4, 114)), new ECBlocks(26, new ECB(3, 44), new ECB(11, 45)), new ECBlocks(26, new ECB(17, 21), new ECB(4, 22)), new ECBlocks(26, new ECB(9, 13), new ECB(16, 14))),
+ new Version(20, new Array(6, 34, 62, 90), new ECBlocks(28, new ECB(3, 107), new ECB(5, 108)), new ECBlocks(26, new ECB(3, 41), new ECB(13, 42)), new ECBlocks(30, new ECB(15, 24), new ECB(5, 25)), new ECBlocks(28, new ECB(15, 15), new ECB(10, 16))),
+ new Version(21, new Array(6, 28, 50, 72, 94), new ECBlocks(28, new ECB(4, 116), new ECB(4, 117)), new ECBlocks(26, new ECB(17, 42)), new ECBlocks(28, new ECB(17, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(19, 16), new ECB(6, 17))),
+ new Version(22, new Array(6, 26, 50, 74, 98), new ECBlocks(28, new ECB(2, 111), new ECB(7, 112)), new ECBlocks(28, new ECB(17, 46)), new ECBlocks(30, new ECB(7, 24), new ECB(16, 25)), new ECBlocks(24, new ECB(34, 13))),
+ new Version(23, new Array(6, 30, 54, 74, 102), new ECBlocks(30, new ECB(4, 121), new ECB(5, 122)), new ECBlocks(28, new ECB(4, 47), new ECB(14, 48)), new ECBlocks(30, new ECB(11, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(16, 15), new ECB(14, 16))),
+ new Version(24, new Array(6, 28, 54, 80, 106), new ECBlocks(30, new ECB(6, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(6, 45), new ECB(14, 46)), new ECBlocks(30, new ECB(11, 24), new ECB(16, 25)), new ECBlocks(30, new ECB(30, 16), new ECB(2, 17))),
+ new Version(25, new Array(6, 32, 58, 84, 110), new ECBlocks(26, new ECB(8, 106), new ECB(4, 107)), new ECBlocks(28, new ECB(8, 47), new ECB(13, 48)), new ECBlocks(30, new ECB(7, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(13, 16))),
+ new Version(26, new Array(6, 30, 58, 86, 114), new ECBlocks(28, new ECB(10, 114), new ECB(2, 115)), new ECBlocks(28, new ECB(19, 46), new ECB(4, 47)), new ECBlocks(28, new ECB(28, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(33, 16), new ECB(4, 17))),
+ new Version(27, new Array(6, 34, 62, 90, 118), new ECBlocks(30, new ECB(8, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(22, 45), new ECB(3, 46)), new ECBlocks(30, new ECB(8, 23), new ECB(26, 24)), new ECBlocks(30, new ECB(12, 15), new ECB(28, 16))),
+ new Version(28, new Array(6, 26, 50, 74, 98, 122), new ECBlocks(30, new ECB(3, 117), new ECB(10, 118)), new ECBlocks(28, new ECB(3, 45), new ECB(23, 46)), new ECBlocks(30, new ECB(4, 24), new ECB(31, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(31, 16))),
+ new Version(29, new Array(6, 30, 54, 78, 102, 126), new ECBlocks(30, new ECB(7, 116), new ECB(7, 117)), new ECBlocks(28, new ECB(21, 45), new ECB(7, 46)), new ECBlocks(30, new ECB(1, 23), new ECB(37, 24)), new ECBlocks(30, new ECB(19, 15), new ECB(26, 16))),
+ new Version(30, new Array(6, 26, 52, 78, 104, 130), new ECBlocks(30, new ECB(5, 115), new ECB(10, 116)), new ECBlocks(28, new ECB(19, 47), new ECB(10, 48)), new ECBlocks(30, new ECB(15, 24), new ECB(25, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(25, 16))),
+ new Version(31, new Array(6, 30, 56, 82, 108, 134), new ECBlocks(30, new ECB(13, 115), new ECB(3, 116)), new ECBlocks(28, new ECB(2, 46), new ECB(29, 47)), new ECBlocks(30, new ECB(42, 24), new ECB(1, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(28, 16))),
+ new Version(32, new Array(6, 34, 60, 86, 112, 138), new ECBlocks(30, new ECB(17, 115)), new ECBlocks(28, new ECB(10, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(10, 24), new ECB(35, 25)), new ECBlocks(30, new ECB(19, 15), new ECB(35, 16))),
+ new Version(33, new Array(6, 30, 58, 86, 114, 142), new ECBlocks(30, new ECB(17, 115), new ECB(1, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(21, 47)), new ECBlocks(30, new ECB(29, 24), new ECB(19, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(46, 16))),
+ new Version(34, new Array(6, 34, 62, 90, 118, 146), new ECBlocks(30, new ECB(13, 115), new ECB(6, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(44, 24), new ECB(7, 25)), new ECBlocks(30, new ECB(59, 16), new ECB(1, 17))),
+ new Version(35, new Array(6, 30, 54, 78, 102, 126, 150), new ECBlocks(30, new ECB(12, 121), new ECB(7, 122)), new ECBlocks(28, new ECB(12, 47), new ECB(26, 48)), new ECBlocks(30, new ECB(39, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(41, 16))),
+ new Version(36, new Array(6, 24, 50, 76, 102, 128, 154), new ECBlocks(30, new ECB(6, 121), new ECB(14, 122)), new ECBlocks(28, new ECB(6, 47), new ECB(34, 48)), new ECBlocks(30, new ECB(46, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(2, 15), new ECB(64, 16))),
+ new Version(37, new Array(6, 28, 54, 80, 106, 132, 158), new ECBlocks(30, new ECB(17, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(29, 46), new ECB(14, 47)), new ECBlocks(30, new ECB(49, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(24, 15), new ECB(46, 16))),
+ new Version(38, new Array(6, 32, 58, 84, 110, 136, 162), new ECBlocks(30, new ECB(4, 122), new ECB(18, 123)), new ECBlocks(28, new ECB(13, 46), new ECB(32, 47)), new ECBlocks(30, new ECB(48, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(42, 15), new ECB(32, 16))),
+ new Version(39, new Array(6, 26, 54, 82, 110, 138, 166), new ECBlocks(30, new ECB(20, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(40, 47), new ECB(7, 48)), new ECBlocks(30, new ECB(43, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(10, 15), new ECB(67, 16))),
+ new Version(40, new Array(6, 30, 58, 86, 114, 142, 170), new ECBlocks(30, new ECB(19, 118), new ECB(6, 119)), new ECBlocks(28, new ECB(18, 47), new ECB(31, 48)), new ECBlocks(30, new ECB(34, 24), new ECB(34, 25)), new ECBlocks(30, new ECB(20, 15), new ECB(61, 16))));
+};
+
+
+Version.VERSION_DECODE_INFO = new Array(0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, 0x2542E, 0x26A64, 0x27541, 0x28C69);
+
+Version.VERSIONS = buildVersions();
+
+Version.getVersionForNumber = function(versionNumber) {
+ if (versionNumber < 1 || versionNumber > 40) {
+ throw "ArgumentException";
+ }
+ return Version.VERSIONS[versionNumber - 1];
+};
+
+Version.getProvisionalVersionForDimension = function(dimension) {
+ if (dimension % 4 != 1) {
+ throw "Error getProvisionalVersionForDimension";
+ }
+ try {
+ return Version.getVersionForNumber((dimension - 17) >> 2);
+ } catch (iae) {
+ throw "Error getVersionForNumber";
+ }
+};
+
+Version.decodeVersionInformation = function(versionBits) {
+ var bestDifference = 0xffffffff;
+ var bestVersion = 0;
+ for (var i = 0; i < Version.VERSION_DECODE_INFO.length; i++) {
+ var targetVersion = Version.VERSION_DECODE_INFO[i];
+ // Do the version info bits match exactly? done.
+ if (targetVersion == versionBits) {
+ return this.getVersionForNumber(i + 7);
+ }
+ // Otherwise see if this is the closest to a real version info bit string
+ // we have seen so far
+ var bitsDifference = FormatInformation$1.numBitsDiffering(versionBits, targetVersion);
+ if (bitsDifference < bestDifference) {
+ bestVersion = i + 7;
+ bestDifference = bitsDifference;
+ }
+ }
+ // We can tolerate up to 3 bits of error since no two version info codewords will
+ // differ in less than 4 bits.
+ if (bestDifference <= 3) {
+ return this.getVersionForNumber(bestVersion);
+ }
+ // If we didn't find a close enough match, fail
+ return null;
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var AlignmentPattern = function(posX, posY, estimatedModuleSize) {
+ this.x = posX;
+ this.y = posY;
+ this.count = 1;
+ this.estimatedModuleSize = estimatedModuleSize;
+
+ this.__defineGetter__("EstimatedModuleSize", function() {
+ return this.estimatedModuleSize;
+ });
+ this.__defineGetter__("Count", function() {
+ return this.count;
+ });
+ this.__defineGetter__("X", function() {
+ return Math.floor(this.x);
+ });
+ this.__defineGetter__("Y", function() {
+ return Math.floor(this.y);
+ });
+ this.incrementCount = function() {
+ this.count++;
+ };
+ this.aboutEquals = function(moduleSize, i, j) {
+ if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) {
+ var moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize);
+ return moduleSizeDiff <= 1.0 || moduleSizeDiff / this.estimatedModuleSize <= 1.0;
+ }
+ return false;
+ };
+
+};
+
+var AlignmentPatternFinder = function(image, startX, startY, width, height, moduleSize, resultPointCallback) {
+ this.image = image;
+ this.possibleCenters = new Array();
+ this.startX = startX;
+ this.startY = startY;
+ this.width = width;
+ this.height = height;
+ this.moduleSize = moduleSize;
+ this.crossCheckStateCount = new Array(0, 0, 0);
+ this.resultPointCallback = resultPointCallback;
+
+ this.centerFromEnd = function(stateCount, end) {
+ return (end - stateCount[2]) - stateCount[1] / 2.0;
+ };
+ this.foundPatternCross = function(stateCount) {
+ var moduleSize = this.moduleSize;
+ var maxVariance = moduleSize / 2.0;
+ for (var i = 0; i < 3; i++) {
+ if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ this.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) {
+ var image = this.image;
+
+ var maxI = qrcode.height;
+ var stateCount = this.crossCheckStateCount;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+
+ // Start counting up from center
+ var i = startI;
+ while (i >= 0 && image[centerJ + i * qrcode.width] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i--;
+ }
+ // If already too many modules in this state or ran off the edge:
+ if (i < 0 || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (i >= 0 && !image[centerJ + i * qrcode.width] && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i--;
+ }
+ if (stateCount[0] > maxCount) {
+ return NaN;
+ }
+
+ // Now also count down from center
+ i = startI + 1;
+ while (i < maxI && image[centerJ + i * qrcode.width] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i++;
+ }
+ if (i == maxI || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (i < maxI && !image[centerJ + i * qrcode.width] && stateCount[2] <= maxCount) {
+ stateCount[2]++;
+ i++;
+ }
+ if (stateCount[2] > maxCount) {
+ return NaN;
+ }
+
+ var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+ return NaN;
+ }
+
+ return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN;
+ };
+
+ this.handlePossibleCenter = function(stateCount, i, j) {
+ var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+ var centerJ = this.centerFromEnd(stateCount, j);
+ var centerI = this.crossCheckVertical(i, Math.floor(centerJ), 2 * stateCount[1], stateCountTotal);
+ if (!isNaN(centerI)) {
+ var estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0;
+ var max = this.possibleCenters.length;
+ for (var index = 0; index < max; index++) {
+ var center = this.possibleCenters[index];
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ return new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ }
+ }
+ // Hadn't found this before; save it
+ var point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ this.possibleCenters.push(point);
+ if (this.resultPointCallback !== null) {
+ this.resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return null;
+ };
+
+ this.find = function() {
+ var startX = this.startX;
+ var height = this.height;
+ var maxJ = startX + width;
+ var middleI = startY + (height >> 1);
+ // We are looking for black/white/black modules in 1:1:1 ratio;
+ // this tracks the number of black/white/black modules seen so far
+ var stateCount = new Array(0, 0, 0);
+ for (var iGen = 0; iGen < height; iGen++) {
+ // Search from middle outwards
+ var i = middleI + ((iGen & 0x01) === 0 ? ((iGen + 1) >> 1) : -((iGen + 1) >> 1));
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ var j = startX;
+ // Burn off leading white pixels before anything else; if we start in the middle of
+ // a white run, it doesn't make sense to count its length, since we don't know if the
+ // white run continued to the left of the start point
+ while (j < maxJ && !image[j + qrcode.width * i]) {
+ j++;
+ }
+ var currentState = 0;
+ while (j < maxJ) {
+ if (image[j + i * qrcode.width]) {
+ // Black pixel
+ if (currentState == 1) {
+ // Counting black pixels
+ stateCount[currentState]++;
+ } else {
+ // Counting white pixels
+ if (currentState == 2) {
+ // A winner?
+ if (this.foundPatternCross(stateCount)) {
+ // Yes
+ var confirmed = this.handlePossibleCenter(stateCount, i, j);
+ if (confirmed !== null) {
+ return confirmed;
+ }
+ }
+ stateCount[0] = stateCount[2];
+ stateCount[1] = 1;
+ stateCount[2] = 0;
+ currentState = 1;
+ } else {
+ stateCount[++currentState]++;
+ }
+ }
+ } else {
+ // White pixel
+ if (currentState == 1) {
+ // Counting black pixels
+ currentState++;
+ }
+ stateCount[currentState]++;
+ }
+ j++;
+ }
+ if (this.foundPatternCross(stateCount)) {
+ var confirmed = this.handlePossibleCenter(stateCount, i, maxJ);
+ if (confirmed !== null) {
+ return confirmed;
+ }
+ }
+ }
+
+ // Hmm, nothing we saw was observed and confirmed twice. If we had
+ // any guess at all, return it.
+ if (!(this.possibleCenters.length === 0)) {
+ return this.possibleCenters[0];
+ }
+
+ throw "Couldn't find enough alignment patterns";
+ };
+
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var GridSampler = {};
+
+GridSampler.checkAndNudgePoints = function(image, points) {
+ var width = qrcode.width;
+ var height = qrcode.height;
+ // Check and nudge points from start until we see some that are OK:
+ var nudged = true;
+ for (var offset = 0; offset < points.length && nudged; offset += 2) {
+ var x = Math.floor(points[offset]);
+ var y = Math.floor(points[offset + 1]);
+ if (x < -1 || x > width || y < -1 || y > height) {
+ throw "Error.checkAndNudgePoints ";
+ }
+ nudged = false;
+ if (x == -1) {
+ points[offset] = 0.0;
+ nudged = true;
+ } else if (x == width) {
+ points[offset] = width - 1;
+ nudged = true;
+ }
+ if (y == -1) {
+ points[offset + 1] = 0.0;
+ nudged = true;
+ } else if (y == height) {
+ points[offset + 1] = height - 1;
+ nudged = true;
+ }
+ }
+ // Check and nudge points from end:
+ nudged = true;
+ for (var offset = points.length - 2; offset >= 0 && nudged; offset -= 2) {
+ var x = Math.floor(points[offset]);
+ var y = Math.floor(points[offset + 1]);
+ if (x < -1 || x > width || y < -1 || y > height) {
+ throw "Error.checkAndNudgePoints ";
+ }
+ nudged = false;
+ if (x == -1) {
+ points[offset] = 0.0;
+ nudged = true;
+ } else if (x == width) {
+ points[offset] = width - 1;
+ nudged = true;
+ }
+ if (y == -1) {
+ points[offset + 1] = 0.0;
+ nudged = true;
+ } else if (y == height) {
+ points[offset + 1] = height - 1;
+ nudged = true;
+ }
+ }
+};
+
+GridSampler.sampleGrid3 = function(image, dimension, transform) {
+ var bits = new BitMatrix(dimension);
+ var points = new Array(dimension << 1);
+ for (var y = 0; y < dimension; y++) {
+ var max = points.length;
+ var iValue = y + 0.5;
+ for (var x = 0; x < max; x += 2) {
+ points[x] = (x >> 1) + 0.5;
+ points[x + 1] = iValue;
+ }
+ transform.transformPoints1(points);
+ // Quick check to see if points transformed to something inside the image;
+ // sufficient to check the endpoints
+ GridSampler.checkAndNudgePoints(image, points);
+ try {
+ for (var x = 0; x < max; x += 2) {
+ var xpoint = (Math.floor(points[x]) * 4) + (Math.floor(points[x + 1]) * qrcode.width * 4);
+ var bit = image[Math.floor(points[x]) + qrcode.width * Math.floor(points[x + 1])];
+ qrcode.imagedata.data[xpoint] = bit ? 255 : 0;
+ qrcode.imagedata.data[xpoint + 1] = bit ? 255 : 0;
+ qrcode.imagedata.data[xpoint + 2] = 0;
+ qrcode.imagedata.data[xpoint + 3] = 255;
+ //bits[x >> 1][ y]=bit;
+ if (bit)
+ bits.set_Renamed(x >> 1, y);
+ }
+ } catch (aioobe) {
+ // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
+ // transform gets "twisted" such that it maps a straight line of points to a set of points
+ // whose endpoints are in bounds, but others are not. There is probably some mathematical
+ // way to detect this about the transformation that I don't know yet.
+ // This results in an ugly runtime exception despite our clever checks above -- can't have
+ // that. We could check each point's coordinates but that feels duplicative. We settle for
+ // catching and wrapping ArrayIndexOutOfBoundsException.
+ throw "Error.checkAndNudgePoints";
+ }
+ }
+ return bits;
+};
+
+GridSampler.sampleGridx = function(image, dimension, p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY) {
+ var transform = PerspectiveTransform.quadrilateralToQuadrilateral(p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
+
+ return GridSampler.sampleGrid3(image, dimension, transform);
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var PerspectiveTransform$1 = function(a11, a21, a31, a12, a22, a32, a13, a23, a33) {
+ this.a11 = a11;
+ this.a12 = a12;
+ this.a13 = a13;
+ this.a21 = a21;
+ this.a22 = a22;
+ this.a23 = a23;
+ this.a31 = a31;
+ this.a32 = a32;
+ this.a33 = a33;
+ this.transformPoints1 = function(points) {
+ var max = points.length;
+ var a11 = this.a11;
+ var a12 = this.a12;
+ var a13 = this.a13;
+ var a21 = this.a21;
+ var a22 = this.a22;
+ var a23 = this.a23;
+ var a31 = this.a31;
+ var a32 = this.a32;
+ var a33 = this.a33;
+ for (var i = 0; i < max; i += 2) {
+ var x = points[i];
+ var y = points[i + 1];
+ var denominator = a13 * x + a23 * y + a33;
+ points[i] = (a11 * x + a21 * y + a31) / denominator;
+ points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
+ }
+ };
+ this.transformPoints2 = function(xValues, yValues) {
+ var n = xValues.length;
+ for (var i = 0; i < n; i++) {
+ var x = xValues[i];
+ var y = yValues[i];
+ var denominator = this.a13 * x + this.a23 * y + this.a33;
+ xValues[i] = (this.a11 * x + this.a21 * y + this.a31) / denominator;
+ yValues[i] = (this.a12 * x + this.a22 * y + this.a32) / denominator;
+ }
+ };
+
+ this.buildAdjoint = function() {
+ // Adjoint is the transpose of the cofactor matrix:
+ return new PerspectiveTransform$1(this.a22 * this.a33 - this.a23 * this.a32, this.a23 * this.a31 - this.a21 * this.a33, this.a21 * this.a32 - this.a22 * this.a31, this.a13 * this.a32 - this.a12 * this.a33, this.a11 * this.a33 - this.a13 * this.a31, this.a12 * this.a31 - this.a11 * this.a32, this.a12 * this.a23 - this.a13 * this.a22, this.a13 * this.a21 - this.a11 * this.a23, this.a11 * this.a22 - this.a12 * this.a21);
+ };
+ this.times = function(other) {
+ return new PerspectiveTransform$1(this.a11 * other.a11 + this.a21 * other.a12 + this.a31 * other.a13, this.a11 * other.a21 + this.a21 * other.a22 + this.a31 * other.a23, this.a11 * other.a31 + this.a21 * other.a32 + this.a31 * other.a33, this.a12 * other.a11 + this.a22 * other.a12 + this.a32 * other.a13, this.a12 * other.a21 + this.a22 * other.a22 + this.a32 * other.a23, this.a12 * other.a31 + this.a22 * other.a32 + this.a32 * other.a33, this.a13 * other.a11 + this.a23 * other.a12 + this.a33 * other.a13, this.a13 * other.a21 + this.a23 * other.a22 + this.a33 * other.a23, this.a13 * other.a31 + this.a23 * other.a32 + this.a33 * other.a33);
+ };
+
+};
+
+PerspectiveTransform$1.quadrilateralToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p) {
+
+ var qToS = this.quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
+ var sToQ = this.squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
+ return sToQ.times(qToS);
+};
+
+PerspectiveTransform$1.squareToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3) {
+ var dy2 = y3 - y2;
+ var dy3 = y0 - y1 + y2 - y3;
+ if (dy2 === 0.0 && dy3 === 0.0) {
+ return new PerspectiveTransform$1(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0, 0.0, 1.0);
+ } else {
+ var dx1 = x1 - x2;
+ var dx2 = x3 - x2;
+ var dx3 = x0 - x1 + x2 - x3;
+ var dy1 = y1 - y2;
+ var denominator = dx1 * dy2 - dx2 * dy1;
+ var a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
+ var a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
+ return new PerspectiveTransform$1(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1.0);
+ }
+};
+
+PerspectiveTransform$1.quadrilateralToSquare = function(x0, y0, x1, y1, x2, y2, x3, y3) {
+ // Here, the adjoint serves as the inverse:
+ return this.squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
+};
+
+var DetectorResult = function(bits, points) {
+ this.bits = bits;
+ this.points = points;
+};
+
+var Detector = function(image) {
+ this.image = image;
+ this.resultPointCallback = null;
+
+ this.sizeOfBlackWhiteBlackRun = function(fromX, fromY, toX, toY) {
+ // Mild variant of Bresenham's algorithm;
+ // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
+ var steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
+ if (steep) {
+ var temp = fromX;
+ fromX = fromY;
+ fromY = temp;
+ temp = toX;
+ toX = toY;
+ toY = temp;
+ }
+
+ var dx = Math.abs(toX - fromX);
+ var dy = Math.abs(toY - fromY);
+ var error = -dx >> 1;
+ var ystep = fromY < toY ? 1 : -1;
+ var xstep = fromX < toX ? 1 : -1;
+ var state = 0; // In black pixels, looking for white, first or second time
+ for (var x = fromX, y = fromY; x != toX; x += xstep) {
+
+ var realX = steep ? y : x;
+ var realY = steep ? x : y;
+ if (state == 1) {
+ // In white pixels, looking for black
+ if (this.image[realX + realY * qrcode.width]) {
+ state++;
+ }
+ } else {
+ if (!this.image[realX + realY * qrcode.width]) {
+ state++;
+ }
+ }
+
+ if (state == 3) {
+ // Found black, white, black, and stumbled back onto white; done
+ var diffX = x - fromX;
+ var diffY = y - fromY;
+ return Math.sqrt((diffX * diffX + diffY * diffY));
+ }
+ error += dy;
+ if (error > 0) {
+ if (y == toY) {
+ break;
+ }
+ y += ystep;
+ error -= dx;
+ }
+ }
+ var diffX2 = toX - fromX;
+ var diffY2 = toY - fromY;
+ return Math.sqrt((diffX2 * diffX2 + diffY2 * diffY2));
+ };
+
+
+ this.sizeOfBlackWhiteBlackRunBothWays = function(fromX, fromY, toX, toY) {
+
+ var result = this.sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
+
+ // Now count other way -- don't run off image though of course
+ var scale = 1.0;
+ var otherToX = fromX - (toX - fromX);
+ if (otherToX < 0) {
+ scale = fromX / (fromX - otherToX);
+ otherToX = 0;
+ } else if (otherToX >= qrcode.width) {
+ scale = (qrcode.width - 1 - fromX) / (otherToX - fromX);
+ otherToX = qrcode.width - 1;
+ }
+ var otherToY = Math.floor(fromY - (toY - fromY) * scale);
+
+ scale = 1.0;
+ if (otherToY < 0) {
+ scale = fromY / (fromY - otherToY);
+ otherToY = 0;
+ } else if (otherToY >= qrcode.height) {
+ scale = (qrcode.height - 1 - fromY) / (otherToY - fromY);
+ otherToY = qrcode.height - 1;
+ }
+ otherToX = Math.floor(fromX + (otherToX - fromX) * scale);
+
+ result += this.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
+ return result - 1.0; // -1 because we counted the middle pixel twice
+ };
+
+
+
+ this.calculateModuleSizeOneWay = function(pattern, otherPattern) {
+ var moduleSizeEst1 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(pattern.X), Math.floor(pattern.Y), Math.floor(otherPattern.X), Math.floor(otherPattern.Y));
+ var moduleSizeEst2 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(otherPattern.X), Math.floor(otherPattern.Y), Math.floor(pattern.X), Math.floor(pattern.Y));
+ if (isNaN(moduleSizeEst1)) {
+ return moduleSizeEst2 / 7.0;
+ }
+ if (isNaN(moduleSizeEst2)) {
+ return moduleSizeEst1 / 7.0;
+ }
+ // Average them, and divide by 7 since we've counted the width of 3 black modules,
+ // and 1 white and 1 black module on either side. Ergo, divide sum by 14.
+ return (moduleSizeEst1 + moduleSizeEst2) / 14.0;
+ };
+
+
+ this.calculateModuleSize = function(topLeft, topRight, bottomLeft) {
+ // Take the average
+ return (this.calculateModuleSizeOneWay(topLeft, topRight) + this.calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0;
+ };
+
+ this.distance = function(pattern1, pattern2) {
+ var xDiff = pattern1.X - pattern2.X;
+ var yDiff = pattern1.Y - pattern2.Y;
+ return Math.sqrt((xDiff * xDiff + yDiff * yDiff));
+ };
+ this.computeDimension = function(topLeft, topRight, bottomLeft, moduleSize) {
+
+ var tltrCentersDimension = Math.round(this.distance(topLeft, topRight) / moduleSize);
+ var tlblCentersDimension = Math.round(this.distance(topLeft, bottomLeft) / moduleSize);
+ var dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7;
+ switch (dimension & 0x03) {
+
+ // mod 4
+ case 0:
+ dimension++;
+ break;
+ // 1? do nothing
+
+ case 2:
+ dimension--;
+ break;
+
+ case 3:
+ throw "Error";
+ }
+ return dimension;
+ };
+
+ this.findAlignmentInRegion = function(overallEstModuleSize, estAlignmentX, estAlignmentY, allowanceFactor) {
+ // Look for an alignment pattern (3 modules in size) around where it
+ // should be
+ var allowance = Math.floor(allowanceFactor * overallEstModuleSize);
+ var alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
+ var alignmentAreaRightX = Math.min(qrcode.width - 1, estAlignmentX + allowance);
+ if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
+ throw "Error";
+ }
+
+ var alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
+ var alignmentAreaBottomY = Math.min(qrcode.height - 1, estAlignmentY + allowance);
+
+ var alignmentFinder = new AlignmentPatternFinder(this.image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, this.resultPointCallback);
+ return alignmentFinder.find();
+ };
+
+ this.createTransform = function(topLeft, topRight, bottomLeft, alignmentPattern, dimension) {
+ var dimMinusThree = dimension - 3.5;
+ var bottomRightX;
+ var bottomRightY;
+ var sourceBottomRightX;
+ var sourceBottomRightY;
+ if (alignmentPattern !== null) {
+ bottomRightX = alignmentPattern.X;
+ bottomRightY = alignmentPattern.Y;
+ sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3.0;
+ } else {
+ // Don't have an alignment pattern, just make up the bottom-right point
+ bottomRightX = (topRight.X - topLeft.X) + bottomLeft.X;
+ bottomRightY = (topRight.Y - topLeft.Y) + bottomLeft.Y;
+ sourceBottomRightX = sourceBottomRightY = dimMinusThree;
+ }
+
+ var transform = PerspectiveTransform$1.quadrilateralToQuadrilateral(3.5, 3.5, dimMinusThree, 3.5, sourceBottomRightX, sourceBottomRightY, 3.5, dimMinusThree, topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRightX, bottomRightY, bottomLeft.X, bottomLeft.Y);
+
+ return transform;
+ };
+
+ this.sampleGrid = function(image, transform, dimension) {
+
+ var sampler = GridSampler;
+ return sampler.sampleGrid3(image, dimension, transform);
+ };
+
+ this.processFinderPatternInfo = function(info) {
+
+ var topLeft = info.TopLeft;
+ var topRight = info.TopRight;
+ var bottomLeft = info.BottomLeft;
+
+ var moduleSize = this.calculateModuleSize(topLeft, topRight, bottomLeft);
+ if (moduleSize < 1.0) {
+ throw "Error";
+ }
+ var dimension = this.computeDimension(topLeft, topRight, bottomLeft, moduleSize);
+ var provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
+ var modulesBetweenFPCenters = provisionalVersion.DimensionForVersion - 7;
+
+ var alignmentPattern = null;
+ // Anything above version 1 has an alignment pattern
+ if (provisionalVersion.AlignmentPatternCenters.length > 0) {
+
+ // Guess where a "bottom right" finder pattern would have been
+ var bottomRightX = topRight.X - topLeft.X + bottomLeft.X;
+ var bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y;
+
+ // Estimate that alignment pattern is closer by 3 modules
+ // from "bottom right" to known top left location
+ var correctionToTopLeft = 1.0 - 3.0 / modulesBetweenFPCenters;
+ var estAlignmentX = Math.floor(topLeft.X + correctionToTopLeft * (bottomRightX - topLeft.X));
+ var estAlignmentY = Math.floor(topLeft.Y + correctionToTopLeft * (bottomRightY - topLeft.Y));
+
+ // Kind of arbitrary -- expand search radius before giving up
+ for (var i = 4; i <= 16; i <<= 1) {
+ //try
+ //{
+ alignmentPattern = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i);
+ break;
+ //}
+ //catch (re)
+ //{
+ // try next round
+ //}
+ }
+ // If we didn't find alignment pattern... well try anyway without it
+ }
+
+ var transform = this.createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
+
+ var bits = this.sampleGrid(this.image, transform, dimension);
+
+ var points;
+ if (alignmentPattern === null) {
+ points = new Array(bottomLeft, topLeft, topRight);
+ } else {
+ points = new Array(bottomLeft, topLeft, topRight, alignmentPattern);
+ }
+ return new DetectorResult(bits, points);
+ };
+
+
+
+ this.detect = function() {
+ var info = new FinderPatternFinder().findFinderPattern(this.image);
+
+ return this.processFinderPatternInfo(info);
+ };
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var GF256Poly = function(field, coefficients) {
+ if (coefficients === null || coefficients.length === 0) {
+ throw "System.ArgumentException";
+ }
+ this.field = field;
+ var coefficientsLength = coefficients.length;
+ if (coefficientsLength > 1 && coefficients[0] === 0) {
+ // Leading term must be non-zero for anything except the constant polynomial "0"
+ var firstNonZero = 1;
+ while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) {
+ firstNonZero++;
+ }
+ if (firstNonZero == coefficientsLength) {
+ this.coefficients = field.Zero.coefficients;
+ } else {
+ this.coefficients = new Array(coefficientsLength - firstNonZero);
+ for (var i = 0; i < this.coefficients.length; i++) this.coefficients[i] = 0;
+ //Array.Copy(coefficients, firstNonZero, this.coefficients, 0, this.coefficients.length);
+ for (var ci = 0; ci < this.coefficients.length; ci++) this.coefficients[ci] = coefficients[firstNonZero + ci];
+ }
+ } else {
+ this.coefficients = coefficients;
+ }
+
+ this.__defineGetter__("Zero", function() {
+ return this.coefficients[0] === 0;
+ });
+ this.__defineGetter__("Degree", function() {
+ return this.coefficients.length - 1;
+ });
+ this.__defineGetter__("Coefficients", function() {
+ return this.coefficients;
+ });
+
+ this.getCoefficient = function(degree) {
+ return this.coefficients[this.coefficients.length - 1 - degree];
+ };
+
+ this.evaluateAt = function(a) {
+ if (a === 0) {
+ // Just return the x^0 coefficient
+ return this.getCoefficient(0);
+ }
+ var size = this.coefficients.length;
+ if (a == 1) {
+ // Just the sum of the coefficients
+ var result = 0;
+ for (var i = 0; i < size; i++) {
+ result = GF256.addOrSubtract(result, this.coefficients[i]);
+ }
+ return result;
+ }
+ var result2 = this.coefficients[0];
+ for (var i = 1; i < size; i++) {
+ result2 = GF256.addOrSubtract(this.field.multiply(a, result2), this.coefficients[i]);
+ }
+ return result2;
+ };
+
+ this.addOrSubtract = function(other) {
+ if (this.field != other.field) {
+ throw "GF256Polys do not have same GF256 field";
+ }
+ if (this.Zero) {
+ return other;
+ }
+ if (other.Zero) {
+ return this;
+ }
+
+ var smallerCoefficients = this.coefficients;
+ var largerCoefficients = other.coefficients;
+ if (smallerCoefficients.length > largerCoefficients.length) {
+ var temp = smallerCoefficients;
+ smallerCoefficients = largerCoefficients;
+ largerCoefficients = temp;
+ }
+ var sumDiff = new Array(largerCoefficients.length);
+ var lengthDiff = largerCoefficients.length - smallerCoefficients.length;
+ // Copy high-order terms only found in higher-degree polynomial's coefficients
+ //Array.Copy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
+ for (var ci = 0; ci < lengthDiff; ci++) sumDiff[ci] = largerCoefficients[ci];
+
+ for (var i = lengthDiff; i < largerCoefficients.length; i++) {
+ sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+ }
+
+ return new GF256Poly(field, sumDiff);
+ };
+ this.multiply1 = function(other) {
+ if (this.field != other.field) {
+ throw "GF256Polys do not have same GF256 field";
+ }
+ if (this.Zero || other.Zero) {
+ return this.field.Zero;
+ }
+ var aCoefficients = this.coefficients;
+ var aLength = aCoefficients.length;
+ var bCoefficients = other.coefficients;
+ var bLength = bCoefficients.length;
+ var product = new Array(aLength + bLength - 1);
+ for (var i = 0; i < aLength; i++) {
+ var aCoeff = aCoefficients[i];
+ for (var j = 0; j < bLength; j++) {
+ product[i + j] = GF256.addOrSubtract(product[i + j], this.field.multiply(aCoeff, bCoefficients[j]));
+ }
+ }
+ return new GF256Poly(this.field, product);
+ };
+ this.multiply2 = function(scalar) {
+ if (scalar === 0) {
+ return this.field.Zero;
+ }
+ if (scalar == 1) {
+ return this;
+ }
+ var size = this.coefficients.length;
+ var product = new Array(size);
+ for (var i = 0; i < size; i++) {
+ product[i] = this.field.multiply(this.coefficients[i], scalar);
+ }
+ return new GF256Poly(this.field, product);
+ };
+ this.multiplyByMonomial = function(degree, coefficient) {
+ if (degree < 0) {
+ throw "System.ArgumentException";
+ }
+ if (coefficient === 0) {
+ return this.field.Zero;
+ }
+ var size = this.coefficients.length;
+ var product = new Array(size + degree);
+ for (var i = 0; i < product.length; i++) product[i] = 0;
+ for (var i = 0; i < size; i++) {
+ product[i] = this.field.multiply(this.coefficients[i], coefficient);
+ }
+ return new GF256Poly(this.field, product);
+ };
+ this.divide = function(other) {
+ if (this.field != other.field) {
+ throw "GF256Polys do not have same GF256 field";
+ }
+ if (other.Zero) {
+ throw "Divide by 0";
+ }
+
+ var quotient = this.field.Zero;
+ var remainder = this;
+
+ var denominatorLeadingTerm = other.getCoefficient(other.Degree);
+ var inverseDenominatorLeadingTerm = this.field.inverse(denominatorLeadingTerm);
+
+ while (remainder.Degree >= other.Degree && !remainder.Zero) {
+ var degreeDifference = remainder.Degree - other.Degree;
+ var scale = this.field.multiply(remainder.getCoefficient(remainder.Degree), inverseDenominatorLeadingTerm);
+ var term = other.multiplyByMonomial(degreeDifference, scale);
+ var iterationQuotient = this.field.buildMonomial(degreeDifference, scale);
+ quotient = quotient.addOrSubtract(iterationQuotient);
+ remainder = remainder.addOrSubtract(term);
+ }
+
+ return new Array(quotient, remainder);
+ };
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var GF256 = function(primitive) {
+ this.expTable = new Array(256);
+ this.logTable = new Array(256);
+ var x = 1;
+ for (var i = 0; i < 256; i++) {
+ this.expTable[i] = x;
+ x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
+ if (x >= 0x100) {
+ x ^= primitive;
+ }
+ }
+ for (var i = 0; i < 255; i++) {
+ this.logTable[this.expTable[i]] = i;
+ }
+ // logTable[0] == 0 but this should never be used
+ var at0 = new Array(1);
+ at0[0] = 0;
+ this.zero = new GF256Poly(this, new Array(at0));
+ var at1 = new Array(1);
+ at1[0] = 1;
+ this.one = new GF256Poly(this, new Array(at1));
+
+ this.__defineGetter__("Zero", function() {
+ return this.zero;
+ });
+ this.__defineGetter__("One", function() {
+ return this.one;
+ });
+ this.buildMonomial = function(degree, coefficient) {
+ if (degree < 0) {
+ throw "System.ArgumentException";
+ }
+ if (coefficient === 0) {
+ return zero;
+ }
+ var coefficients = new Array(degree + 1);
+ for (var i = 0; i < coefficients.length; i++) coefficients[i] = 0;
+ coefficients[0] = coefficient;
+ return new GF256Poly(this, coefficients);
+ };
+ this.exp = function(a) {
+ return this.expTable[a];
+ };
+ this.log = function(a) {
+ if (a === 0) {
+ throw "System.ArgumentException";
+ }
+ return this.logTable[a];
+ };
+ this.inverse = function(a) {
+ if (a === 0) {
+ throw "System.ArithmeticException";
+ }
+ return this.expTable[255 - this.logTable[a]];
+ };
+ this.multiply = function(a, b) {
+ if (a === 0 || b === 0) {
+ return 0;
+ }
+ if (a == 1) {
+ return b;
+ }
+ if (b == 1) {
+ return a;
+ }
+ return this.expTable[(this.logTable[a] + this.logTable[b]) % 255];
+ };
+};
+
+GF256.QR_CODE_FIELD = new GF256(0x011D);
+GF256.DATA_MATRIX_FIELD = new GF256(0x012D);
+
+GF256.addOrSubtract = function(a, b) {
+ return a ^ b;
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var ReedSolomonDecoder = function(field) {
+ this.field = field;
+ this.decode = function(received, twoS) {
+ var poly = new GF256Poly(this.field, received);
+ var syndromeCoefficients = new Array(twoS);
+ for (var i = 0; i < syndromeCoefficients.length; i++) syndromeCoefficients[i] = 0;
+ var dataMatrix = false; //this.field.Equals(GF256.DATA_MATRIX_FIELD);
+ var noError = true;
+ for (var i = 0; i < twoS; i++) {
+ // Thanks to sanfordsquires for this fix:
+ var val = poly.evaluateAt(this.field.exp(dataMatrix ? i + 1 : i));
+ syndromeCoefficients[syndromeCoefficients.length - 1 - i] = val;
+ if (val !== 0) {
+ noError = false;
+ }
+ }
+ if (noError) {
+ return;
+ }
+ var syndrome = new GF256Poly(this.field, syndromeCoefficients);
+ var sigmaOmega = this.runEuclideanAlgorithm(this.field.buildMonomial(twoS, 1), syndrome, twoS);
+ var sigma = sigmaOmega[0];
+ var omega = sigmaOmega[1];
+ var errorLocations = this.findErrorLocations(sigma);
+ var errorMagnitudes = this.findErrorMagnitudes(omega, errorLocations, dataMatrix);
+ for (var i = 0; i < errorLocations.length; i++) {
+ var position = received.length - 1 - this.field.log(errorLocations[i]);
+ if (position < 0) {
+ throw "ReedSolomonException Bad error location";
+ }
+ received[position] = GF256.addOrSubtract(received[position], errorMagnitudes[i]);
+ }
+ };
+
+ this.runEuclideanAlgorithm = function(a, b, R) {
+ // Assume a's degree is >= b's
+ if (a.Degree < b.Degree) {
+ var temp = a;
+ a = b;
+ b = temp;
+ }
+
+ var rLast = a;
+ var r = b;
+ var sLast = this.field.One;
+ var s = this.field.Zero;
+ var tLast = this.field.Zero;
+ var t = this.field.One;
+
+ // Run Euclidean algorithm until r's degree is less than R/2
+ while (r.Degree >= Math.floor(R / 2)) {
+ var rLastLast = rLast;
+ var sLastLast = sLast;
+ var tLastLast = tLast;
+ rLast = r;
+ sLast = s;
+ tLast = t;
+
+ // Divide rLastLast by rLast, with quotient in q and remainder in r
+ if (rLast.Zero) {
+ // Oops, Euclidean algorithm already terminated?
+ throw "r_{i-1} was zero";
+ }
+ r = rLastLast;
+ var q = this.field.Zero;
+ var denominatorLeadingTerm = rLast.getCoefficient(rLast.Degree);
+ var dltInverse = this.field.inverse(denominatorLeadingTerm);
+ while (r.Degree >= rLast.Degree && !r.Zero) {
+ var degreeDiff = r.Degree - rLast.Degree;
+ var scale = this.field.multiply(r.getCoefficient(r.Degree), dltInverse);
+ q = q.addOrSubtract(this.field.buildMonomial(degreeDiff, scale));
+ r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
+ //r.EXE();
+ }
+
+ s = q.multiply1(sLast).addOrSubtract(sLastLast);
+ t = q.multiply1(tLast).addOrSubtract(tLastLast);
+ }
+
+ var sigmaTildeAtZero = t.getCoefficient(0);
+ if (sigmaTildeAtZero === 0) {
+ throw "ReedSolomonException sigmaTilde(0) was zero";
+ }
+
+ var inverse = this.field.inverse(sigmaTildeAtZero);
+ var sigma = t.multiply2(inverse);
+ var omega = r.multiply2(inverse);
+ return new Array(sigma, omega);
+ };
+ this.findErrorLocations = function(errorLocator) {
+ // This is a direct application of Chien's search
+ var numErrors = errorLocator.Degree;
+ if (numErrors == 1) {
+ // shortcut
+ return new Array(errorLocator.getCoefficient(1));
+ }
+ var result = new Array(numErrors);
+ var e = 0;
+ for (var i = 1; i < 256 && e < numErrors; i++) {
+ if (errorLocator.evaluateAt(i) === 0) {
+ result[e] = this.field.inverse(i);
+ e++;
+ }
+ }
+ if (e != numErrors) {
+ throw "Error locator degree does not match number of roots";
+ }
+ return result;
+ };
+ this.findErrorMagnitudes = function(errorEvaluator, errorLocations, dataMatrix) {
+ // This is directly applying Forney's Formula
+ var s = errorLocations.length;
+ var result = new Array(s);
+ for (var i = 0; i < s; i++) {
+ var xiInverse = this.field.inverse(errorLocations[i]);
+ var denominator = 1;
+ for (var j = 0; j < s; j++) {
+ if (i != j) {
+ denominator = this.field.multiply(denominator, GF256.addOrSubtract(1, this.field.multiply(errorLocations[j], xiInverse)));
+ }
+ }
+ result[i] = this.field.multiply(errorEvaluator.evaluateAt(xiInverse), this.field.inverse(denominator));
+ // Thanks to sanfordsquires for this fix:
+ if (dataMatrix) {
+ result[i] = this.field.multiply(result[i], xiInverse);
+ }
+ }
+ return result;
+ };
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+var ErrorCorrectionLevel = function(ordinal, bits, name) {
+ this.ordinal_Renamed_Field = ordinal;
+ this.bits = bits;
+ this.name = name;
+ this.__defineGetter__("Bits", function() {
+ return this.bits;
+ });
+ this.__defineGetter__("Name", function() {
+ return this.name;
+ });
+ this.ordinal = function() {
+ return this.ordinal_Renamed_Field;
+ };
+};
+
+ErrorCorrectionLevel.forBits = function(bits) {
+ if (bits < 0 || bits >= FOR_BITS.length) {
+ throw "ArgumentException";
+ }
+ return FOR_BITS[bits];
+};
+
+var L = new ErrorCorrectionLevel(0, 0x01, "L");
+var M = new ErrorCorrectionLevel(1, 0x00, "M");
+var Q = new ErrorCorrectionLevel(2, 0x03, "Q");
+var H = new ErrorCorrectionLevel(3, 0x02, "H");
+var FOR_BITS = new Array(M, L, H, Q);
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var FORMAT_INFO_MASK_QR = 0x5412;
+var FORMAT_INFO_DECODE_LOOKUP = new Array(new Array(0x5412, 0x00), new Array(0x5125, 0x01), new Array(0x5E7C, 0x02), new Array(0x5B4B, 0x03), new Array(0x45F9, 0x04), new Array(0x40CE, 0x05), new Array(0x4F97, 0x06), new Array(0x4AA0, 0x07), new Array(0x77C4, 0x08), new Array(0x72F3, 0x09), new Array(0x7DAA, 0x0A), new Array(0x789D, 0x0B), new Array(0x662F, 0x0C), new Array(0x6318, 0x0D), new Array(0x6C41, 0x0E), new Array(0x6976, 0x0F), new Array(0x1689, 0x10), new Array(0x13BE, 0x11), new Array(0x1CE7, 0x12), new Array(0x19D0, 0x13), new Array(0x0762, 0x14), new Array(0x0255, 0x15), new Array(0x0D0C, 0x16), new Array(0x083B, 0x17), new Array(0x355F, 0x18), new Array(0x3068, 0x19), new Array(0x3F31, 0x1A), new Array(0x3A06, 0x1B), new Array(0x24B4, 0x1C), new Array(0x2183, 0x1D), new Array(0x2EDA, 0x1E), new Array(0x2BED, 0x1F));
+var BITS_SET_IN_HALF_BYTE = new Array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
+
+
+var FormatInformation$1 = function(formatInfo) {
+ this.errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03);
+ this.dataMask = (formatInfo & 0x07);
+
+ this.__defineGetter__("ErrorCorrectionLevel", function() {
+ return this.errorCorrectionLevel;
+ });
+ this.__defineGetter__("DataMask", function() {
+ return this.dataMask;
+ });
+ this.GetHashCode = function() {
+ return (this.errorCorrectionLevel.ordinal() << 3) | dataMask;
+ };
+ this.Equals = function(o) {
+ var other = o;
+ return this.errorCorrectionLevel == other.errorCorrectionLevel && this.dataMask == other.dataMask;
+ };
+};
+
+FormatInformation$1.numBitsDiffering = function(a, b) {
+ a ^= b; // a now has a 1 bit exactly where its bit differs with b's
+ // Count bits set quickly with a series of lookups:
+ return BITS_SET_IN_HALF_BYTE[a & 0x0F] + BITS_SET_IN_HALF_BYTE[(URShift(a, 4) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 8) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 12) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 16) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 20) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 24) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(URShift(a, 28) & 0x0F)];
+};
+
+FormatInformation$1.decodeFormatInformation = function(maskedFormatInfo) {
+ var formatInfo = FormatInformation$1.doDecodeFormatInformation(maskedFormatInfo);
+ if (formatInfo !== null) {
+ return formatInfo;
+ }
+ // Should return null, but, some QR codes apparently
+ // do not mask this info. Try again by actually masking the pattern
+ // first
+ return FormatInformation$1.doDecodeFormatInformation(maskedFormatInfo ^ FORMAT_INFO_MASK_QR);
+};
+FormatInformation$1.doDecodeFormatInformation = function(maskedFormatInfo) {
+ // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
+ var bestDifference = 0xffffffff;
+ var bestFormatInfo = 0;
+ for (var i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) {
+ var decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i];
+ var targetInfo = decodeInfo[0];
+ if (targetInfo == maskedFormatInfo) {
+ // Found an exact match
+ return new FormatInformation$1(decodeInfo[1]);
+ }
+ var bitsDifference = this.numBitsDiffering(maskedFormatInfo, targetInfo);
+ if (bitsDifference < bestDifference) {
+ bestFormatInfo = decodeInfo[1];
+ bestDifference = bitsDifference;
+ }
+ }
+ // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits
+ // differing means we found a match
+ if (bestDifference <= 3) {
+ return new FormatInformation$1(bestFormatInfo);
+ }
+ return null;
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var DataMask = {};
+
+DataMask.forReference = function(reference) {
+ if (reference < 0 || reference > 7) {
+ throw "System.ArgumentException";
+ }
+ return DataMask.DATA_MASKS[reference];
+};
+
+var DataMask000 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return ((i + j) & 0x01) === 0;
+ };
+};
+
+var DataMask001 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (i & 0x01) === 0;
+ };
+};
+
+var DataMask010 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return j % 3 === 0;
+ };
+};
+
+var DataMask011 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (i + j) % 3 === 0;
+ };
+};
+
+var DataMask100 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (((URShift(i, 1)) + (j / 3)) & 0x01) === 0;
+ };
+};
+
+var DataMask101 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ var temp = i * j;
+ return (temp & 0x01) + (temp % 3) === 0;
+ };
+};
+
+var DataMask110 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ var temp = i * j;
+ return (((temp & 0x01) + (temp % 3)) & 0x01) === 0;
+ };
+};
+
+var DataMask111 = function() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (var i = 0; i < dimension; i++) {
+ for (var j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) === 0;
+ };
+};
+
+DataMask.DATA_MASKS = new Array(new DataMask000(), new DataMask001(), new DataMask010(), new DataMask011(), new DataMask100(), new DataMask101(), new DataMask110(), new DataMask111());
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var BitMatrixParser = function(bitMatrix) {
+ var dimension = bitMatrix.Dimension;
+ if (dimension < 21 || (dimension & 0x03) != 1) {
+ throw "Error BitMatrixParser";
+ }
+ this.bitMatrix = bitMatrix;
+ this.parsedVersion = null;
+ this.parsedFormatInfo = null;
+
+ this.copyBit = function(i, j, versionBits) {
+ return this.bitMatrix.get_Renamed(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1;
+ };
+
+ this.readFormatInformation = function() {
+ if (this.parsedFormatInfo !== null) {
+ return this.parsedFormatInfo;
+ }
+
+ // Read top-left format info bits
+ var formatInfoBits = 0;
+ for (var i = 0; i < 6; i++) {
+ formatInfoBits = this.copyBit(i, 8, formatInfoBits);
+ }
+ // .. and skip a bit in the timing pattern ...
+ formatInfoBits = this.copyBit(7, 8, formatInfoBits);
+ formatInfoBits = this.copyBit(8, 8, formatInfoBits);
+ formatInfoBits = this.copyBit(8, 7, formatInfoBits);
+ // .. and skip a bit in the timing pattern ...
+ for (var j = 5; j >= 0; j--) {
+ formatInfoBits = this.copyBit(8, j, formatInfoBits);
+ }
+
+ this.parsedFormatInfo = FormatInformation$1.decodeFormatInformation(formatInfoBits);
+ if (this.parsedFormatInfo !== null) {
+ return this.parsedFormatInfo;
+ }
+
+ // Hmm, failed. Try the top-right/bottom-left pattern
+ var dimension = this.bitMatrix.Dimension;
+ formatInfoBits = 0;
+ var iMin = dimension - 8;
+ for (var i = dimension - 1; i >= iMin; i--) {
+ formatInfoBits = this.copyBit(i, 8, formatInfoBits);
+ }
+ for (var j = dimension - 7; j < dimension; j++) {
+ formatInfoBits = this.copyBit(8, j, formatInfoBits);
+ }
+
+ this.parsedFormatInfo = FormatInformation$1.decodeFormatInformation(formatInfoBits);
+ if (this.parsedFormatInfo !== null) {
+ return this.parsedFormatInfo;
+ }
+ throw "Error readFormatInformation";
+ };
+ this.readVersion = function() {
+
+ if (this.parsedVersion !== null) {
+ return this.parsedVersion;
+ }
+
+ var dimension = this.bitMatrix.Dimension;
+
+ var provisionalVersion = (dimension - 17) >> 2;
+ if (provisionalVersion <= 6) {
+ return Version.getVersionForNumber(provisionalVersion);
+ }
+
+ // Read top-right version info: 3 wide by 6 tall
+ var versionBits = 0;
+ var ijMin = dimension - 11;
+ for (var j = 5; j >= 0; j--) {
+ for (var i = dimension - 9; i >= ijMin; i--) {
+ versionBits = this.copyBit(i, j, versionBits);
+ }
+ }
+
+ this.parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (this.parsedVersion !== null && this.parsedVersion.DimensionForVersion == dimension) {
+ return this.parsedVersion;
+ }
+
+ // Hmm, failed. Try bottom left: 6 wide by 3 tall
+ versionBits = 0;
+ for (var i = 5; i >= 0; i--) {
+ for (var j = dimension - 9; j >= ijMin; j--) {
+ versionBits = this.copyBit(i, j, versionBits);
+ }
+ }
+
+ this.parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (this.parsedVersion !== null && this.parsedVersion.DimensionForVersion === dimension) {
+ return this.parsedVersion;
+ }
+ throw "Error readVersion";
+ };
+ this.readCodewords = function() {
+
+ var formatInfo = this.readFormatInformation();
+ var version = this.readVersion();
+
+ // Get the data mask for the format used in this QR Code. This will exclude
+ // some bits from reading as we wind through the bit matrix.
+ var dataMask = DataMask.forReference(formatInfo.DataMask);
+ var dimension = this.bitMatrix.Dimension;
+ dataMask.unmaskBitMatrix(this.bitMatrix, dimension);
+
+ var functionPattern = version.buildFunctionPattern();
+
+ var readingUp = true;
+ var result = new Array(version.TotalCodewords);
+ var resultOffset = 0;
+ var currentByte = 0;
+ var bitsRead = 0;
+ // Read columns in pairs, from right to left
+ for (var j = dimension - 1; j > 0; j -= 2) {
+ if (j == 6) {
+ // Skip whole column with vertical alignment pattern;
+ // saves time and makes the other code proceed more cleanly
+ j--;
+ }
+ // Read alternatingly from bottom to top then top to bottom
+ for (var count = 0; count < dimension; count++) {
+ var i = readingUp ? dimension - 1 - count : count;
+ for (var col = 0; col < 2; col++) {
+ // Ignore bits covered by the function pattern
+ if (!functionPattern.get_Renamed(j - col, i)) {
+ // Read a bit
+ bitsRead++;
+ currentByte <<= 1;
+ if (this.bitMatrix.get_Renamed(j - col, i)) {
+ currentByte |= 1;
+ }
+ // If we've made a whole byte, save it off
+ if (bitsRead == 8) {
+ result[resultOffset++] = currentByte;
+ bitsRead = 0;
+ currentByte = 0;
+ }
+ }
+ }
+ }
+ readingUp ^= true; // readingUp = !readingUp; // switch directions
+ }
+ if (resultOffset != version.TotalCodewords) {
+ throw "Error readCodewords";
+ }
+ return result;
+ };
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+var DataBlock = function(numDataCodewords, codewords) {
+ this.numDataCodewords = numDataCodewords;
+ this.codewords = codewords;
+
+ this.__defineGetter__("NumDataCodewords", function() {
+ return this.numDataCodewords;
+ });
+ this.__defineGetter__("Codewords", function() {
+ return this.codewords;
+ });
+};
+
+DataBlock.getDataBlocks = function(rawCodewords, version, ecLevel) {
+
+ if (rawCodewords.length != version.TotalCodewords) {
+ throw "ArgumentException";
+ }
+
+ // Figure out the number and size of data blocks used by this version and
+ // error correction level
+ var ecBlocks = version.getECBlocksForLevel(ecLevel);
+
+ // First count the total number of data blocks
+ var totalBlocks = 0;
+ var ecBlockArray = ecBlocks.getECBlocks();
+ for (var i = 0; i < ecBlockArray.length; i++) {
+ totalBlocks += ecBlockArray[i].Count;
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ var result = new Array(totalBlocks);
+ var numResultBlocks = 0;
+ for (var j = 0; j < ecBlockArray.length; j++) {
+ var ecBlock = ecBlockArray[j];
+ for (var i = 0; i < ecBlock.Count; i++) {
+ var numDataCodewords = ecBlock.DataCodewords;
+ var numBlockCodewords = ecBlocks.ECCodewordsPerBlock + numDataCodewords;
+ result[numResultBlocks++] = new DataBlock(numDataCodewords, new Array(numBlockCodewords));
+ }
+ }
+
+ // All blocks have the same amount of data, except that the last n
+ // (where n may be 0) have 1 more byte. Figure out where these start.
+ var shorterBlocksTotalCodewords = result[0].codewords.length;
+ var longerBlocksStartAt = result.length - 1;
+ while (longerBlocksStartAt >= 0) {
+ var numCodewords = result[longerBlocksStartAt].codewords.length;
+ if (numCodewords == shorterBlocksTotalCodewords) {
+ break;
+ }
+ longerBlocksStartAt--;
+ }
+ longerBlocksStartAt++;
+
+ var shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.ECCodewordsPerBlock;
+ // The last elements of result may be 1 element longer;
+ // first fill out as many elements as all of them have
+ var rawCodewordsOffset = 0;
+ for (var i = 0; i < shorterBlocksNumDataCodewords; i++) {
+ for (var j = 0; j < numResultBlocks; j++) {
+ result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+ // Fill out the last data block in the longer ones
+ for (var j = longerBlocksStartAt; j < numResultBlocks; j++) {
+ result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++];
+ }
+ // Now add in error correction blocks
+ var max = result[0].codewords.length;
+ for (var i = shorterBlocksNumDataCodewords; i < max; i++) {
+ for (var j = 0; j < numResultBlocks; j++) {
+ var iOffset = j < longerBlocksStartAt ? i : i + 1;
+ result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+ return result;
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+ *
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var QRCodeDataBlockReader = function(blocks, version, numErrorCorrectionCode) {
+ this.blockPointer = 0;
+ this.bitPointer = 7;
+ this.dataLength = 0;
+ this.blocks = blocks;
+ this.numErrorCorrectionCode = numErrorCorrectionCode;
+ if (version <= 9)
+ this.dataLengthMode = 0;
+ else if (version >= 10 && version <= 26)
+ this.dataLengthMode = 1;
+ else if (version >= 27 && version <= 40)
+ this.dataLengthMode = 2;
+
+ this.getNextBits = function(numBits) {
+ var bits = 0;
+ if (numBits < this.bitPointer + 1) {
+ // next word fits into current data block
+ var mask = 0;
+ for (var i = 0; i < numBits; i++) {
+ mask += (1 << i);
+ }
+ mask <<= (this.bitPointer - numBits + 1);
+
+ bits = (this.blocks[this.blockPointer] & mask) >> (this.bitPointer - numBits + 1);
+ this.bitPointer -= numBits;
+ return bits;
+ } else if (numBits < this.bitPointer + 1 + 8) {
+ // next word crosses 2 data blocks
+ var mask1 = 0;
+ for (var i = 0; i < this.bitPointer + 1; i++) {
+ mask1 += (1 << i);
+ }
+ bits = (this.blocks[this.blockPointer] & mask1) << (numBits - (this.bitPointer + 1));
+ this.blockPointer++;
+ bits += ((this.blocks[this.blockPointer]) >> (8 - (numBits - (this.bitPointer + 1))));
+
+ this.bitPointer = this.bitPointer - numBits % 8;
+ if (this.bitPointer < 0) {
+ this.bitPointer = 8 + this.bitPointer;
+ }
+ return bits;
+ } else if (numBits < this.bitPointer + 1 + 16) {
+ // next word crosses 3 data blocks
+ var mask1 = 0; // mask of first block
+ var mask3 = 0; // mask of 3rd block
+ //bitPointer + 1 : number of bits of the 1st block
+ //8 : number of the 2nd block (note that use already 8bits because next word uses 3 data blocks)
+ //numBits - (bitPointer + 1 + 8) : number of bits of the 3rd block
+ for (var i = 0; i < this.bitPointer + 1; i++) {
+ mask1 += (1 << i);
+ }
+ var bitsFirstBlock = (this.blocks[this.blockPointer] & mask1) << (numBits - (this.bitPointer + 1));
+ this.blockPointer++;
+
+ var bitsSecondBlock = this.blocks[this.blockPointer] << (numBits - (this.bitPointer + 1 + 8));
+ this.blockPointer++;
+
+ for (var i = 0; i < numBits - (this.bitPointer + 1 + 8); i++) {
+ mask3 += (1 << i);
+ }
+ mask3 <<= 8 - (numBits - (this.bitPointer + 1 + 8));
+ var bitsThirdBlock = (this.blocks[this.blockPointer] & mask3) >> (8 - (numBits - (this.bitPointer + 1 + 8)));
+
+ bits = bitsFirstBlock + bitsSecondBlock + bitsThirdBlock;
+ this.bitPointer = this.bitPointer - (numBits - 8) % 8;
+ if (this.bitPointer < 0) {
+ this.bitPointer = 8 + this.bitPointer;
+ }
+ return bits;
+ } else {
+ return 0;
+ }
+ };
+ this.NextMode = function() {
+ if ((this.blockPointer > this.blocks.length - this.numErrorCorrectionCode - 2))
+ return 0;
+ else
+ return this.getNextBits(4);
+ };
+ this.getDataLength = function(modeIndicator) {
+ var index = 0;
+ while (true) {
+ if ((modeIndicator >> index) == 1)
+ break;
+ index++;
+ }
+
+ return this.getNextBits(qrcode.sizeOfDataLengthInfo[this.dataLengthMode][index]);
+ };
+ this.getRomanAndFigureString = function(dataLength) {
+ var length = dataLength;
+ var intData = 0;
+ var strData = "";
+ var tableRomanAndFigure = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':');
+ do {
+ if (length > 1) {
+ intData = this.getNextBits(11);
+ var firstLetter = Math.floor(intData / 45);
+ var secondLetter = intData % 45;
+ strData += tableRomanAndFigure[firstLetter];
+ strData += tableRomanAndFigure[secondLetter];
+ length -= 2;
+ } else if (length == 1) {
+ intData = this.getNextBits(6);
+ strData += tableRomanAndFigure[intData];
+ length -= 1;
+ }
+ }
+ while (length > 0);
+
+ return strData;
+ };
+ this.getFigureString = function(dataLength) {
+ var length = dataLength;
+ var intData = 0;
+ var strData = "";
+ do {
+ if (length >= 3) {
+ intData = this.getNextBits(10);
+ if (intData < 100)
+ strData += "0";
+ if (intData < 10)
+ strData += "0";
+ length -= 3;
+ } else if (length == 2) {
+ intData = this.getNextBits(7);
+ if (intData < 10)
+ strData += "0";
+ length -= 2;
+ } else if (length == 1) {
+ intData = this.getNextBits(4);
+ length -= 1;
+ }
+ strData += intData;
+ }
+ while (length > 0);
+
+ return strData;
+ };
+ this.get8bitByteArray = function(dataLength) {
+ var length = dataLength;
+ var intData = 0;
+ var output = new Array();
+
+ do {
+ intData = this.getNextBits(8);
+ output.push(intData);
+ length--;
+ }
+ while (length > 0);
+ return output;
+ };
+ this.getKanjiString = function(dataLength) {
+ var length = dataLength;
+ var intData = 0;
+ var unicodeString = "";
+ do {
+ intData = getNextBits(13);
+ var lowerByte = intData % 0xC0;
+ var higherByte = intData / 0xC0;
+
+ var tempWord = (higherByte << 8) + lowerByte;
+ var shiftjisWord = 0;
+ if (tempWord + 0x8140 <= 0x9FFC) {
+ // between 8140 - 9FFC on Shift_JIS character set
+ shiftjisWord = tempWord + 0x8140;
+ } else {
+ // between E040 - EBBF on Shift_JIS character set
+ shiftjisWord = tempWord + 0xC140;
+ }
+
+ //var tempByte = new Array(0,0);
+ //tempByte[0] = (sbyte) (shiftjisWord >> 8);
+ //tempByte[1] = (sbyte) (shiftjisWord & 0xFF);
+ //unicodeString += new String(SystemUtils.ToCharArray(SystemUtils.ToByteArray(tempByte)));
+ unicodeString += String.fromCharCode(shiftjisWord);
+ length--;
+ }
+ while (length > 0);
+
+
+ return unicodeString;
+ };
+
+ this.__defineGetter__("DataByte", function() {
+ var output = new Array();
+ var MODE_NUMBER = 1;
+ var MODE_ROMAN_AND_NUMBER = 2;
+ var MODE_8BIT_BYTE = 4;
+ var MODE_KANJI = 8;
+ do {
+ var mode = this.NextMode();
+ //canvas.println("mode: " + mode);
+ if (mode === 0) {
+ if (output.length > 0)
+ break;
+ else
+ throw "Empty data block";
+ }
+ //if (mode != 1 && mode != 2 && mode != 4 && mode != 8)
+ // break;
+ //}
+ if (mode != MODE_NUMBER && mode != MODE_ROMAN_AND_NUMBER && mode != MODE_8BIT_BYTE && mode != MODE_KANJI) {
+ /* canvas.println("Invalid mode: " + mode);
+ mode = guessMode(mode);
+ canvas.println("Guessed mode: " + mode); */
+ throw "Invalid mode: " + mode + " in (block:" + this.blockPointer + " bit:" + this.bitPointer + ")";
+ }
+ var dataLength = this.getDataLength(mode);
+ if (dataLength < 1)
+ throw "Invalid data length: " + dataLength;
+ //canvas.println("length: " + dataLength);
+ switch (mode) {
+
+ case MODE_NUMBER:
+ //canvas.println("Mode: Figure");
+ var temp_str = this.getFigureString(dataLength);
+ var ta = new Array(temp_str.length);
+ for (var j = 0; j < temp_str.length; j++)
+ ta[j] = temp_str.charCodeAt(j);
+ output.push(ta);
+ break;
+
+ case MODE_ROMAN_AND_NUMBER:
+ //canvas.println("Mode: Roman&Figure");
+ var temp_str = this.getRomanAndFigureString(dataLength);
+ var ta = new Array(temp_str.length);
+ for (var j = 0; j < temp_str.length; j++)
+ ta[j] = temp_str.charCodeAt(j);
+ output.push(ta);
+ //output.Write(SystemUtils.ToByteArray(temp_sbyteArray2), 0, temp_sbyteArray2.Length);
+ break;
+
+ case MODE_8BIT_BYTE:
+ //canvas.println("Mode: 8bit Byte");
+ //sbyte[] temp_sbyteArray3;
+ var temp_sbyteArray3 = this.get8bitByteArray(dataLength);
+ output.push(temp_sbyteArray3);
+ //output.Write(SystemUtils.ToByteArray(temp_sbyteArray3), 0, temp_sbyteArray3.Length);
+ break;
+
+ case MODE_KANJI:
+ //canvas.println("Mode: Kanji");
+ //sbyte[] temp_sbyteArray4;
+ //temp_sbyteArray4 = SystemUtils.ToSByteArray(SystemUtils.ToByteArray(getKanjiString(dataLength)));
+ //output.Write(SystemUtils.ToByteArray(temp_sbyteArray4), 0, temp_sbyteArray4.Length);
+ var temp_str = this.getKanjiString(dataLength);
+ output.push(temp_str);
+ break;
+ }
+ //
+ //canvas.println("DataLength: " + dataLength);
+ //Console.out.println(dataString);
+ }
+ while (true);
+ return output;
+ });
+};
+
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+
+*/
+
+/*
+*
+* Copyright 2007 ZXing authors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+var Decoder={};
+Decoder.rsDecoder = new ReedSolomonDecoder(GF256.QR_CODE_FIELD);
+
+Decoder.correctErrors=function( codewordBytes, numDataCodewords)
+{
+ var numCodewords = codewordBytes.length;
+ // First read into an array of ints
+ var codewordsInts = new Array(numCodewords);
+ for (var i = 0; i < numCodewords; i++)
+ {
+ codewordsInts[i] = codewordBytes[i] & 0xFF;
+ }
+ var numECCodewords = codewordBytes.length - numDataCodewords;
+ try
+ {
+ Decoder.rsDecoder.decode(codewordsInts, numECCodewords);
+ //var corrector = new ReedSolomon(codewordsInts, numECCodewords);
+ //corrector.correct();
+ }
+ catch ( rse)
+ {
+ throw rse;
+ }
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for (var i = 0; i < numDataCodewords; i++)
+ {
+ codewordBytes[i] = codewordsInts[i];
+ }
+};
+
+Decoder.decode=function(bits)
+{
+ var parser = new BitMatrixParser(bits);
+ var version = parser.readVersion();
+ var ecLevel = parser.readFormatInformation().ErrorCorrectionLevel;
+
+ // Read codewords
+ var codewords = parser.readCodewords();
+
+ // Separate into data blocks
+ var dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
+
+ // Count total number of data bytes
+ var totalBytes = 0;
+ for (var i = 0; i < dataBlocks.length; i++)
+ {
+ totalBytes += dataBlocks[i].NumDataCodewords;
+ }
+ var resultBytes = new Array(totalBytes);
+ var resultOffset = 0;
+
+ // Error-correct and copy data blocks together into a stream of bytes
+ for (var j = 0; j < dataBlocks.length; j++)
+ {
+ var dataBlock = dataBlocks[j];
+ var codewordBytes = dataBlock.Codewords;
+ var numDataCodewords = dataBlock.NumDataCodewords;
+ Decoder.correctErrors(codewordBytes, numDataCodewords);
+ for (var i = 0; i < numDataCodewords; i++)
+ {
+ resultBytes[resultOffset++] = codewordBytes[i];
+ }
+ }
+
+ // Decode the contents of that stream of bytes
+ var reader = new QRCodeDataBlockReader(resultBytes, version.VersionNumber, ecLevel.Bits);
+ return reader;
+ //return DecodedBitStreamParser.decode(resultBytes, version, ecLevel);
+};
+
+/*
+ Copyright 2011 Lazar Laszlo (lazarsoft@gmail.com, www.lazarsoft.info)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+var qrcode = {};
+qrcode.imagedata = null;
+qrcode.width = 0;
+qrcode.height = 0;
+qrcode.qrCodeSymbol = null;
+qrcode.debug = false;
+qrcode.maxImgSize = 1024 * 1024;
+
+qrcode.sizeOfDataLengthInfo = [
+ [10, 9, 8, 8],
+ [12, 11, 16, 10],
+ [14, 13, 16, 12]
+];
+
+qrcode.callback = null;
+
+qrcode.decode = function(src) {
+
+ if (arguments.length === 0) {
+ var canvas_qr = document.getElementById("qr-canvas");
+ var context = canvas_qr.getContext('2d');
+ qrcode.width = canvas_qr.width;
+ qrcode.height = canvas_qr.height;
+ qrcode.imagedata = context.getImageData(0, 0, qrcode.width, qrcode.height);
+ qrcode.result = qrcode.process(context);
+ if (qrcode.callback !== null)
+ qrcode.callback(qrcode.result);
+ return qrcode.result;
+ } else {
+ var image = new Image();
+ image.crossOrigin = "Anonymous";
+ image.onload = function() {
+ //var canvas_qr = document.getElementById("qr-canvas");
+ var canvas_qr = document.createElement('canvas');
+ var context = canvas_qr.getContext('2d');
+ var nheight = image.height;
+ var nwidth = image.width;
+ if (image.width * image.height > qrcode.maxImgSize) {
+ var ir = image.width / image.height;
+ nheight = Math.sqrt(qrcode.maxImgSize / ir);
+ nwidth = ir * nheight;
+ }
+
+ canvas_qr.width = nwidth;
+ canvas_qr.height = nheight;
+
+ context.drawImage(image, 0, 0, canvas_qr.width, canvas_qr.height);
+ qrcode.width = canvas_qr.width;
+ qrcode.height = canvas_qr.height;
+ try {
+ qrcode.imagedata = context.getImageData(0, 0, canvas_qr.width, canvas_qr.height);
+ } catch (e) {
+ qrcode.result = "Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!";
+ if (qrcode.callback !== null)
+ qrcode.callback(qrcode.result);
+ return;
+ }
+
+ try {
+ qrcode.result = qrcode.process(context);
+ } catch (e) {
+ console.log(e);
+ qrcode.result = "error decoding QR Code";
+ }
+ if (qrcode.callback !== null)
+ qrcode.callback(qrcode.result);
+ };
+ image.src = src;
+ }
+};
+
+qrcode.isUrl = function(s) {
+ var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
+ return regexp.test(s);
+};
+
+qrcode.decode_url = function(s) {
+ var escaped = "";
+ try {
+ escaped = escape(s);
+ } catch (e) {
+ console.log(e);
+ escaped = s;
+ }
+ var ret = "";
+ try {
+ ret = decodeURIComponent(escaped);
+ } catch (e) {
+ console.log(e);
+ ret = escaped;
+ }
+ return ret;
+};
+
+qrcode.decode_utf8 = function(s) {
+ if (qrcode.isUrl(s))
+ return qrcode.decode_url(s);
+ else
+ return s;
+};
+
+qrcode.process = function(ctx) {
+
+ var start = new Date().getTime();
+
+ var image = qrcode.grayScaleToBitmap(qrcode.grayscale());
+ //var image = qrcode.binarize(128);
+
+ if (qrcode.debug) {
+ for (var y = 0; y < qrcode.height; y++) {
+ for (var x = 0; x < qrcode.width; x++) {
+ var point = (x * 4) + (y * qrcode.width * 4);
+ qrcode.imagedata.data[point] = image[x + y * qrcode.width] ? 0 : 0;
+ qrcode.imagedata.data[point + 1] = image[x + y * qrcode.width] ? 0 : 0;
+ qrcode.imagedata.data[point + 2] = image[x + y * qrcode.width] ? 255 : 0;
+ }
+ }
+ ctx.putImageData(qrcode.imagedata, 0, 0);
+ }
+
+ //var finderPatternInfo = new FinderPatternFinder().findFinderPattern(image);
+
+ var detector = new Detector(image);
+
+ var qRCodeMatrix = detector.detect();
+
+ /*for (var y = 0; y < qRCodeMatrix.bits.Height; y++)
+ {
+ for (var x = 0; x < qRCodeMatrix.bits.Width; x++)
+ {
+ var point = (x * 4*2) + (y*2 * qrcode.width * 4);
+ qrcode.imagedata.data[point] = qRCodeMatrix.bits.get_Renamed(x,y)?0:0;
+ qrcode.imagedata.data[point+1] = qRCodeMatrix.bits.get_Renamed(x,y)?0:0;
+ qrcode.imagedata.data[point+2] = qRCodeMatrix.bits.get_Renamed(x,y)?255:0;
+ }
+ }*/
+ if (qrcode.debug)
+ ctx.putImageData(qrcode.imagedata, 0, 0);
+
+ var reader = Decoder.decode(qRCodeMatrix.bits);
+ var data = reader.DataByte;
+ var str = "";
+ for (var i = 0; i < data.length; i++) {
+ for (var j = 0; j < data[i].length; j++)
+ str += String.fromCharCode(data[i][j]);
+ }
+
+ var end = new Date().getTime();
+ var time = end - start;
+ console.log(time);
+
+ return qrcode.decode_utf8(str);
+ //alert("Time:" + time + " Code: "+str);
+};
+
+qrcode.getPixel = function(x, y) {
+ if (qrcode.width < x) {
+ throw "point error";
+ }
+ if (qrcode.height < y) {
+ throw "point error";
+ }
+ var point = (x * 4) + (y * qrcode.width * 4);
+ var p = (qrcode.imagedata.data[point] * 33 + qrcode.imagedata.data[point + 1] * 34 + qrcode.imagedata.data[point + 2] * 33) / 100;
+ return p;
+};
+
+qrcode.binarize = function(th) {
+ var ret = new Array(qrcode.width * qrcode.height);
+ for (var y = 0; y < qrcode.height; y++) {
+ for (var x = 0; x < qrcode.width; x++) {
+ var gray = qrcode.getPixel(x, y);
+
+ ret[x + y * qrcode.width] = gray <= th ? true : false;
+ }
+ }
+ return ret;
+};
+
+qrcode.getMiddleBrightnessPerArea = function(image) {
+ var numSqrtArea = 4;
+ //obtain middle brightness((min + max) / 2) per area
+ var areaWidth = Math.floor(qrcode.width / numSqrtArea);
+ var areaHeight = Math.floor(qrcode.height / numSqrtArea);
+ var minmax = new Array(numSqrtArea);
+ for (var i = 0; i < numSqrtArea; i++) {
+ minmax[i] = new Array(numSqrtArea);
+ for (var i2 = 0; i2 < numSqrtArea; i2++) {
+ minmax[i][i2] = new Array(0, 0);
+ }
+ }
+ for (var ay = 0; ay < numSqrtArea; ay++) {
+ for (var ax = 0; ax < numSqrtArea; ax++) {
+ minmax[ax][ay][0] = 0xFF;
+ for (var dy = 0; dy < areaHeight; dy++) {
+ for (var dx = 0; dx < areaWidth; dx++) {
+ var target = image[areaWidth * ax + dx + (areaHeight * ay + dy) * qrcode.width];
+ if (target < minmax[ax][ay][0])
+ minmax[ax][ay][0] = target;
+ if (target > minmax[ax][ay][1])
+ minmax[ax][ay][1] = target;
+ }
+ }
+ //minmax[ax][ay][0] = (minmax[ax][ay][0] + minmax[ax][ay][1]) / 2;
+ }
+ }
+ var middle = new Array(numSqrtArea);
+ for (var i3 = 0; i3 < numSqrtArea; i3++) {
+ middle[i3] = new Array(numSqrtArea);
+ }
+ for (var ay = 0; ay < numSqrtArea; ay++) {
+ for (var ax = 0; ax < numSqrtArea; ax++) {
+ middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2);
+ //Console.out.print(middle[ax][ay] + ",");
+ }
+ //Console.out.println("");
+ }
+ //Console.out.println("");
+
+ return middle;
+};
+
+qrcode.grayScaleToBitmap = function(grayScale) {
+ var middle = qrcode.getMiddleBrightnessPerArea(grayScale);
+ var sqrtNumArea = middle.length;
+ var areaWidth = Math.floor(qrcode.width / sqrtNumArea);
+ var areaHeight = Math.floor(qrcode.height / sqrtNumArea);
+ var bitmap = new Array(qrcode.height * qrcode.width);
+
+ for (var ay = 0; ay < sqrtNumArea; ay++) {
+ for (var ax = 0; ax < sqrtNumArea; ax++) {
+ for (var dy = 0; dy < areaHeight; dy++) {
+ for (var dx = 0; dx < areaWidth; dx++) {
+ bitmap[areaWidth * ax + dx + (areaHeight * ay + dy) * qrcode.width] = (grayScale[areaWidth * ax + dx + (areaHeight * ay + dy) * qrcode.width] < middle[ax][ay]) ? true : false;
+ }
+ }
+ }
+ }
+ return bitmap;
+};
+
+qrcode.grayscale = function() {
+ var ret = new Array(qrcode.width * qrcode.height);
+ for (var y = 0; y < qrcode.height; y++) {
+ for (var x = 0; x < qrcode.width; x++) {
+ var gray = qrcode.getPixel(x, y);
+
+ ret[x + y * qrcode.width] = gray;
+ }
+ }
+ return ret;
+};
+
+qrcode.orderBestPatterns = function(patterns) {
+
+ function distance(pattern1, pattern2) {
+ var xDiff = pattern1.X - pattern2.X;
+ var yDiff = pattern1.Y - pattern2.Y;
+ return Math.sqrt((xDiff * xDiff + yDiff * yDiff));
+ }
+
+ /// Returns the z component of the cross product between vectors BC and BA.
+ function crossProductZ(pointA, pointB, pointC) {
+ var bX = pointB.x;
+ var bY = pointB.y;
+ return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
+ }
+
+
+ // Find distances between pattern centers
+ var zeroOneDistance = distance(patterns[0], patterns[1]);
+ var oneTwoDistance = distance(patterns[1], patterns[2]);
+ var zeroTwoDistance = distance(patterns[0], patterns[2]);
+
+ var pointA, pointB, pointC;
+ // Assume one closest to other two is B; A and C will just be guesses at first
+ if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
+ pointB = patterns[0];
+ pointA = patterns[1];
+ pointC = patterns[2];
+ } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
+ pointB = patterns[1];
+ pointA = patterns[0];
+ pointC = patterns[2];
+ } else {
+ pointB = patterns[2];
+ pointA = patterns[0];
+ pointC = patterns[1];
+ }
+
+ // Use cross product to figure out whether A and C are correct or flipped.
+ // This asks whether BC x BA has a positive z component, which is the arrangement
+ // we want for A, B, C. If it's negative, then we've got it flipped around and
+ // should swap A and C.
+ if (crossProductZ(pointA, pointB, pointC) < 0.0) {
+ var temp = pointA;
+ pointA = pointC;
+ pointC = temp;
+ }
+
+ patterns[0] = pointA;
+ patterns[1] = pointB;
+ patterns[2] = pointC;
+};
+
+var URShift = function(number, bits) {
+ if (number >= 0)
+ return number >> bits;
+ else
+ return (number >> bits) + (2 << ~bits);
+};
+
+
+Array.prototype.remove = function(from, to) {
+ var rest = this.slice((to || from) + 1 || this.length);
+ this.length = from < 0 ? this.length + from : from;
+ return this.push.apply(this, rest);
+};
+
+exports.qrcode = qrcode;
+exports.URShift = URShift;
+exports.Array = Array;
diff --git a/app/src/scan-qrcode.js b/app/src/scan-qrcode.js
new file mode 100644
index 0000000..8bf7e89
--- /dev/null
+++ b/app/src/scan-qrcode.js
@@ -0,0 +1,86 @@
+const { desktopCapturer } = require('electron')
+var { qrcode } = require('./qrcode')
+
+/**
+ * Create a screenshot of the entire screen using the desktopCapturer module of Electron.
+ *
+ * @param callback {Function} callback receives as first parameter the base64 string of the image
+ * @param imageFormat {String} Format of the image to generate ('image/jpeg' or 'image/png')
+ **/
+export default function fullscreenScreenshot (callback, imageFormat = 'image/png') {
+ function handleStream (stream) {
+ // Create hidden video tag
+ var video = document.createElement('video')
+ video.style.cssText = 'position:absolute;top:-10000px;left:-10000px;'
+ // Event connected to stream
+ video.onloadedmetadata = function () {
+ // Set video ORIGINAL height (screenshot)
+ video.style.height = this.videoHeight + 'px' // videoHeight
+ video.style.width = this.videoWidth + 'px' // videoWidth
+
+ // Create canvas
+ var canvas = document.createElement('canvas')
+ canvas.width = this.videoWidth
+ canvas.height = this.videoHeight
+ var ctx = canvas.getContext('2d')
+ // Draw video on canvas
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
+
+ if (callback) {
+ // Save screenshot to base64
+ // var base64 = canvas.toDataURL(imageFormat)
+ qrcode.width = this.videoWidth
+ qrcode.height = this.videoHeight
+ qrcode.imagedata = ctx.getImageData(0, 0, qrcode.width, qrcode.height)
+ try {
+ var result = qrcode.process(ctx)
+ console.log('r:', result)
+ callback(null, result)
+ } catch (e) {
+ console.error(e)
+ callback(e)
+ }
+ } else {
+ console.log('Need callback!')
+ }
+
+ // Remove hidden video tag
+ video.remove()
+ try {
+ // Destroy connect to stream
+ stream.getTracks()[0].stop()
+ } catch (e) {}
+ }
+ video.src = window.URL.createObjectURL(stream)
+ document.body.appendChild(video)
+ }
+
+ function handleError (e) {
+ console.log(e)
+ }
+
+ // Filter only screen type
+ desktopCapturer.getSources({ types: ['screen'] }, (error, sources) => {
+ if (error) throw error
+ // console.log(sources);
+ for (let i = 0; i < sources.length; ++i) {
+ // Filter: main screen
+ if (sources[i].name === 'Entire screen') {
+ navigator.webkitGetUserMedia({
+ audio: false,
+ video: {
+ mandatory: {
+ chromeMediaSource: 'desktop',
+ chromeMediaSourceId: sources[i].id,
+ minWidth: 1280,
+ maxWidth: 4000,
+ minHeight: 720,
+ maxHeight: 4000
+ }
+ }
+ }, handleStream, handleError)
+ return
+ }
+ }
+ })
+}
diff --git a/app/src/util/index.js b/app/src/util/index.js
new file mode 100644
index 0000000..e20fb61
--- /dev/null
+++ b/app/src/util/index.js
@@ -0,0 +1,3 @@
+export function clone (obj) {
+ return JSON.parse(JSON.stringify(obj))
+}
diff --git a/app/storage.js b/app/storage.js
new file mode 100644
index 0000000..2a4ac09
--- /dev/null
+++ b/app/storage.js
@@ -0,0 +1,60 @@
+const path = require('path')
+const fs = require('fs')
+const fsExtra = require('fs-extra')
+
+const defaultConfig = { configs: [], selected: -1, autoLaunch: false, enable: false, pyPath: '',
+ methods: ['aes-128-cfb', 'aes-192-cfb', 'aes-256-cfb', 'aes-128-cfb8', 'aes-192-cfb8', 'aes-256-cfb8',
+ 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', 'camellia-128-cfb', 'camellia-192-cfb', 'camellia-256-cfb',
+ 'bf-cfb', 'rc4', 'rc4-md5', 'rc4-md5-6', 'salsa20', 'chacha20', 'chacha20-ietf'
+ ],
+ protocols: ['origin', 'verify_deflate', 'verify_sha1', 'auth_sha1_v2',
+ 'auth_sha1_v4', 'auth_aes128_md5', 'auth_aes128_sha1'
+ ],
+ obfses: ['plain', 'http_simple', 'http_post', 'ramdom_head', 'tls1.2_ticket_auth']
+}
+let currentConfig
+let dataPath
+let logPath
+let configPath = ''
+
+module.exports.setup = function (appConfigPath) {
+ try {
+ dataPath = appConfigPath
+ configPath = path.join(appConfigPath, 'shadowsocksr.json')
+ logPath = path.join(dataPath, 'logs/shadowsocksr-client.log')
+ console.log('Config file\'s path: ' + configPath)
+ // configs
+ fsExtra.ensureFileSync(configPath)
+ // logs
+ fsExtra.ensureDirSync(path.join(dataPath, 'logs'))
+ fsExtra.ensureFileSync(logPath)
+ } catch (e) {
+ console.error('Error occured:\n' + JSON.stringify(e, null, 2))
+ }
+}
+
+module.exports.getConfig = function () {
+ const content = fs.readFileSync(configPath, 'utf8')
+ if (content) {
+ currentConfig = JSON.parse(content)
+ } else {
+ currentConfig = defaultConfig
+ }
+ return currentConfig
+}
+
+module.exports.saveConfig = function () {
+ fsExtra.writeJSONSync(configPath, currentConfig)
+}
+
+module.exports.saveLogs = function (data) {
+ fs.appendFileSync(logPath, data, { flag: 'a+' })
+}
+
+module.exports.getLogPath = function () {
+ return logPath
+}
+
+module.exports.getConfigPath = function () {
+ return configPath
+}
diff --git a/app/tray.js b/app/tray.js
new file mode 100644
index 0000000..83aab7e
--- /dev/null
+++ b/app/tray.js
@@ -0,0 +1,89 @@
+const { Menu, Tray, nativeImage } = require('electron')
+const EventEmitter = require('events').EventEmitter
+const path = require('path')
+const os = require('os')
+
+let tray = null
+let menus = null
+let contextMenu = null
+const event = new EventEmitter()
+const osTrayIcon = os.platform() === 'darwin' ? 'tray_mac.png' : 'tray_win.png'
+const image = nativeImage.createFromPath(path.join(__dirname, './trayicons/' + osTrayIcon))
+
+function toggleEnable (e) {
+ event.emit('change-enable', e.checked)
+}
+
+function toggleAutoLaunch (e) {
+ event.emit('change-auto-launch', e.checked)
+}
+
+// generate submenu of configs
+function generateConfigSubmenus (configs, selectedIndex) {
+ const submenus = configs.map((config, index) => {
+ return {
+ label: `${config.remark}(${config.host}:${config.port})`,
+ type: 'checkbox',
+ checked: index === selectedIndex,
+ click (e) {
+ // set others to false
+ e.menu.items.forEach(submenu => {
+ submenu.checked = false
+ })
+ e.checked = true
+ event.emit('change-selected', e.menu.items.indexOf(e))
+ }
+ }
+ })
+ submenus.push({
+ label: '编辑服务器',
+ click () {
+ event.emit('open')
+ }
+ })
+ return submenus
+}
+
+module.exports = {
+ // init
+ setup (config) {
+ tray = new Tray(image)
+ tray.setToolTip('ShadowsocksR client')
+ menus = [
+ { label: '启用系统代理', type: 'checkbox', checked: config.enable, click: toggleEnable },
+ { label: '服务器', submenu: generateConfigSubmenus(config.configs, config.selected) },
+ { label: '开机自启', type: 'checkbox', checked: config.autoLaunch, click: toggleAutoLaunch },
+ { label: '二维码扫描', click: () => { event.emit('qr-scan') } },
+ { label: '查看日志', click: () => { event.emit('open-log') } },
+ { label: '打开配置文件', click: () => { event.emit('open-config') } },
+ { label: '退出', click: () => { event.emit('exit') } }
+ ]
+ contextMenu = Menu.buildFromTemplate(menus)
+ tray.setContextMenu(contextMenu)
+ tray.on('open', () => {
+ event.emit('open')
+ })
+ tray.on('click', () => {
+ event.emit('open')
+ })
+ return event
+ },
+ // refresh the configs submenu
+ refreshConfigs (configs, selectedIndex) {
+ menus[1].submenu = generateConfigSubmenus(configs, selectedIndex)
+ contextMenu = Menu.buildFromTemplate(menus)
+ tray.setContextMenu(contextMenu)
+ },
+ // get menu config
+ getMenuConfig () {
+ const selected = menus[1].submenu.filter(config => config.checked)
+ return {
+ enable: menus[0].checked,
+ autoLaunch: menus[2].checked,
+ selected: selected.length ? menus[1].submenu.indexOf(selected[0]) : -1
+ }
+ },
+ destroy () {
+ tray.destroy()
+ }
+}
diff --git a/app/trayicons/tray.png b/app/trayicons/tray.png
new file mode 100644
index 0000000..1201b89
Binary files /dev/null and b/app/trayicons/tray.png differ
diff --git a/app/trayicons/tray_mac.png b/app/trayicons/tray_mac.png
new file mode 100644
index 0000000..b69c6a5
Binary files /dev/null and b/app/trayicons/tray_mac.png differ
diff --git a/app/trayicons/tray_mac@2x.png b/app/trayicons/tray_mac@2x.png
new file mode 100644
index 0000000..e783313
Binary files /dev/null and b/app/trayicons/tray_mac@2x.png differ
diff --git a/app/trayicons/tray_mac@3x.png b/app/trayicons/tray_mac@3x.png
new file mode 100644
index 0000000..e783313
Binary files /dev/null and b/app/trayicons/tray_mac@3x.png differ
diff --git a/app/trayicons/tray_win.png b/app/trayicons/tray_win.png
new file mode 100644
index 0000000..2d7e833
Binary files /dev/null and b/app/trayicons/tray_win.png differ
diff --git a/app/trayicons/tray_win@2x.png b/app/trayicons/tray_win@2x.png
new file mode 100644
index 0000000..3dda23e
Binary files /dev/null and b/app/trayicons/tray_win@2x.png differ
diff --git a/app/trayicons/tray_win@3x.png b/app/trayicons/tray_win@3x.png
new file mode 100644
index 0000000..6cfd036
Binary files /dev/null and b/app/trayicons/tray_win@3x.png differ
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..3d97001
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,27 @@
+version: 1.0.{build}
+
+platform:
+ - x64
+
+cache:
+ - node_modules
+ - app\node_modules
+ - '%APPDATA%\npm-cache'
+ - '%USERPROFILE%\.electron'
+
+branches:
+ only:
+ - master
+
+init:
+ - git config --global core.autocrlf input
+
+install:
+ - ps: Install-Product node 6 x64
+ - git reset --hard HEAD
+ - npm install
+
+build_script:
+ - node --version
+ - npm --version
+ - npm run build
diff --git a/assets/images/ssr-client-tray.jpg b/assets/images/ssr-client-tray.jpg
new file mode 100644
index 0000000..f12896d
Binary files /dev/null and b/assets/images/ssr-client-tray.jpg differ
diff --git a/assets/images/ssr-client.jpg b/assets/images/ssr-client.jpg
new file mode 100644
index 0000000..e277c83
Binary files /dev/null and b/assets/images/ssr-client.jpg differ
diff --git a/assets/images/ssr-initialization.jpg b/assets/images/ssr-initialization.jpg
new file mode 100644
index 0000000..22403cf
Binary files /dev/null and b/assets/images/ssr-initialization.jpg differ
diff --git a/assets/send.svg b/assets/send.svg
new file mode 100644
index 0000000..bc46187
--- /dev/null
+++ b/assets/send.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/build/config.gypi b/build/config.gypi
new file mode 100644
index 0000000..62cf89d
--- /dev/null
+++ b/build/config.gypi
@@ -0,0 +1,61 @@
+# Do not edit. File was generated by node-gyp's "configure" step
+{
+ "target_defaults": {
+ "cflags": [],
+ "default_configuration": "Release",
+ "defines": [],
+ "include_dirs": [],
+ "libraries": []
+ },
+ "variables": {
+ "asan": 0,
+ "debug_devtools": "node",
+ "force_dynamic_crt": 0,
+ "host_arch": "x64",
+ "icu_data_file": "icudt57l.dat",
+ "icu_data_in": "..\\..\\deps/icu-small\\source/data/in\\icudt57l.dat",
+ "icu_endianness": "l",
+ "icu_gyp_path": "tools/icu/icu-generic.gyp",
+ "icu_locales": "en,root",
+ "icu_path": "deps/icu-small",
+ "icu_small": "true",
+ "icu_ver_major": "57",
+ "node_byteorder": "little",
+ "node_enable_d8": "false",
+ "node_enable_v8_vtunejit": "false",
+ "node_install_npm": "true",
+ "node_module_version": 48,
+ "node_no_browser_globals": "false",
+ "node_prefix": "/usr/local",
+ "node_release_urlbase": "https://nodejs.org/download/release/",
+ "node_shared": "false",
+ "node_shared_cares": "false",
+ "node_shared_http_parser": "false",
+ "node_shared_libuv": "false",
+ "node_shared_openssl": "false",
+ "node_shared_zlib": "false",
+ "node_tag": "",
+ "node_use_bundled_v8": "true",
+ "node_use_dtrace": "false",
+ "node_use_etw": "true",
+ "node_use_lttng": "false",
+ "node_use_openssl": "true",
+ "node_use_perfctr": "true",
+ "node_use_v8_platform": "true",
+ "openssl_fips": "",
+ "openssl_no_asm": 0,
+ "shlib_suffix": "so.48",
+ "target_arch": "x64",
+ "v8_enable_gdbjit": 0,
+ "v8_enable_i18n_support": 1,
+ "v8_inspector": "true",
+ "v8_no_strict_aliasing": 1,
+ "v8_optimized_debug": 0,
+ "v8_random_seed": 0,
+ "v8_use_snapshot": "true",
+ "want_separate_host_toolset": 0,
+ "nodedir": "C:\\Users\\t2cloud\\.node-gyp\\6.6.0",
+ "copy_dev_lib": "true",
+ "standalone_static_library": 1
+ }
+}
diff --git a/build/icon.icns b/build/icon.icns
new file mode 100644
index 0000000..dae15b0
Binary files /dev/null and b/build/icon.icns differ
diff --git a/build/icon.ico b/build/icon.ico
new file mode 100644
index 0000000..415b981
Binary files /dev/null and b/build/icon.ico differ
diff --git a/build/icons/128x128.png b/build/icons/128x128.png
new file mode 100644
index 0000000..2b2f178
Binary files /dev/null and b/build/icons/128x128.png differ
diff --git a/build/icons/16x16.png b/build/icons/16x16.png
new file mode 100644
index 0000000..e25622e
Binary files /dev/null and b/build/icons/16x16.png differ
diff --git a/build/icons/24x24.png b/build/icons/24x24.png
new file mode 100644
index 0000000..24ea3d1
Binary files /dev/null and b/build/icons/24x24.png differ
diff --git a/build/icons/256x256.png b/build/icons/256x256.png
new file mode 100644
index 0000000..1495fe2
Binary files /dev/null and b/build/icons/256x256.png differ
diff --git a/build/icons/32x32.png b/build/icons/32x32.png
new file mode 100644
index 0000000..2b1aa0e
Binary files /dev/null and b/build/icons/32x32.png differ
diff --git a/build/icons/48x48.png b/build/icons/48x48.png
new file mode 100644
index 0000000..91c259a
Binary files /dev/null and b/build/icons/48x48.png differ
diff --git a/build/icons/64x64.png b/build/icons/64x64.png
new file mode 100644
index 0000000..5e3ff26
Binary files /dev/null and b/build/icons/64x64.png differ
diff --git a/build/icons/96x96.png b/build/icons/96x96.png
new file mode 100644
index 0000000..83c492c
Binary files /dev/null and b/build/icons/96x96.png differ
diff --git a/builds/.gitkeep b/builds/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..20b8e2d
--- /dev/null
+++ b/config.js
@@ -0,0 +1,38 @@
+'use strict'
+
+const path = require('path')
+
+const config = {
+ // Name of electron app
+ // Will be used in production builds
+ name: 'ShadowsocksR客户端',
+
+ // Use ESLint (extends `standard`)
+ // Further changes can be made in `.eslintrc.js`
+ eslint: true,
+
+ // webpack-dev-server port
+ port: 9080,
+
+ // electron-packager options
+ // Docs: https://simulatedgreg.gitbooks.io/electron-vue/content/docs/building_your_app.html
+ building: {
+ arch: 'x64',
+ asar: false,
+ dir: path.join(__dirname, 'app'),
+ icon: path.join(__dirname, 'app/icons/icon'),
+ ignore: [
+ /\bnode_modules[\\\/]*\.bin\b/,
+ /\bnode_modules[\\\/]*vue/,
+ /\bsrc\b/,
+ /\bindex\.ejs\b/
+ ],
+ out: path.join(__dirname, 'builds'),
+ overwrite: true,
+ platform: process.env.PLATFORM_TARGET || 'all'
+ }
+}
+
+config.building.name = config.name
+
+module.exports = config
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..95237b2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "electron-ssr",
+ "version": "0.1.2",
+ "description": "Shadowsocksr client using electron",
+ "scripts": {
+ "dev": "node tasks/runner.js",
+ "build:clean": "cross-env PLATFORM_TARGET=clean node tasks/release.js",
+ "build": "node tasks/release.js",
+ "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter app",
+ "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix app",
+ "pack": "cross-env NODE_ENV=production webpack -p --progress --colors",
+ "postinstall": "npm run lint:fix && cd app && npm install",
+ "test:unit": "./node_modules/.bin/mocha tests/unit/qrcode.spec.js"
+ },
+ "author": "erguotou525@gmail.com",
+ "license": "MIT",
+ "devDependencies": {
+ "babel-core": "^6.8.0",
+ "babel-eslint": "^7.0.0",
+ "babel-loader": "^6.2.4",
+ "babel-plugin-transform-runtime": "^6.8.0",
+ "babel-preset-es2015": "^6.6.0",
+ "babel-preset-stage-0": "^6.5.0",
+ "babel-runtime": "^6.6.1",
+ "chai": "^3.5.0",
+ "cross-env": "^1.0.7",
+ "css-loader": "^0.23.1",
+ "del": "^2.2.1",
+ "delete-empty": "^0.1.3",
+ "devtron": "^1.1.0",
+ "electron": "^1.6.0",
+ "electron-builder": "^10.17.3",
+ "electron-devtools-installer": "^1.1.4",
+ "eslint": "^2.10.2",
+ "eslint-config-vue": "^1.1.0",
+ "eslint-friendly-formatter": "^2.0.5",
+ "eslint-loader": "^1.3.0",
+ "eslint-plugin-html": "^1.3.0",
+ "eslint-plugin-promise": "^1.0.8",
+ "extract-text-webpack-plugin": "^1.0.1",
+ "file-loader": "^0.8.5",
+ "html-webpack-plugin": "^2.16.1",
+ "json-loader": "^0.5.4",
+ "mocha": "^3.2.0",
+ "style-loader": "^0.13.1",
+ "stylus": "^0.54.5",
+ "stylus-loader": "^2.4.0",
+ "tree-kill": "^1.1.0",
+ "url-loader": "^0.5.7",
+ "vue-hot-reload-api": "^1.3.2",
+ "vue-html-loader": "^1.2.2",
+ "vue-loader": "^9.5.1",
+ "vue-style-loader": "^1.0.0",
+ "webpack": "^1.13.0",
+ "webpack-dev-server": "^1.14.1"
+ },
+ "dependencies": {}
+}
diff --git a/tasks/release.js b/tasks/release.js
new file mode 100644
index 0000000..6490a15
--- /dev/null
+++ b/tasks/release.js
@@ -0,0 +1,103 @@
+'use strict'
+
+const exec = require('child_process').exec
+const builder = require('electron-builder')
+const path = require('path')
+const Platform = builder.Platform
+const platform = require('os').platform()
+const config = require('../config')
+
+const YELLOW = '\x1b[33m'
+const BLUE = '\x1b[34m'
+const END = '\x1b[0m'
+
+if (process.env.PLATFORM_TARGET === 'clean') {
+ require('del').sync(['builds/*', '!.gitkeep'])
+ console.log(`${YELLOW}\`builds\` directory cleaned.\n${END}`)
+} else pack()
+
+/**
+ * Build webpack in production
+ */
+function pack () {
+ if (!/darwin|win32|linux/.test(platform)) {
+ return console.error('Not support current platform:' + platform)
+ }
+ console.log(`${YELLOW}Building webpack in production mode...\n${END}`)
+ const pack = exec('npm run pack')
+
+ pack.stdout.on('data', data => console.log(data))
+ pack.stderr.on('data', data => console.error(data))
+ pack.on('exit', code => build())
+}
+
+/**
+ * Use electron-packager to build electron app
+ */
+function build () {
+ let targets
+ switch (platform) {
+ case 'darwin':
+ targets = Platform.MAC.createTarget()
+ break
+ case 'win32':
+ targets = Platform.WINDOWS.createTarget()
+ break
+ case 'linux':
+ targets = Platform.LINUX.createTarget()
+ }
+ console.log('\x1b[34mBuilding electron app(s)...\n\x1b[0m')
+ builder.build({
+ targets: targets,
+ appMetadata: {
+ homepage: 'https://github.com/erguotou520/electron-ssr'
+ },
+ devMetadata: {
+ build: {
+ appId: 'me.erguotou',
+ files: [
+ '**/*',
+ '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme,test,__tests__,tests,powered-test,example,examples,*.d.ts}',
+ '!**/node_modules/.bin',
+ '!**/*.{o,hprof,orig,pyc,pyo,rbc}',
+ '**/._*',
+ '!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,thumbs.db,.gitignore,.gitattributes,.editorconfig,.flowconfig,.yarn-metadata.json,.idea,appveyor.yml,.travis.yml,circle.yml,npm-debug.log,.nyc_output,yarn.lock,.yarn-integrity}',
+ '!node_modules/vue*/**/*',
+ '!src/**/*'
+ ],
+ directories: {
+ app: 'app',
+ output: 'builds'
+ },
+ compression: 'maximum',
+ productName: config.name,
+ win: {
+ target: ['nsis', 'zip']
+ },
+ nsis: {
+ license: path.join(__dirname, '../LICENSE')
+ },
+ mac: {
+ category: 'public.app-category.developer-tools',
+ target: ['dmg', 'zip'],
+ extendInfo: {
+ LSUIElement: 'YES'
+ }
+ },
+ linux: {
+ category: 'Development',
+ target: ['rpm', 'tar.gz']
+ },
+ publish: [{
+ provider: 'github'
+ }]
+ }
+ }
+ })
+ .then(() => {
+ console.log(`${BLUE}Done${END}`)
+ })
+ .catch(error => {
+ console.error(`${YELLOW}Build error: ${error}${END}`)
+ })
+}
diff --git a/tasks/runner.js b/tasks/runner.js
new file mode 100644
index 0000000..069cdeb
--- /dev/null
+++ b/tasks/runner.js
@@ -0,0 +1,61 @@
+/**
+ * Credits to https://github.com/bradstewart/electron-boilerplate-vue/blob/master/build/dev-runner.js
+ */
+'use strict'
+
+const config = require('../config')
+const exec = require('child_process').exec
+const treeKill = require('tree-kill')
+
+const YELLOW = '\x1b[33m'
+const BLUE = '\x1b[34m'
+const END = '\x1b[0m'
+
+let isElectronOpen = false
+
+function format (command, data, color) {
+ return color + command + END +
+ ' ' + // Two space offset
+ data.toString().trim().replace(/\n/g, '\n' + repeat(' ', command.length + 2)) +
+ '\n'
+}
+
+function repeat (str, times) {
+ return (new Array(times + 1)).join(str)
+}
+
+const children = []
+
+function run (command, color, name) {
+ const child = exec(command)
+
+ child.stdout.on('data', data => {
+ console.log(format(name, data, color))
+
+ /**
+ * Start electron after VALID build
+ * (prevents electron from opening a blank window that requires refreshing)
+ *
+ * NOTE: needs more testing for stability
+ */
+ if (/Compiled successfully/g.test(data.toString().trim().replace(/\n/g, '\n' + repeat(' ', command.length + 2))) && !isElectronOpen) {
+ console.log(`${BLUE}Starting electron...\n${END}`)
+ run('cross-env NODE_ENV=development electron app/electron.js', BLUE, 'electron')
+ isElectronOpen = true
+ }
+ })
+
+ child.stderr.on('data', data => console.error(format(name, data, color)))
+ child.on('exit', code => exit(code))
+
+ children.push(child)
+}
+
+function exit (code) {
+ children.forEach(child => {
+ treeKill(child.pid)
+ })
+}
+
+console.log(`${YELLOW}Starting webpack-dev-server...\n${END}`)
+run(`webpack-dev-server --inline --hot --colors --port ${config.port} --content-base app/dist`, YELLOW, 'webpack')
diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js
new file mode 100644
index 0000000..d6d2805
--- /dev/null
+++ b/tests/.eslintrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+ globals: {
+ 'it': true,
+ 'describe': true
+ }
+}
diff --git a/tests/unit/qrcode.spec.js b/tests/unit/qrcode.spec.js
new file mode 100644
index 0000000..0f5810e
--- /dev/null
+++ b/tests/unit/qrcode.spec.js
@@ -0,0 +1,91 @@
+var expect = require('chai').expect
+var Config = require('../../app/src/Config')
+
+var strWithRemark = 'ssr://MTI3LjAuMC4xOjEyMzQ6YXV0aF9hZXMxMjhfbWQ1OmFlcy0xMjgtY2ZiOnRsczEuMl90aWNrZXRfYXV0aDpZV0ZoWW1KaS8_b2Jmc3BhcmFtPVluSmxZV3QzWVRFeExtMXZaUSZyZW1hcmtzPTVyV0w2Sy1WNUxpdDVwYUg'
+var strNoRemark = 'ssr://MTI3LjAuMC4xOjEyMzQ6YXV0aF9hZXMxMjhfbWQ1OmFlcy0xMjgtY2ZiOnRsczEuMl90aWNrZXRfYXV0aDpZV0ZoWW1KaS8_b2Jmc3BhcmFtPVluSmxZV3QzWVRFeExtMXZaUQ'
+var ssStr = 'ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4'
+
+function clone (obj) {
+ var r = {}
+ for (var key in obj) {
+ r[key] = obj[key]
+ }
+ return r
+}
+
+describe('Config', function () {
+ it('ssr-qrcode', function () {
+ var base = {
+ host: '127.0.0.1',
+ port: '1234',
+ password: 'aaabbb',
+ method: 'aes-128-cfb',
+ protocol: 'auth_aes128_md5',
+ obfs: 'tls1.2_ticket_auth',
+ obfsparam: 'breakwa11.moe'
+ }
+ var withRemark = clone(base)
+ withRemark.remark = '测试中文'
+ expect(new Config(base).getSSRLink()).to.equal(strNoRemark)
+ expect(new Config(withRemark).getSSRLink()).to.equal(strWithRemark)
+ })
+
+ it('ssr-decode', function () {
+ var base = new Config()
+ base.setSSRLink(strNoRemark)
+ expect(base.host).to.equal('127.0.0.1')
+ expect(base.port).to.equal('1234')
+ expect(base.password).to.equal('aaabbb')
+ expect(base.method).to.equal('aes-128-cfb')
+ expect(base.protocol).to.equal('auth_aes128_md5')
+ expect(base.obfs).to.equal('tls1.2_ticket_auth')
+ expect(base.obfsparam).to.equal('breakwa11.moe')
+
+ var withRemark = new Config()
+ withRemark.setSSRLink(strWithRemark)
+ expect(withRemark.host).to.equal('127.0.0.1')
+ expect(withRemark.port).to.equal('1234')
+ expect(withRemark.password).to.equal('aaabbb')
+ expect(withRemark.method).to.equal('aes-128-cfb')
+ expect(withRemark.protocol).to.equal('auth_aes128_md5')
+ expect(withRemark.obfs).to.equal('tls1.2_ticket_auth')
+ expect(withRemark.obfsparam).to.equal('breakwa11.moe')
+ expect(withRemark.remark).to.equal('测试中文')
+ })
+
+ it('ss-qrcode', function () {
+ var base = {
+ host: '192.168.100.1',
+ port: '8888',
+ password: 'test',
+ method: 'bf-cfb'
+ }
+ var withRemark = clone(base)
+ withRemark.remark = 'example-server'
+ expect(new Config(base).getSSLink()).to.equal(ssStr)
+ expect(new Config(withRemark).getSSLink()).to.equal(ssStr + '#example-server')
+ })
+
+ it('ss-decode', function () {
+ var base = new Config()
+ base.setSSLink(ssStr)
+ expect(base.host).to.equal('192.168.100.1')
+ expect(base.port).to.equal('8888')
+ expect(base.password).to.equal('test')
+ expect(base.method).to.equal('bf-cfb')
+ expect(base.protocol).to.equal('origin')
+ expect(base.obfs).to.equal('plain')
+ expect(base.obfsparam).to.equal('')
+
+ var withRemark = new Config()
+ withRemark.setSSLink(ssStr + '#example-server')
+ expect(base.host).to.equal('192.168.100.1')
+ expect(base.port).to.equal('8888')
+ expect(base.password).to.equal('test')
+ expect(base.method).to.equal('bf-cfb')
+ expect(base.protocol).to.equal('origin')
+ expect(base.obfs).to.equal('plain')
+ expect(base.obfsparam).to.equal('')
+ expect(withRemark.remark).to.equal('example-server')
+ })
+})
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..82e794b
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,131 @@
+'use strict'
+
+const path = require('path')
+// const pkg = require('./app/package.json')
+const settings = require('./config.js')
+const webpack = require('webpack')
+
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+
+let config = {
+ devtool: '#eval-source-map',
+ eslint: {
+ formatter: require('eslint-friendly-formatter')
+ },
+ entry: {
+ build: path.join(__dirname, 'app/src/main.js')
+ },
+ module: {
+ preLoaders: [],
+ loaders: [
+ {
+ test: /\.css$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+ },
+ {
+ test: /\.html$/,
+ loader: 'vue-html-loader'
+ },
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ exclude: /node_modules/
+ },
+ {
+ test: /\.json$/,
+ loader: 'json-loader'
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader'
+ },
+ {
+ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+ loader: 'url-loader',
+ query: {
+ limit: 10000,
+ name: 'imgs/[name].[hash:7].[ext]'
+ }
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+ loader: 'url-loader',
+ query: {
+ limit: 10000,
+ name: 'fonts/[name].[hash:7].[ext]'
+ }
+ }
+ ]
+ },
+ plugins: [
+ new ExtractTextPlugin('styles.css'),
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ template: './app/index.ejs',
+ title: settings.name
+ }),
+ new webpack.NoErrorsPlugin()
+ ],
+ output: {
+ filename: '[name].js',
+ path: path.join(__dirname, 'app/dist')
+ },
+ resolve: {
+ alias: {
+ 'components': path.join(__dirname, 'app/src/components'),
+ 'src': path.join(__dirname, 'app/src')
+ },
+ extensions: ['', '.js', '.vue', '.json', '.css'],
+ fallback: [path.join(__dirname, 'app/node_modules')]
+ },
+ resolveLoader: {
+ root: path.join(__dirname, 'node_modules')
+ },
+ target: 'electron-renderer',
+ vue: {
+ loaders: {
+ stylus: 'vue-style-loader!css-loader!stylus-loader'
+ }
+ }
+}
+
+if (process.env.NODE_ENV !== 'production') {
+ /**
+ * Apply ESLint
+ */
+ if (settings.eslint) {
+ config.module.preLoaders.push(
+ {
+ test: /\.js$/,
+ loader: 'eslint-loader',
+ exclude: /node_modules/
+ },
+ {
+ test: /\.vue$/,
+ loader: 'eslint-loader'
+ }
+ )
+ }
+}
+
+/**
+ * Adjust config for production settings
+ */
+if (process.env.NODE_ENV === 'production') {
+ config.devtool = ''
+
+ config.plugins.push(
+ new webpack.DefinePlugin({
+ 'process.env.NODE_ENV': '"production"'
+ }),
+ new webpack.optimize.OccurenceOrderPlugin(),
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ }
+ })
+ )
+}
+
+module.exports = config