diff --git a/.vscode/launch.json b/.vscode/launch.json index 370616e6..ea8866b5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,151 +5,172 @@ "type": "node", "request": "launch", "name": "Unit Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/**/*.spec.ts", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/**/*.spec.ts" ] }, { "type": "node", "request": "launch", - "name": "Unit Tests - ts-node", + "name": "Code 39 Tests", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "--require", "ts-node/register", + "--require", + "tsconfig-paths/register", "-u", "tdd", "--timeout", "999999", "--colors", "--recursive", - "./src/test/**/*.spec.ts" - ], - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "node", - "request": "launch", - "name": "Code 39 Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", - "args": [ "./src/test/core/oned/Code39*.spec.ts", - "--colors", - "--timeout", - "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" ] }, { "type": "node", "request": "launch", "name": "EAN 13 Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/oned/Ean13*.spec.ts", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/test/core/oned/Ean13*.spec.ts", ] }, { "type": "node", "request": "launch", "name": "PDF417 Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/pdf417/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/test/core/pdf417/**/*.spec.ts" ] }, { "type": "node", "request": "launch", "name": "QR Code Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/qrcode/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" + "--colors", + "--recursive", + "./src/test/core/qrcode/**/*.spec.ts" ] }, { "type": "node", "request": "launch", "name": "Data Matrix Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/datamatrix/", - "--recursive", - "--colors", + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" - ] + "--colors", + "--recursive", + "./src/test/core/datamatrix/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" }, { "type": "node", "request": "launch", "name": "Aztec 2D Tests", - "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ - "./src/test/core/aztec/", - "--recursive", + "--require", + "ts-node/register", + "-u", + "tdd", + "--timeout", + "999999", "--colors", + "--recursive", + "./src/test/core/aztec/**/*.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "StringBuilder tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--require", + "ts-node/register", + "--require", + "tsconfig-paths/register", + "-u", + "tdd", "--timeout", "999999", - "--webpack-env", - "dbg", - "--webpack-config", - "webpack.config.test.js" - ] + "--colors", + "--recursive", + "./src/test/core/util/StringBuilder.spec.ts" + ], + "internalConsoleOptions": "openOnSessionStart" }, { "type": "node", "request": "launch", - "name": "Aztec 2D Tests - ts-node", + "name": "Result tests", "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", "args": [ "--require", "ts-node/register", + "--require", + "tsconfig-paths/register", "-u", "tdd", "--timeout", "999999", "--colors", "--recursive", - "./src/test/core/aztec/**/*.spec.ts" + "./src/test/core/Result.spec.ts" ], "internalConsoleOptions": "openOnSessionStart" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91b0d15d..c2aaae77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Initial port from 3.3.1-SNAPSHOT on May 2017 by Adrian Toșcă (@aleris). ### Approach -The Java files are transformed using regexps for some obvious syntax transformation (see ./autotransform) and then modified manually. +The Java files are transformed using RegExps for some obvious syntax transformation, see `` for a starting point. Using http://www.jsweet.org was considered but rejected because of loosing type information early on (for example number versus int is essential for bitwise operations), language style and older TypeScript version. @@ -50,6 +50,84 @@ number versus int is essential for bitwise operations), language style and older | `byte[]` | `Uint8ClampedArray` | | `int[]` | `Int32Array` | +### Java numbers to TS numbers + +- Take care of `int` -> `number` (integer to number) port when doing bitwise transformation especially `<<`. Do a `& 0xFFFFFFFF` for ints, a &0xFF for bytes. +- Take care of array initialization, in Java `new Array(N)` initializes capacity NOT size/length. +- Use `Math.floor` for any division of `int`s otherwise the `number` type is a floating point and keeps the numbers after the dot. +- For `float`/`number` to `int` casting use `Math.trunc`, to replicate the same effect as Java casting does. + +### Porting overloads + +> [but don't rewrite JavaScript to be Java](https://github.com/zxing-js/library/pull/376#commitcomment-44928885) + +Strong words and you should agree, so we're in favor of mixing implementations using a prefered order: + +1. Don't implement overloading if not needed but document it. +2. Missing argument handling and then calling a (one) implementation method (this is easily preferable than 3 bellow). +3. Missing argument handling and calling vastly (multiple) different implementations as the arguments matches. + +All this in favor of keeping the interfaces similar to Java and the code as close as possible for porting and debugging. Both should be very well commented in the code so they explain why they're there and what they're doing. + +> [Most of the contributors to this library will most likely have a JavaScript background rather than Java.](https://github.com/zxing-js/library/pull/376#commitcomment-44928885) + +Yeah but most will have to have a very good understanding of both languages so they can port the `core` and porting is terrible hard when code doesn't matches. For new modules **not based** in the Java version we're **against** the use of overloading pattern, JavaScript simply doesn't fits it well and should be avoided in here. + +> You can find more on this discussion in [this Pull Request](https://github.com/zxing-js/library/pull/376). + +#### Examples + +Based on the rules set above, this is where we land, first with a simpler yet effective approach: + +```typescript +constructor(arg1: any); +constructor(arg1: any, arg2: any); +constructor(arg1: any, arg2: any, arg3: any); +constructor(arg1: any, arg2?: any, arg3?: any) { + if (arg2 == null) arg2 = {}; + if (arg3 == null) arg3 = {}; + return constructorImpl(arg1, arg2, arg3) +} + +constructorImpl(arg1: any, arg2: any, arg3: any) { + /* Implementation code */ +} +``` + +And less preferred if more advanced logic needed: + +```typescript +constructor(arg1: any); +constructor(arg1: any, arg2: any); +constructor(arg1: any, arg2: any, arg3: any); +constructor(arg1: any, arg2?: any, arg3?: any) { + if (arg3 != null) return constructorImpl(arg1, arg2, arg3); + if (arg2 != null) return constructorOverload2(arg1, arg2); + return constructorOverload1(arg1) +} + +private constructorOverload1( + arg1: any, +) { + return this.constructorOverload2(arg1, {}); +} + +private constructorOverload2( + arg1: any, + arg2: any, +) { + return this.constructorImpl(arg1, arg2, {}); +} + +private constructorImpl( + arg1: any, + arg2: any, + arg3: any, +) { + /* Implementation code */ +} +``` + ## Types ### Java types @@ -62,17 +140,12 @@ https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html - `int` has 32 bits, signed, so `int[]` transforms to `Int32Array`. - `char` has 2 bytes, so `char[]` transforms to `Uint16Array`. - `long` has 64 bit two's complement `integer`, can be signed or unsigned. +- `float[]` can be ported to `Float32Array`. +- `double[]` can be ported to `Float64Array`. ### JavaScript's TypedArray -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray - -## Things to look for - -- Take care of `int` -> `number` (integer to number) port when doing bitwise transformation especially `<<`. Do a `& 0xFFFFFFFF` for ints, a &0xFF for bytes. -- Take care of array initialization, in Java `new Array(N)` initializes capacity NOT size/length. -- Use `Math.floor` for any division of ints otherwise the `number` type is a floating point and keeps the numbers after the dot. -- For `float` to `int` casting use `Math.trunc`, to replicate the same effect as Java casting does. +Read about JavaScript TypedArray [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray). ## Encoding @@ -89,8 +162,8 @@ Will became: `StringEncoding.decode(, encoding)`. - `common/AbstractBlackBoxTestCase.java` - `Cp437` not supported by TextEncoding library see `DecodedBitStreamParserTestCase`. - Replace `instanceof` with something more robust. -- Simplify double `null !== && undefined !== ` checks. +- Simplify double ` !== null && !== undefined` checks. ----- +--- Most of things here are opinions and were written by the first porter, please feel free to discuss and help us to make it better. diff --git a/package.json b/package.json index b0373011..b3272a4e 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "nyc": "^15.1.0", "rollup": "^2.8.2", "seedrandom": "^2.4.4", - "sharp": "^0.22.1", + "sharp": "^0.26.3", "shx": "0.3.2", "sinon": "^7.2.7", "terser": "^5.3.7", diff --git a/src/core/MultiFormatReader.ts b/src/core/MultiFormatReader.ts index 3b0c1de4..b91ca7e7 100644 --- a/src/core/MultiFormatReader.ts +++ b/src/core/MultiFormatReader.ts @@ -54,10 +54,7 @@ export default class MultiFormatReader implements Reader { * @throws NotFoundException Any errors which occurred */ /*@Override*/ - // public decode(image: BinaryBitmap): Result { - // setHints(null) - // return decodeInternal(image) - // } + public decode(image: BinaryBitmap): Result; /** * Decode an image using the hints provided. Does not honor existing state. diff --git a/src/core/Reader.ts b/src/core/Reader.ts index 0757759d..bef8bfab 100644 --- a/src/core/Reader.ts +++ b/src/core/Reader.ts @@ -46,8 +46,9 @@ interface Reader { * @throws NotFoundException if no potential barcode is found * @throws ChecksumException if a potential barcode is found but does not pass its checksum * @throws FormatException if a potential barcode is found but format is invalid + * @override decode */ - // decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException*/ + decode(image: BinaryBitmap): Result; /** * Locates and decodes a barcode in some format within an image. This method also accepts diff --git a/src/core/Result.ts b/src/core/Result.ts index 3af8b8e2..f05c3ea7 100644 --- a/src/core/Result.ts +++ b/src/core/Result.ts @@ -22,6 +22,8 @@ import ResultPoint from './ResultPoint'; import BarcodeFormat from './BarcodeFormat'; import System from './util/System'; import ResultMetadataType from './ResultMetadataType'; +import { long } from '../customTypings'; +import { isBarcodeFormatValue } from './util/BarcodeFormaHelpers'; /** *

Encapsulates the result of decoding a barcode within an image.

@@ -31,44 +33,107 @@ import ResultMetadataType from './ResultMetadataType'; export default class Result { private resultMetadata: Map; - - // public constructor(private text: string, - // Uint8Array rawBytes, - // ResultPoconst resultPoints: Int32Array, - // BarcodeFormat format) { - // this(text, rawBytes, resultPoints, format, System.currentTimeMillis()) - // } - - // public constructor(text: string, - // Uint8Array rawBytes, - // ResultPoconst resultPoints: Int32Array, - // BarcodeFormat format, - // long timestamp) { - // this(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, - // resultPoints, format, timestamp) - // } - - public constructor(private text: string, + private numBits: number; + private resultPoints: ResultPoint[]; + private format: BarcodeFormat; + + public constructor( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + ); + public constructor( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: long, + ); + public constructor( + text: string, + rawBytes: Uint8Array, + numBits: number, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: number + ); + public constructor( + private text: string, private rawBytes: Uint8Array, - private numBits: number /*int*/ = rawBytes == null ? 0 : 8 * rawBytes.length, - private resultPoints: ResultPoint[], - private format: BarcodeFormat, - private timestamp: number /*long*/ = System.currentTimeMillis()) { - this.text = text; - this.rawBytes = rawBytes; - if (undefined === numBits || null === numBits) { - this.numBits = (rawBytes === null || rawBytes === undefined) ? 0 : 8 * rawBytes.length; - } else { - this.numBits = numBits; - } - this.resultPoints = resultPoints; - this.format = format; - this.resultMetadata = null; - if (undefined === timestamp || null === timestamp) { - this.timestamp = System.currentTimeMillis(); - } else { - this.timestamp = timestamp; - } + numBits_resultPoints: number | ResultPoint[], + resultPoints_format: ResultPoint[] | BarcodeFormat | any, + format_timestamp: BarcodeFormat | long | any = null, + private timestamp: long = System.currentTimeMillis() + ) { + // checks overloading order from most to least params + + // check overload 3 + if (numBits_resultPoints instanceof Number && Array.isArray(resultPoints_format) && isBarcodeFormatValue(format_timestamp)) { + numBits_resultPoints = rawBytes == null ? 0 : 8 * rawBytes.length; + this.constructorImpl(text, rawBytes, numBits_resultPoints, resultPoints_format, format_timestamp, timestamp); + return; + } + + // check overload 2 + if (Array.isArray(resultPoints_format) && isBarcodeFormatValue(format_timestamp)) { + this.constructorOverload2(text, rawBytes, resultPoints_format, format_timestamp, timestamp); + return; + } + + // check overload 1 + if (typeof text === 'string' && rawBytes instanceof Uint8Array && Array.isArray(numBits_resultPoints) && isBarcodeFormatValue(resultPoints_format)) { + this.constructorOverload1(text, rawBytes, numBits_resultPoints, resultPoints_format); + return; + } + + // throw no supported overload exception + throw new Error('No supported overload for the given combination of parameters.'); + } + + private constructorOverload1( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + ) { + return this.constructorOverload2(text, rawBytes, resultPoints, format, System.currentTimeMillis()); + } + + private constructorOverload2( + text: string, + rawBytes: Uint8Array, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: number /* long */, + ) { + return this.constructorImpl(text, rawBytes, rawBytes == null ? 0 : 8 * rawBytes.length, + resultPoints, format, timestamp); + } + + private constructorImpl( + text: string, + rawBytes: Uint8Array, + numBits: number, + resultPoints: ResultPoint[], + format: BarcodeFormat, + timestamp: number + ) { + this.text = text; + this.rawBytes = rawBytes; + if (undefined === numBits || null === numBits) { + this.numBits = (rawBytes === null || rawBytes === undefined) ? 0 : 8 * rawBytes.length; + } else { + this.numBits = numBits; + } + this.resultPoints = resultPoints; + this.format = format; + this.resultMetadata = null; + if (undefined === timestamp || null === timestamp) { + this.timestamp = System.currentTimeMillis(); + } else { + this.timestamp = timestamp; + } } /** diff --git a/src/core/multi/MultipleBarcodeReader.ts b/src/core/multi/MultipleBarcodeReader.ts index b8d7c4e6..b00811ef 100644 --- a/src/core/multi/MultipleBarcodeReader.ts +++ b/src/core/multi/MultipleBarcodeReader.ts @@ -36,6 +36,7 @@ export default /*public*/ interface MultipleBarcodeReader { /** * @throws NotFoundException + * @override decodeMultiple */ decodeMultiple(image: BinaryBitmap): Result[]; diff --git a/src/core/multi/qrcode/QRCodeMultiReader.ts b/src/core/multi/qrcode/QRCodeMultiReader.ts new file mode 100644 index 00000000..0cf9b81b --- /dev/null +++ b/src/core/multi/qrcode/QRCodeMultiReader.ts @@ -0,0 +1,193 @@ +/* + * Copyright 2009 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. + */ + +import { int, List } from 'src/customTypings'; +import BarcodeFormat from '../../BarcodeFormat'; +import BinaryBitmap from '../../BinaryBitmap'; +import DecoderResult from '../../common/DecoderResult'; +import DetectorResult from '../../common/DetectorResult'; +import DecodeHintType from '../../DecodeHintType'; +import QRCodeDecoderMetaData from '../../qrcode/decoder/QRCodeDecoderMetaData'; +import QRCodeReader from '../../qrcode/QRCodeReader'; +import ReaderException from '../../ReaderException'; +import Result from '../../Result'; +import ResultMetadataType from '../../ResultMetadataType'; +import ResultPoint from '../../ResultPoint'; +import ByteArrayOutputStream from '../../util/ByteArrayOutputStream'; +import Collections from '../../util/Collections'; +import Comparator from '../../util/Comparator'; +import Integer from '../../util/Integer'; +import StringBuilder from '../../util/StringBuilder'; +import MultipleBarcodeReader from '../MultipleBarcodeReader'; +import MultiDetector from './detector/MultiDetector'; + +// package com.google.zxing.multi.qrcode; + +// import com.google.zxing.BarcodeFormat; +// import com.google.zxing.BinaryBitmap; +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ReaderException; +// import com.google.zxing.Result; +// import com.google.zxing.ResultMetadataType; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.common.DecoderResult; +// import com.google.zxing.common.DetectorResult; +// import com.google.zxing.multi.MultipleBarcodeReader; +// import com.google.zxing.multi.qrcode.detector.MultiDetector; +// import com.google.zxing.qrcode.QRCodeReader; +// import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData; + +// import java.io.ByteArrayOutputStream; +// import java.io.Serializable; +// import java.util.ArrayList; +// import java.util.List; +// import java.util.Map; +// import java.util.Collections; +// import java.util.Comparator; + +/** + * This implementation can detect and decode multiple QR Codes in an image. + * + * @author Sean Owen + * @author Hannes Erven + */ +export default /*public final*/ class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader { + + private static /* final */ EMPTY_RESULT_ARRAY: Result[] = []; + protected static /* final */ NO_POINTS = new Array(); + + /** + * TYPESCRIPTPORT: this is an overloaded method so here it'll work only as a entrypoint for choosing which overload to call. + */ + public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { + + if (hints && hints instanceof Map) { + return this.decodeMultipleImpl(image, hints); + } + + return this.decodeMultipleOverload1(image); + } + + /** + * @throws NotFoundException + * @override decodeMultiple + */ + private decodeMultipleOverload1(image: BinaryBitmap): Result[] { + return this.decodeMultipleImpl(image, null); + } + + /** + * @override + * @throws NotFoundException + */ + private decodeMultipleImpl(image: BinaryBitmap, hints: Map): Result[] { + let results: List = []; + const detectorResults: DetectorResult[] = new MultiDetector(image.getBlackMatrix()).detectMulti(hints); + for (const detectorResult of detectorResults) { + try { + const decoderResult: DecoderResult = this.getDecoder().decodeBitMatrix(detectorResult.getBits(), hints); + const points: ResultPoint[] = detectorResult.getPoints(); + // If the code was mirrored: swap the bottom-left and the top-right points. + if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) { + ( decoderResult.getOther()).applyMirroredCorrection(points); + } + const result: Result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, + BarcodeFormat.QR_CODE); + const byteSegments: List = decoderResult.getByteSegments(); + if (byteSegments != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments); + } + const ecLevel: string = decoderResult.getECLevel(); + if (ecLevel != null) { + result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); + } + if (decoderResult.hasStructuredAppend()) { + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, + decoderResult.getStructuredAppendSequenceNumber()); + result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY, + decoderResult.getStructuredAppendParity()); + } + results.push(result); + } catch (re) { + if (re instanceof ReaderException) { + // ignore and continue + } else { + throw re; + } + } + } + if (results.length === 0) { + return QRCodeMultiReader.EMPTY_RESULT_ARRAY; + } else { + results = QRCodeMultiReader.processStructuredAppend(results); + return results/* .toArray(QRCodeMultiReader.EMPTY_RESULT_ARRAY) */; + } + } + + static processStructuredAppend( results: List): List { + const newResults: List = []; + const saResults: List = []; + for (const result of results) { + if (result.getResultMetadata().has(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) { + saResults.push(result); + } else { + newResults.push(result); + } + } + if (saResults.length === 0) { + return results; + } + + // sort and concatenate the SA list items + Collections.sort(saResults, new SAComparator()); + const newText: StringBuilder = new StringBuilder(); + const newRawBytes: ByteArrayOutputStream = new ByteArrayOutputStream(); + const newByteSegment: ByteArrayOutputStream = new ByteArrayOutputStream(); + for (const saResult of saResults) { + newText.append(saResult.getText()); + const saBytes: Uint8Array = saResult.getRawBytes(); + newRawBytes.writeBytesOffset(saBytes, 0, saBytes.length); + // @SuppressWarnings("unchecked") + const byteSegments: Iterable = + > saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS); + if (byteSegments != null) { + for (const segment of byteSegments) { + newByteSegment.writeBytesOffset(segment, 0, segment.length); + } + } + } + + const newResult: Result = new Result(newText.toString(), newRawBytes.toByteArray(), QRCodeMultiReader.NO_POINTS, BarcodeFormat.QR_CODE); + if (newByteSegment.size() > 0) { + newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, Collections.singletonList(newByteSegment.toByteArray())); + } + newResults.push(newResult); // TYPESCRIPTPORT: inserted element at the start of the array because it seems the Java version does that as well. + return newResults; + } + +} + +/* private static final*/ class SAComparator implements Comparator/*, Serializable*/ { + /** + * @override + */ + public compare(a: Result, b: Result): int { + const aNumber: int = a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + const bNumber: int = b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + return Integer.compare(aNumber, bNumber); + } +} diff --git a/src/core/multi/qrcode/detector/MultiDetector.ts b/src/core/multi/qrcode/detector/MultiDetector.ts new file mode 100644 index 00000000..f669a4af --- /dev/null +++ b/src/core/multi/qrcode/detector/MultiDetector.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2009 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. + */ + +import BitMatrix from '../../../common/BitMatrix'; +import DetectorResult from '../../../common/DetectorResult'; +import DecodeHintType from '../../../DecodeHintType'; +import NotFoundException from '../../../NotFoundException'; +import Detector from '../../../qrcode/detector/Detector'; +import FinderPatternInfo from '../../../qrcode/detector/FinderPatternInfo'; +import ReaderException from '../../../ReaderException'; +import ResultPointCallback from '../../../ResultPointCallback'; +import { List } from '../../../../customTypings'; +import MultiFinderPatternFinder from './MultiFinderPatternFinder'; + +// package com.google.zxing.multi.qrcode.detector; + +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ReaderException; +// import com.google.zxing.ResultPointCallback; +// import com.google.zxing.common.BitMatrix; +// import com.google.zxing.common.DetectorResult; +// import com.google.zxing.qrcode.detector.Detector; +// import com.google.zxing.qrcode.detector.FinderPatternInfo; + +// import java.util.ArrayList; +// import java.util.List; +// import java.util.Map; + +/** + *

Encapsulates logic that can detect one or more QR Codes in an image, even if the QR Code + * is rotated or skewed, or partially obscured.

+ * + * @author Sean Owen + * @author Hannes Erven + */ +export default /* public final */ class MultiDetector extends Detector { + + private static /* final */ EMPTY_DETECTOR_RESULTS: DetectorResult[] = []; + + public constructor( image: BitMatrix) { + super(image); + } + + /** @throws NotFoundException */ + public detectMulti(hints: Map): DetectorResult[] { + const image: BitMatrix = this.getImage(); + const resultPointCallback: ResultPointCallback = + hints == null ? null : hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + const finder: MultiFinderPatternFinder = new MultiFinderPatternFinder(image, resultPointCallback); + const infos: FinderPatternInfo[] = finder.findMulti(hints); + + if (infos.length === 0) { + throw NotFoundException.getNotFoundInstance(); + } + + const result: List = []; + for (const info of infos) { + try { + result.push(this.processFinderPatternInfo(info)); + } catch (e) { + if (e instanceof ReaderException) { + // ignore + } else { + throw e; + } + } + } + if (result.length === 0) { + return MultiDetector.EMPTY_DETECTOR_RESULTS; + } else { + return result/* .toArray(EMPTY_DETECTOR_RESULTS) */; + } + } + +} diff --git a/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts new file mode 100644 index 00000000..6d6dbba9 --- /dev/null +++ b/src/core/multi/qrcode/detector/MultiFinderPatternFinder.ts @@ -0,0 +1,300 @@ +/* + * Copyright 2009 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. + */ + +import BitMatrix from '../../../common/BitMatrix'; +import DecodeHintType from '../../../DecodeHintType'; +import NotFoundException from '../../../NotFoundException'; +import FinderPattern from '../../../qrcode/detector/FinderPattern'; +import FinderPatternFinder from '../../../qrcode/detector/FinderPatternFinder'; +import FinderPatternInfo from '../../../qrcode/detector/FinderPatternInfo'; +import ResultPoint from '../../../ResultPoint'; +import ResultPointCallback from '../../../ResultPointCallback'; +import Collections from '../../../util/Collections'; +import Comparator from '../../../util/Comparator'; +import { double, float, int, List } from '../../../../customTypings'; + +// package com.google.zxing.multi.qrcode.detector; + +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.ResultPointCallback; +// import com.google.zxing.common.BitMatrix; +// import com.google.zxing.qrcode.detector.FinderPattern; +// import com.google.zxing.qrcode.detector.FinderPatternFinder; +// import com.google.zxing.qrcode.detector.FinderPatternInfo; + +// import java.io.Serializable; +// import java.util.ArrayList; +// import java.util.Collections; +// import java.util.Comparator; +// import java.util.List; +// import java.util.Map; + +/** + *

This class attempts to find finder patterns in a QR Code. Finder patterns are the square + * markers at three corners of a QR Code.

+ * + *

This class is thread-safe but not reentrant. Each thread must allocate its own object. + * + *

In contrast to {@link FinderPatternFinder}, this class will return an array of all possible + * QR code locations in the image.

+ * + *

Use the TRY_HARDER hint to ask for a more thorough detection.

+ * + * @author Sean Owen + * @author Hannes Erven + */ +export default /* public final */ class MultiFinderPatternFinder extends FinderPatternFinder { + + private static /* final */ EMPTY_RESULT_ARRAY: FinderPatternInfo[] = []; + private static /* final */ EMPTY_FP_ARRAY: FinderPattern[] = []; + private static /* final */ EMPTY_FP_2D_ARRAY: FinderPattern[][] = [[]]; + + // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for + // since it limits the number of regions to decode + + // max. legal count of modules per QR code edge (177) + private static /* final */ MAX_MODULE_COUNT_PER_EDGE: float = 180; + // min. legal count per modules per QR code edge (11) + private static /* final */ MIN_MODULE_COUNT_PER_EDGE: float = 9; + + /** + * More or less arbitrary cutoff point for determining if two finder patterns might belong + * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their + * estimated modules sizes. + */ + private static /* final */ DIFF_MODSIZE_CUTOFF_PERCENT: float = 0.05; + + /** + * More or less arbitrary cutoff point for determining if two finder patterns might belong + * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their + * estimated modules sizes. + */ + private static /* final */ DIFF_MODSIZE_CUTOFF: float = 0.5; + + + public constructor(image: BitMatrix, resultPointCallback: ResultPointCallback) { + super(image, resultPointCallback); + } + + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those that have been detected at least 2 times, and whose module + * size differs from the average among those patterns the least + * @throws NotFoundException if 3 such finder patterns do not exist + */ + private selectMultipleBestPatterns(): FinderPattern[][] { + const possibleCenters: List = this.getPossibleCenters(); + const size: int = possibleCenters.length; + + if (size < 3) { + // Couldn't find enough finder patterns + throw NotFoundException.getNotFoundInstance(); + } + + /* + * Begin HE modifications to safely detect multiple codes of equal size + */ + if (size === 3) { + return [ possibleCenters ]; + } + + // Sort by estimated module size to speed up the upcoming checks + Collections.sort(possibleCenters, new ModuleSizeComparator()); + + /* + * Now lets start: build a list of tuples of three finder locations that + * - feature similar module sizes + * - are placed in a distance so the estimated module count is within the QR specification + * - have similar distance between upper left/right and left top/bottom finder patterns + * - form a triangle with 90° angle (checked by comparing top right/bottom left distance + * with pythagoras) + * + * Note: we allow each point to be used for more than one code region: this might seem + * counterintuitive at first, but the performance penalty is not that big. At this point, + * we cannot make a good quality decision whether the three finders actually represent + * a QR code, or are just by chance laid out so it looks like there might be a QR code there. + * So, if the layout seems right, lets have the decoder try to decode. + */ + + const results: List = new Array(); // holder for the results + + for (let i1: int = 0; i1 < (size - 2); i1++) { + const p1: FinderPattern = possibleCenters[i1]; + if (p1 == null) { + continue; + } + + for (let i2: int = i1 + 1; i2 < (size - 1); i2++) { + const p2: FinderPattern = possibleCenters[i2]; + if (p2 == null) { + continue; + } + + // Compare the expected module sizes; if they are really off, skip + const vModSize12: float = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) / + Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize()); + const vModSize12A: float = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()); + if (vModSize12A > MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF && vModSize12 >= MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF_PERCENT) { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + for (let i3: int = i2 + 1; i3 < size; i3++) { + const p3: FinderPattern = possibleCenters[i3]; + if (p3 == null) { + continue; + } + + // Compare the expected module sizes; if they are really off, skip + const vModSize23: float = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) / + Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize()); + const vModSize23A: float = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()); + if (vModSize23A > MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF && vModSize23 >= MultiFinderPatternFinder.DIFF_MODSIZE_CUTOFF_PERCENT) { + // break, since elements are ordered by the module size deviation there cannot be + // any more interesting elements for the given p1. + break; + } + + const test: FinderPattern[] = [p1, p2, p3]; + ResultPoint.orderBestPatterns(test); + + // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal + const info: FinderPatternInfo = new FinderPatternInfo(test); + const dA: float = ResultPoint.distance(info.getTopLeft(), info.getBottomLeft()); + const dC: float = ResultPoint.distance(info.getTopRight(), info.getBottomLeft()); + const dB: float = ResultPoint.distance(info.getTopLeft(), info.getTopRight()); + + // Check the sizes + const estimatedModuleCount: float = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0); + if (estimatedModuleCount > MultiFinderPatternFinder.MAX_MODULE_COUNT_PER_EDGE || + estimatedModuleCount < MultiFinderPatternFinder.MIN_MODULE_COUNT_PER_EDGE) { + continue; + } + + // Calculate the difference of the edge lengths in percent + const vABBC: float = Math.abs((dA - dB) / Math.min(dA, dB)); + if (vABBC >= 0.1) { + continue; + } + + // Calculate the diagonal length by assuming a 90° angle at topleft + const dCpy: float = Math.sqrt( dA * dA + dB * dB); + // Compare to the real distance in % + const vPyC: float = Math.abs((dC - dCpy) / Math.min(dC, dCpy)); + + if (vPyC >= 0.1) { + continue; + } + + // All tests passed! + results.push(test); + } + } + } + + if (results.length > 0) { + return results/* .toArray(MultiFinderPatternFinder.EMPTY_FP_2D_ARRAY) */; + } + + // Nothing found! + throw NotFoundException.getNotFoundInstance(); + } + + /** + * @throws NotFoundException + */ + public findMulti(hints: Map): FinderPatternInfo[] { + const tryHarder: boolean = hints != null && hints.has(DecodeHintType.TRY_HARDER); + const image: BitMatrix = this.getImage(); + const maxI: int = image.getHeight(); + const maxJ: int = image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + let iSkip: int = Math.trunc((3 * maxI) / (4 * MultiFinderPatternFinder.MAX_MODULES)); // TYPESCRIPTPORT: Java integer divisions always discard decimal chars. + if (iSkip < MultiFinderPatternFinder.MIN_SKIP || tryHarder) { + iSkip = MultiFinderPatternFinder.MIN_SKIP; + } + + const stateCount: Int32Array = Int32Array.from({ length: 5 }); + for (let i: int = iSkip - 1; i < maxI; i += iSkip) { + // Get a row of black/white values + this.clearCounts(stateCount); + let currentState: int = 0; + for (let j: int = 0; j < maxJ; j++) { + if (image.get(j, i)) { + // 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 (MultiFinderPatternFinder.foundPatternCross(stateCount) && this.handlePossibleCenter(stateCount, i, j)) { // Yes + // Clear state to start looking again + currentState = 0; + this.clearCounts(stateCount); + } else { // No, shift counts back by two + this.shiftCounts2(stateCount); + currentState = 3; + } + } else { + stateCount[++currentState]++; + } + } else { // Counting white pixels + stateCount[currentState]++; + } + } + } // for j=... + + if (MultiFinderPatternFinder.foundPatternCross(stateCount)) { + this.handlePossibleCenter(stateCount, i, maxJ); + } + } // for i=iSkip-1 ... + const patternInfo: FinderPattern[][] = this.selectMultipleBestPatterns(); + const result: List = new Array(); + for (const pattern of patternInfo) { + ResultPoint.orderBestPatterns(pattern); + result.push(new FinderPatternInfo(pattern)); + } + + if (result.length === 0) { + return MultiFinderPatternFinder.EMPTY_RESULT_ARRAY; + } else { + return result/* .toArray(MultiFinderPatternFinder.EMPTY_RESULT_ARRAY) */; + } + } + +} + + /** + * A comparator that orders FinderPatterns by their estimated module size. + */ + /* private static final */ class ModuleSizeComparator implements Comparator/* , Serializable */ { + /** @override */ + public compare(center1: FinderPattern, center2: FinderPattern): int { + const value: float = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize(); + return value < 0.0 ? -1 : value > 0.0 ? 1 : 0; + } + } diff --git a/src/core/oned/MultiFormatUPCEANReader.ts b/src/core/oned/MultiFormatUPCEANReader.ts index 94455f5d..0f9d282f 100644 --- a/src/core/oned/MultiFormatUPCEANReader.ts +++ b/src/core/oned/MultiFormatUPCEANReader.ts @@ -100,7 +100,6 @@ export default class MultiFormatUPCEANReader extends OneDReader { const resultUPCA: Result = new Result( result.getText().substring(1), rawBytes, - rawBytes.length, result.getResultPoints(), BarcodeFormat.UPC_A ); diff --git a/src/core/oned/UPCAReader.ts b/src/core/oned/UPCAReader.ts index 49ea740d..01a23283 100644 --- a/src/core/oned/UPCAReader.ts +++ b/src/core/oned/UPCAReader.ts @@ -71,7 +71,7 @@ export default class UPCAReader extends UPCEANReader { public maybeReturnResult(result: Result) { let text = result.getText(); if (text.charAt(0) === '0') { - let upcaResult = new Result(text.substring(1), null, null, result.getResultPoints(), BarcodeFormat.UPC_A); + let upcaResult = new Result(text.substring(1), null, null, BarcodeFormat.UPC_A); if (result.getResultMetadata() != null) { upcaResult.putAllMetadata(result.getResultMetadata()); } diff --git a/src/core/oned/rss/expanded/decoders/createDecoder.ts b/src/core/oned/rss/expanded/decoders/createDecoder.ts index 2c0efc4c..27965777 100644 --- a/src/core/oned/rss/expanded/decoders/createDecoder.ts +++ b/src/core/oned/rss/expanded/decoders/createDecoder.ts @@ -1,4 +1,5 @@ -import { BitArray, IllegalStateException } from '../../../../..'; +import BitArray from '../../../../common/BitArray'; +import IllegalStateException from '../../../../IllegalStateException'; import AbstractExpandedDecoder from './AbstractExpandedDecoder'; import AI013103decoder from './AI013103decoder'; import AI01320xDecoder from './AI01320xDecoder'; diff --git a/src/core/pdf417/PDF417Reader.ts b/src/core/pdf417/PDF417Reader.ts index 55c78450..3c4ca6e1 100644 --- a/src/core/pdf417/PDF417Reader.ts +++ b/src/core/pdf417/PDF417Reader.ts @@ -72,8 +72,8 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa * @throws NotFoundException if a PDF417 code cannot be found, * @throws FormatException if a PDF417 cannot be decoded * @throws ChecksumException + * @override decode */ - // @Override public decode(image: BinaryBitmap, hints: Map = null): Result { let result = PDF417Reader.decode(image, hints, false); if (result == null || result.length === 0 || result[0] == null) { @@ -82,14 +82,43 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa return result[0]; } + /** + * + * @override decodeMultiple + */ + public decodeMultiple(image: BinaryBitmap): Result[]; /** * * @param BinaryBitmap * @param image * @throws NotFoundException + * @override */ - // @Override public decodeMultiple(image: BinaryBitmap, hints: Map = null): Result[] { + + if (!hints) { + return this.decodeMultipleOverload1(image); + } + + return this.decodeMultipleImpl(image, hints); + } + + /** + * + * @override decodeMultiple + */ + private decodeMultipleOverload1(image: BinaryBitmap): Result[] { + return this.decodeMultipleImpl(image, null); + } + + /** + * + * @param BinaryBitmap + * @param image + * @throws NotFoundException + * @override + */ + private decodeMultipleImpl(image: BinaryBitmap, hints: Map = null): Result[] { try { return PDF417Reader.decode(image, hints, true); } catch (ignored) { @@ -117,7 +146,7 @@ export default /*public final*/ class PDF417Reader implements Reader, MultipleBa for (const points of detectorResult.getPoints()) { const decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5], points[6], points[7], PDF417Reader.getMinCodewordWidth(points), PDF417Reader.getMaxCodewordWidth(points)); - const result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), undefined, points, BarcodeFormat.PDF_417); + const result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF_417); result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel()); const pdf417ResultMetadata: PDF417ResultMetadata = decoderResult.getOther(); if (pdf417ResultMetadata != null) { diff --git a/src/core/pdf417/decoder/Codeword.ts b/src/core/pdf417/decoder/Codeword.ts index 6b2118fa..16d27ad9 100644 --- a/src/core/pdf417/decoder/Codeword.ts +++ b/src/core/pdf417/decoder/Codeword.ts @@ -78,8 +78,8 @@ export default /*final*/ class Codeword { this.rowNumber = rowNumber; } -// @Override - public toString(): string { + // @Override + public toString(): string { return this.rowNumber + '|' + this.value; } diff --git a/src/core/qrcode/QRCodeReader.ts b/src/core/qrcode/QRCodeReader.ts index 6380bcea..a324598d 100644 --- a/src/core/qrcode/QRCodeReader.ts +++ b/src/core/qrcode/QRCodeReader.ts @@ -42,7 +42,7 @@ import Detector from './detector/Detector'; */ export default class QRCodeReader implements Reader { - private static NO_POINTS = new Array(); + protected static NO_POINTS = new Array(); private decoder = new Decoder(); @@ -58,13 +58,35 @@ export default class QRCodeReader implements Reader { * @throws FormatException if a QR code cannot be decoded * @throws ChecksumException if error correction fails */ - /*@Override*/ - // public decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { - // return this.decode(image, null) - // } - - /*@Override*/ + public decode(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */; + /** + * @override + */ public decode(image: BinaryBitmap, hints?: Map): Result { + + if (!hints) { + this.decodeOverload1(image); + } + + return this.decodeImpl(image, hints); + } + + /** + * Locates and decodes a QR code in an image. + * + * @return a representing: string the content encoded by the QR code + * @throws NotFoundException if a QR code cannot be found + * @throws FormatException if a QR code cannot be decoded + * @throws ChecksumException if error correction fails + */ + public decodeOverload1(image: BinaryBitmap): Result /*throws NotFoundException, ChecksumException, FormatException */ { + return this.decodeImpl(image, null); + } + + /** + * @override + */ + public decodeImpl(image: BinaryBitmap, hints?: Map): Result { let decoderResult: DecoderResult; let points: Array; if (hints !== undefined && hints !== null && undefined !== hints.get(DecodeHintType.PURE_BARCODE)) { diff --git a/src/core/qrcode/decoder/Decoder.ts b/src/core/qrcode/decoder/Decoder.ts index 7a5a9264..b080a08f 100644 --- a/src/core/qrcode/decoder/Decoder.ts +++ b/src/core/qrcode/decoder/Decoder.ts @@ -74,6 +74,7 @@ export default class Decoder { * @return text and bytes encoded within the QR Code * @throws FormatException if the QR Code cannot be decoded * @throws ChecksumException if error correction fails + * @override decode */ public decodeBitMatrix(bits: BitMatrix, hints?: Map): DecoderResult { diff --git a/src/core/qrcode/detector/FinderPatternFinder.ts b/src/core/qrcode/detector/FinderPatternFinder.ts index 8415b19c..c987a1c9 100644 --- a/src/core/qrcode/detector/FinderPatternFinder.ts +++ b/src/core/qrcode/detector/FinderPatternFinder.ts @@ -14,25 +14,46 @@ * limitations under the License. */ -/*namespace com.google.zxing.qrcode.detector {*/ - +import BitMatrix from '../../common/BitMatrix'; import DecodeHintType from '../../DecodeHintType'; +import NotFoundException from '../../NotFoundException'; import ResultPoint from '../../ResultPoint'; import ResultPointCallback from '../../ResultPointCallback'; -import BitMatrix from '../../common/BitMatrix'; +import Arrays from '../../util/Arrays'; +import Comparator from '../../util/Comparator'; +import Double from '../../util/Double'; +import Float from '../../util/Float'; +import { double, float, int, List } from '../../../customTypings'; import FinderPattern from './FinderPattern'; import FinderPatternInfo from './FinderPatternInfo'; -import NotFoundException from '../../NotFoundException'; +// package com.google.zxing.qrcode.detector; -import { float } from '../../../customTypings'; +// import com.google.zxing.DecodeHintType; +// import com.google.zxing.NotFoundException; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.ResultPointCallback; +// import com.google.zxing.common.BitMatrix; + +// import java.io.Serializable; +// import java.util.ArrayList; +// import java.util.Arrays; +// import java.util.Comparator; +// import java.util.List; +// import java.util.Map; + + +// TYPESCRIPTPORT: this class woudl normaly exist at the end of this file, but it's here due to ESLint. +/** + *

Orders by {@link FinderPattern#getEstimatedModuleSize()}

+ */ +/* private static final */ class EstimatedModuleComparator implements Comparator/*, Serializable*/ { + /** @override */ + public compare(center1: FinderPattern, center2: FinderPattern): int { + return Float.compare(center1.getEstimatedModuleSize(), center2.getEstimatedModuleSize()); + } +} -/*import java.io.Serializable;*/ -/*import java.util.ArrayList;*/ -/*import java.util.Collections;*/ -/*import java.util.Comparator;*/ -/*import java.util.List;*/ -/*import java.util.Map;*/ /** *

This class attempts to find finder patterns in a QR Code. Finder patterns are the square @@ -44,629 +65,644 @@ import { float } from '../../../customTypings'; */ export default class FinderPatternFinder { - private static CENTER_QUORUM = 2; - protected static MIN_SKIP = 3; // 1 pixel/module times 3 modules/center - protected static MAX_MODULES = 57; // support up to version 10 for mobile clients - - private possibleCenters: FinderPattern[]; - private hasSkipped: boolean; - private crossCheckStateCount: Int32Array; + private static /* final*/ CENTER_QUORUM: int = 2; + private static /* final*/ moduleComparator: EstimatedModuleComparator = new EstimatedModuleComparator(); + protected static /* final*/ MIN_SKIP: int = 3; // 1 pixel/module times 3 modules/center + protected static /* final*/ MAX_MODULES: int = 97; // support up to version 20 for mobile clients + + private /* final*/ image: BitMatrix; + private /* final*/ possibleCenters: List; + private hasSkipped: boolean; + private /* final*/ crossCheckStateCount: Int32Array; + private /* final*/ resultPointCallback: ResultPointCallback; + + /** + *

Creates a finder that will search the image for three finder patterns.

+ * + * @param image image to search + */ + private constructorOverload1(image: BitMatrix) { + this.constructorOverload2(image, null); + } /** - *

Creates a finder that will search the image for three finder patterns.

- * - * @param image image to search - */ - // public constructor(image: BitMatrix) { - // this(image, null) - // } - - public constructor(private image: BitMatrix, private resultPointCallback: ResultPointCallback) { - this.possibleCenters = []; - this.crossCheckStateCount = new Int32Array(5); - this.resultPointCallback = resultPointCallback; - } - - protected getImage(): BitMatrix { - return this.image; - } - - protected getPossibleCenters(): FinderPattern[] { - return this.possibleCenters; - } - - public find(hints: Map): FinderPatternInfo /*throws NotFoundException */ { - const tryHarder: boolean = (hints !== null && hints !== undefined) && undefined !== hints.get(DecodeHintType.TRY_HARDER); - const pureBarcode: boolean = (hints !== null && hints !== undefined) && undefined !== hints.get(DecodeHintType.PURE_BARCODE); - const image = this.image; - const maxI = image.getHeight(); - const maxJ = image.getWidth(); - // We are looking for black/white/black/white/black modules in - // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far - - // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the - // image, and then account for the center being 3 modules in size. This gives the smallest - // number of pixels the center could be, so skip this often. When trying harder, look for all - // QR versions regardless of how dense they are. - let iSkip = Math.floor((3 * maxI) / (4 * FinderPatternFinder.MAX_MODULES)); - if (iSkip < FinderPatternFinder.MIN_SKIP || tryHarder) { - iSkip = FinderPatternFinder.MIN_SKIP; - } + * @param image image to search + * @param resultPointCallback + */ + private constructorOverload2(image: BitMatrix, resultPointCallback: ResultPointCallback) { + this.image = image; + this.possibleCenters = new Array(); + this.crossCheckStateCount = Int32Array.from({ length: 5 }); + this.resultPointCallback = resultPointCallback; + } + + /** + * @param image image to search + */ + constructor(image: BitMatrix, resultPointCallback?: ResultPointCallback) { + // TYPESCRIPTPORT: this contructor only serves as entrypoint for the original Java overloads + if (resultPointCallback) { + this.constructorOverload2(image, resultPointCallback); + return; + } + this.constructorOverload1(image); + } + + protected /* final */ getImage(): BitMatrix { + return this.image; + } + + protected /* final */ getPossibleCenters(): List { + return this.possibleCenters; + } + + /** + * + * @throws NotFoundException + */ + /* final */ find(hints: Map): FinderPatternInfo { + const tryHarder: boolean = hints != null && hints.has(DecodeHintType.TRY_HARDER); + const maxI: int = this.image.getHeight(); + const maxJ: int = this.image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + let iSkip: int = Math.trunc((3 * maxI) / (4 * FinderPatternFinder.MAX_MODULES)); + if (iSkip < FinderPatternFinder.MIN_SKIP || tryHarder) { + iSkip = FinderPatternFinder.MIN_SKIP; + } - let done: boolean = false; - const stateCount = new Int32Array(5); - for (let 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; - let currentState = 0; - for (let j = 0; j < maxJ; j++) { - if (image.get(j, i)) { - // 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 (FinderPatternFinder.foundPatternCross(stateCount)) { // Yes - const confirmed: boolean = this.handlePossibleCenter(stateCount, i, j, pureBarcode); - if (confirmed === true) { - // Start examining every other line. Checking each line turned out to be too - // expensive and didn't improve performance. - iSkip = 2; - if (this.hasSkipped === true) { - done = this.haveMultiplyConfirmedCenters(); - } else { - const 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 { - stateCount[0] = stateCount[2]; - stateCount[1] = stateCount[3]; - stateCount[2] = stateCount[4]; - stateCount[3] = 1; - stateCount[4] = 0; - currentState = 3; - continue; - } - // 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 (FinderPatternFinder.foundPatternCross(stateCount)) { - const confirmed: boolean = this.handlePossibleCenter(stateCount, i, maxJ, pureBarcode); - if (confirmed === true) { - iSkip = stateCount[0]; - if (this.hasSkipped) { - // Found a third one - done = this.haveMultiplyConfirmedCenters(); + let done: boolean = false; + const stateCount: Int32Array = Int32Array.from({ length: 5 }); + for (let i: int = iSkip - 1; i < maxI && !done; i += iSkip) { + // Get a row of black/white values + this.clearCounts(stateCount); + let currentState: int = 0; + for (let j: int = 0; j < maxJ; j++) { + if (this.image.get(j, i)) { + // 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 (FinderPatternFinder.foundPatternCross(stateCount)) { // Yes + let confirmed: boolean = 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 { + let rowSkip: int = 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 { + this.shiftCounts2(stateCount); + currentState = 3; + continue; } + // Clear state to start looking again + currentState = 0; + this.clearCounts(stateCount); + } else { // No, shift counts back by two + this.shiftCounts2(stateCount); + currentState = 3; + } + } else { + stateCount[++currentState]++; } - } - - const patternInfo: FinderPattern[] = this.selectBestPatterns(); - ResultPoint.orderBestPatterns(patternInfo); - - return new FinderPatternInfo(patternInfo); + } else { // Counting white pixels + stateCount[currentState]++; + } + } + } + if (FinderPatternFinder.foundPatternCross(stateCount)) { + let confirmed: boolean = this.handlePossibleCenter(stateCount, i, maxJ); + if (confirmed) { + iSkip = stateCount[0]; + if (this.hasSkipped) { + // Found a third one + done = this.haveMultiplyConfirmedCenters(); + } + } + } } - /** - * Given a count of black/white/black/white/black pixels just seen and an end position, - * figures the location of the center of this run. - */ - private static centerFromEnd(stateCount: Int32Array, end: number /*int*/): number/*float*/ { - return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0; + const patternInfo: FinderPattern[] = this.selectBestPatterns(); + ResultPoint.orderBestPatterns(patternInfo); + + return new FinderPatternInfo(patternInfo); + } + + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + */ + private static centerFromEnd(stateCount: Int32Array, end: int): float { + return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0; + } + + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static foundPatternCross(stateCount: Int32Array): boolean { + let totalModuleSize: int = 0; + for (let i: int = 0; i < 5; i++) { + let count: int = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; } - - /** - * @param stateCount count of black/white/black/white/black pixels just read - * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios - * used by finder patterns to be considered a match - */ - protected static foundPatternCross(stateCount: Int32Array): boolean { - let totalModuleSize = 0; - for (let i = 0; i < 5; i++) { - const count = stateCount[i]; - if (count === 0) { - return false; - } - totalModuleSize += count; - } - if (totalModuleSize < 7) { - return false; - } - const moduleSize: number /*float*/ = totalModuleSize / 7.0; - const maxVariance: number /*float*/ = moduleSize / 2.0; - // Allow less than 50% variance from 1-1-3-1-1 proportions - return Math.abs(moduleSize - stateCount[0]) < maxVariance && - Math.abs(moduleSize - stateCount[1]) < maxVariance && - Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && - Math.abs(moduleSize - stateCount[3]) < maxVariance && - Math.abs(moduleSize - stateCount[4]) < maxVariance; - } - - private getCrossCheckStateCount(): Int32Array { - const crossCheckStateCount = this.crossCheckStateCount; - crossCheckStateCount[0] = 0; - crossCheckStateCount[1] = 0; - crossCheckStateCount[2] = 0; - crossCheckStateCount[3] = 0; - crossCheckStateCount[4] = 0; - return crossCheckStateCount; + if (totalModuleSize < 7) { + return false; } - - /** - * After a vertical and horizontal scan finds a potential finder pattern, this method - * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible - * finder pattern to see if the same proportion is detected. - * - * @param startI row where a finder pattern was detected - * @param centerJ center of the section that appears to cross a finder pattern - * @param maxCount maximum reasonable number of modules that should be - * observed in any reading state, based on the results of the horizontal scan - * @param originalStateCountTotal The original state count total. - * @return true if proportions are withing expected limits - */ - private crossCheckDiagonal(startI: number /*int*/, centerJ: number /*int*/, maxCount: number /*int*/, originalStateCountTotal: number /*int*/): boolean { - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - // Start counting up, left from center finding black center mass - let i = 0; - const image = this.image; - while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i)) { - stateCount[2]++; - i++; - } - - if (startI < i || centerJ < i) { - return false; - } - - // Continue up, left finding white space - while (startI >= i && centerJ >= i && !image.get(centerJ - i, startI - i) && - stateCount[1] <= maxCount) { - stateCount[1]++; - i++; - } - - // If already too many modules in this state or ran off the edge: - if (startI < i || centerJ < i || stateCount[1] > maxCount) { - return false; - } - - // Continue up, left finding black border - while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i) && - stateCount[0] <= maxCount) { - stateCount[0]++; - i++; - } - if (stateCount[0] > maxCount) { - return false; - } - - const maxI = image.getHeight(); - const maxJ = image.getWidth(); - - // Now also count down, right from center - i = 1; - while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i)) { - stateCount[2]++; - i++; - } - - // Ran off the edge? - if (startI + i >= maxI || centerJ + i >= maxJ) { - return false; - } - - while (startI + i < maxI && centerJ + i < maxJ && !image.get(centerJ + i, startI + i) && - stateCount[3] < maxCount) { - stateCount[3]++; - i++; - } - - if (startI + i >= maxI || centerJ + i >= maxJ || stateCount[3] >= maxCount) { - return false; - } - - while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i) && - stateCount[4] < maxCount) { - stateCount[4]++; - i++; - } - - if (stateCount[4] >= maxCount) { - return false; - } - - // If we found a finder-pattern-like section, but its size is more than 100% different than - // the original, assume it's a false positive - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; - return Math.abs(stateCountTotal - originalStateCountTotal) < 2 * originalStateCountTotal && - FinderPatternFinder.foundPatternCross(stateCount); + const moduleSize: float = totalModuleSize / 7.0; // TYPESCRIPTPORT: check if a precision reduction is needed + const maxVariance: float = moduleSize / 2.0; // TYPESCRIPTPORT: check if a precision reduction is needed + // Allow less than 50% variance from 1-1-3-1-1 proportions + return Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } + + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + protected static foundPatternDiagonal(stateCount: Int32Array): boolean { + let totalModuleSize: int = 0; + for (let i: int = 0; i < 5; i++) { + const count: int = stateCount[i]; + if (count === 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + const moduleSize: float = totalModuleSize / 7.0; + const maxVariance: float = moduleSize / 1.333; + // Allow less than 75% variance from 1-1-3-1-1 proportions + return Math.abs(moduleSize - stateCount[0]) < maxVariance && + Math.abs(moduleSize - stateCount[1]) < maxVariance && + Math.abs(3.0 * moduleSize - stateCount[2]) < 3 * maxVariance && + Math.abs(moduleSize - stateCount[3]) < maxVariance && + Math.abs(moduleSize - stateCount[4]) < maxVariance; + } + + private getCrossCheckStateCount(): Int32Array { + this.clearCounts(this.crossCheckStateCount); + return this.crossCheckStateCount; + } + + protected /* final */ clearCounts(counts: Int32Array): void { + Arrays.fill(counts, 0); + } + + protected /* final */ shiftCounts2(stateCount: Int32Array): void { + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + } + + /** + * After a vertical and horizontal scan finds a potential finder pattern, this method + * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible + * finder pattern to see if the same proportion is detected. + * + * @param centerI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @return true if proportions are withing expected limits + */ + private crossCheckDiagonal(centerI: int, centerJ: int): boolean { + const stateCount: Int32Array = this.getCrossCheckStateCount(); + + // Start counting up, left from center finding black center mass + let i: int = 0; + while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { + stateCount[2]++; + i++; + } + if (stateCount[2] === 0) { + return false; } - /** - *

After a horizontal scan finds a potential finder pattern, this method - * "cross-checks" by scanning down vertically through the center of the possible - * finder pattern to see if the same proportion is detected.

- * - * @param startI row where a finder pattern was detected - * @param centerJ center of the section that appears to cross a finder pattern - * @param maxCount maximum reasonable number of modules that should be - * observed in any reading state, based on the results of the horizontal scan - * @return vertical center of finder pattern, or {@link Float#NaN} if not found - */ - private crossCheckVertical(startI: number /*int*/, centerJ: number /*int*/, maxCount: number /*int*/, - originalStateCountTotal: number /*int*/): number/*float*/ { - const image: BitMatrix = this.image; - - const maxI = image.getHeight(); - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - // Start counting up from center - let i = startI; - while (i >= 0 && image.get(centerJ, i)) { - stateCount[2]++; - i--; - } - if (i < 0) { - return NaN; - } - while (i >= 0 && !image.get(centerJ, i) && 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.get(centerJ, i) && stateCount[0] <= maxCount) { - stateCount[0]++; - i--; - } - if (stateCount[0] > maxCount) { - return NaN; - } + // Continue up, left finding white space + while (centerI >= i && centerJ >= i && !this.image.get(centerJ - i, centerI - i)) { + stateCount[1]++; + i++; + } + if (stateCount[1] === 0) { + return false; + } - // Now also count down from center - i = startI + 1; - while (i < maxI && image.get(centerJ, i)) { - stateCount[2]++; - i++; - } - if (i === maxI) { - return NaN; - } - while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) { - stateCount[3]++; - i++; - } - if (i === maxI || stateCount[3] >= maxCount) { - return NaN; - } - while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) { - stateCount[4]++; - i++; - } - if (stateCount[4] >= maxCount) { - return NaN; - } + // Continue up, left finding black border + while (centerI >= i && centerJ >= i && this.image.get(centerJ - i, centerI - i)) { + stateCount[0]++; + i++; + } + if (stateCount[0] === 0) { + return false; + } - // 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 - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { - return NaN; - } + const maxI: int = this.image.getHeight(); + const maxJ: int = this.image.getWidth(); - return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, i) : NaN; + // Now also count down, right from center + i = 1; + while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { + stateCount[2]++; + i++; } - /** - *

Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical, - * except it reads horizontally instead of vertically. This is used to cross-cross - * check a vertical cross check and locate the real center of the alignment pattern.

- */ - private crossCheckHorizontal(startJ: number /*int*/, centerI: number /*int*/, maxCount: number /*int*/, - originalStateCountTotal: number /*int*/): number/*float*/ { - const image: BitMatrix = this.image; - - const maxJ = image.getWidth(); - const stateCount: Int32Array = this.getCrossCheckStateCount(); - - let j = startJ; - while (j >= 0 && image.get(j, centerI)) { - stateCount[2]++; - j--; - } - if (j < 0) { - return NaN; - } - while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) { - stateCount[1]++; - j--; - } - if (j < 0 || stateCount[1] > maxCount) { - return NaN; - } - while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) { - stateCount[0]++; - j--; - } - if (stateCount[0] > maxCount) { - return NaN; - } - - j = startJ + 1; - while (j < maxJ && image.get(j, centerI)) { - stateCount[2]++; - j++; - } - if (j === maxJ) { - return NaN; - } - while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) { - stateCount[3]++; - j++; - } - if (j === maxJ || stateCount[3] >= maxCount) { - return NaN; - } - while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) { - stateCount[4]++; - j++; - } - if (stateCount[4] >= maxCount) { - return NaN; - } + while (centerI + i < maxI && centerJ + i < maxJ && !this.image.get(centerJ + i, centerI + i)) { + stateCount[3]++; + i++; + } + if (stateCount[3] === 0) { + return false; + } - // If we found a finder-pattern-like section, but its size is significantly different than - // the original, assume it's a false positive - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { - return NaN; - } + while (centerI + i < maxI && centerJ + i < maxJ && this.image.get(centerJ + i, centerI + i)) { + stateCount[4]++; + i++; + } + if (stateCount[4] === 0) { + return false; + } - return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, j) : NaN; + return FinderPatternFinder.foundPatternDiagonal(stateCount); + } + + /** + *

After a horizontal scan finds a potential finder pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * finder pattern to see if the same proportion is detected.

+ * + * @param startI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of finder pattern, or {@link Float#NaN} if not found + */ + private crossCheckVertical(startI: int, centerJ: int, maxCount: int, + originalStateCountTotal: int): float { + const image: BitMatrix = this.image; + + const maxI: int = image.getHeight(); + let stateCount: Int32Array = this.getCrossCheckStateCount(); + + // Start counting up from center + let i: int = startI; + while (i >= 0 && image.get(centerJ, i)) { + stateCount[2]++; + i--; + } + if (i < 0) { + return Float.NaN; + } + while (i >= 0 && !image.get(centerJ, i) && 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 Float.NaN; + } + while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return Float.NaN; } - /** - *

This is called when a horizontal scan finds a possible alignment pattern. It will - * cross check with a vertical scan, and if successful, will, ah, cross-cross-check - * with another horizontal scan. This is needed primarily to locate the real horizontal - * center of the pattern in cases of extreme skew. - * And then we cross-cross-cross check with another diagonal scan.

- * - *

If that succeeds the finder pattern location is added to a list that tracks - * the number of times each location has been nearly-matched as a finder pattern. - * Each additional find is more evidence that the location is in fact a finder - * pattern center - * - * @param stateCount reading state module counts from horizontal scan - * @param i row where finder pattern may be found - * @param j end of possible finder pattern in row - * @param pureBarcode true if in "pure barcode" mode - * @return true if a finder pattern candidate was found this time - */ - protected handlePossibleCenter(stateCount: Int32Array, i: number /*int*/, j: number /*int*/, pureBarcode: boolean): boolean { - const stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + - stateCount[4]; - let centerJ: number /*float*/ = FinderPatternFinder.centerFromEnd(stateCount, j); - let centerI: number /*float*/ = this.crossCheckVertical(i, /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal); - if (!isNaN(centerI)) { - // Re-cross check - centerJ = this.crossCheckHorizontal(/*(int) */Math.floor(centerJ), /*(int) */Math.floor(centerI), stateCount[2], stateCountTotal); - if (!isNaN(centerJ) && - (!pureBarcode || this.crossCheckDiagonal(/*(int) */Math.floor(centerI), /*(int) */Math.floor(centerJ), stateCount[2], stateCountTotal))) { - const estimatedModuleSize: number /*float*/ = stateCountTotal / 7.0; - let found: boolean = false; - const possibleCenters = this.possibleCenters; - for (let index = 0, length = possibleCenters.length; index < length; index++) { - const center: FinderPattern = possibleCenters[index]; - // Look for about the same center and module size: - if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { - possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); - found = true; - break; - } - } - if (!found) { - const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); - possibleCenters.push(point); - if (this.resultPointCallback !== null && this.resultPointCallback !== undefined) { - this.resultPointCallback.foundPossibleResultPoint(point); - } - } - return true; - } - } - return false; + // Now also count down from center + i = startI + 1; + while (i < maxI && image.get(centerJ, i)) { + stateCount[2]++; + i++; + } + if (i === maxI) { + return Float.NaN; + } + while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (i === maxI || stateCount[3] >= maxCount) { + return Float.NaN; + } + while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return Float.NaN; } - /** - * @return number of rows we could safely skip during scanning, based on the first - * two finder patterns that have been located. In some cases their position will - * allow us to infer that the third pattern must lie below a certain point farther - * down in the image. - */ - private findRowSkip(): number /*int*/ { - const max = this.possibleCenters.length; - if (max <= 1) { - return 0; - } - let firstConfirmedCenter: ResultPoint = null; - for (const center of this.possibleCenters) { - if (center.getCount() >= FinderPatternFinder.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 /*(int) */Math.floor((Math.abs(firstConfirmedCenter.getX() - center.getX()) - - Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2); - } - } - } - return 0; + // 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 + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { + return Float.NaN; } - /** - * @return true iff we have found at least 3 finder patterns that have been detected - * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the - * candidates is "pretty similar" - */ - private haveMultiplyConfirmedCenters(): boolean { - let confirmedCount = 0; - let totalModuleSize: number /*float*/ = 0.0; - const max = this.possibleCenters.length; - for (const pattern of this.possibleCenters) { - if (pattern.getCount() >= FinderPatternFinder.CENTER_QUORUM) { - confirmedCount++; - totalModuleSize += pattern.getEstimatedModuleSize(); - } - } - 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. - const average: number /*float*/ = totalModuleSize / max; - let totalDeviation: number /*float*/ = 0.0; - for (const pattern of this.possibleCenters) { - totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average); - } - return totalDeviation <= 0.05 * totalModuleSize; + return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, i) : Float.NaN; + } + + /** + *

Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical, + * except it reads horizontally instead of vertically. This is used to cross-cross + * check a vertical cross check and locate the real center of the alignment pattern.

+ */ + private crossCheckHorizontal(startJ: int, centerI: int, maxCount: int, + originalStateCountTotal: int): float { + const image: BitMatrix = this.image; + + const maxJ: int = image.getWidth(); + const stateCount: Int32Array = this.getCrossCheckStateCount(); + + let j: int = startJ; + while (j >= 0 && image.get(j, centerI)) { + stateCount[2]++; + j--; + } + if (j < 0) { + return Float.NaN; + } + while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return Float.NaN; + } + while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) { + stateCount[0]++; + j--; + } + if (stateCount[0] > maxCount) { + return Float.NaN; } - /** - * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are - * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module - * size differs from the average among those patterns the least - * @throws NotFoundException if 3 such finder patterns do not exist - */ - private selectBestPatterns(): FinderPattern[] /*throws NotFoundException */ { - - const startSize = this.possibleCenters.length; - if (startSize < 3) { - // Couldn't find enough finder patterns - throw new NotFoundException(); - } + j = startJ + 1; + while (j < maxJ && image.get(j, centerI)) { + stateCount[2]++; + j++; + } + if (j === maxJ) { + return Float.NaN; + } + while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j === maxJ || stateCount[3] >= maxCount) { + return Float.NaN; + } + while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return Float.NaN; + } - const possibleCenters = this.possibleCenters; - - let average: float; - // 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 - let totalModuleSize: float = 0.0; - let square: float = 0.0; - for (const center of this.possibleCenters) { - const size: float = center.getEstimatedModuleSize(); - totalModuleSize += size; - square += size * size; - } - average = totalModuleSize / startSize; - let stdDev: float = Math.sqrt(square / startSize - average * average); - - possibleCenters.sort( - /** - *

Orders by furthest from average

- */ - // FurthestFromAverageComparator implements Comparator - (center1: FinderPattern, center2: FinderPattern) => { - const dA: float = Math.abs(center2.getEstimatedModuleSize() - average); - const dB: float = Math.abs(center1.getEstimatedModuleSize() - average); - return dA < dB ? -1 : dA > dB ? 1 : 0; - }); - - const limit: float = Math.max(0.2 * average, stdDev); - - for (let i = 0; i < possibleCenters.length && possibleCenters.length > 3; i++) { - const pattern: FinderPattern = possibleCenters[i]; - if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) { - possibleCenters.splice(i, 1); - i--; - } - } - } + // If we found a finder-pattern-like section, but its size is significantly different than + // the original, assume it's a false positive + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return Float.NaN; + } - if (possibleCenters.length > 3) { - // Throw away all but those first size candidate points we found. + return FinderPatternFinder.foundPatternCross(stateCount) ? FinderPatternFinder.centerFromEnd(stateCount, j) : Float.NaN; + } + + /** + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @param pureBarcode ignored + * @return true if a finder pattern candidate was found this time + * @deprecated only exists for backwards compatibility + * @see #handlePossibleCenter(Int32Array, int, int) + * @Deprecated + */ + protected /* final */ handlePossibleCenterX(stateCount: Int32Array, i: int, j: int, pureBarcode: boolean): boolean { + return this.handlePossibleCenter(stateCount, i, j); + } + + /** + *

This is called when a horizontal scan finds a possible alignment pattern. It will + * cross check with a vertical scan, and if successful, will, ah, cross-cross-check + * with another horizontal scan. This is needed primarily to locate the real horizontal + * center of the pattern in cases of extreme skew. + * And then we cross-cross-cross check with another diagonal scan.

+ * + *

If that succeeds the finder pattern location is added to a list that tracks + * the number of times each location has been nearly-matched as a finder pattern. + * Each additional find is more evidence that the location is in fact a finder + * pattern center + * + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @return true if a finder pattern candidate was found this time + */ + protected /* final */ handlePossibleCenter(stateCount: Int32Array, i: int, j: int): boolean { + const stateCountTotal: int = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + + stateCount[4]; + let centerJ: float = FinderPatternFinder.centerFromEnd(stateCount, j); + const centerI: float = this.crossCheckVertical(i, centerJ, stateCount[2], stateCountTotal); + if (!Float.isNaN(centerI)) { + // Re-cross check + centerJ = this.crossCheckHorizontal(Math.trunc(centerJ), Math.trunc(centerI), stateCount[2], stateCountTotal); + if (!Float.isNaN(centerJ) && this.crossCheckDiagonal(Math.trunc(centerI), Math.trunc(centerJ))) { + const estimatedModuleSize: float = stateCountTotal / 7.0; + let found: boolean = false; + for (let index: int = 0; index < this.possibleCenters.length; index++) { + const center: FinderPattern = this.possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + this.possibleCenters[index] = center.combineEstimate(centerI, centerJ, estimatedModuleSize); + found = true; + break; + } + } + if (!found) { + const point: FinderPattern = new FinderPattern(centerJ, centerI, estimatedModuleSize); + this.possibleCenters.push(point); + if (this.resultPointCallback != null) { + this.resultPointCallback.foundPossibleResultPoint(point); + } + } + return true; + } + } + return false; + } + + /** + * @return number of rows we could safely skip during scanning, based on the first + * two finder patterns that have been located. In some cases their position will + * allow us to infer that the third pattern must lie below a certain point farther + * down in the image. + */ + private findRowSkip(): int { + const max: int = this.possibleCenters.length; + if (max <= 1) { + return 0; + } + let firstConfirmedCenter: ResultPoint = null; + for (const center/*: FinderPattern*/ of this.possibleCenters) { + if (center.getCount() >= FinderPatternFinder.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 /* TYPESCRIPTPORT: Math.trunc here to emulate Java's `int` cast see CONTRIBUTING */ Math.trunc((Math.abs(firstConfirmedCenter.getX() - center.getX()) - + Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2); + } + } + } + return 0; + } + + /** + * @return true iff we have found at least 3 finder patterns that have been detected + * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the + * candidates is "pretty similar" + */ + private haveMultiplyConfirmedCenters(): boolean { + let confirmedCount: int = 0; + let totalModuleSize: float = 0.0; + const max: int = this.possibleCenters.length; + for (const pattern/*: FinderPattern*/ of this.possibleCenters) { + if (pattern.getCount() >= FinderPatternFinder.CENTER_QUORUM) { + confirmedCount++; + totalModuleSize += pattern.getEstimatedModuleSize(); + } + } + 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. + const average: float = totalModuleSize / max; + let totalDeviation: float = 0.0; + for (const pattern/*: FinderPattern*/ of this.possibleCenters) { + totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average); + } + return totalDeviation <= 0.05 * totalModuleSize; + } + + /** + * Get square of distance between a and b. + */ + private static squaredDistance(a: FinderPattern, b: FinderPattern): double { + const x: double = a.getX() - b.getX(); + const y: double = a.getY() - b.getY(); + return x * x + y * y; + } + + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those have similar module size and form a shape closer to a isosceles right triangle. + * @throws {@link NotFoundException} if 3 such finder patterns do not exist + */ + private selectBestPatterns(): FinderPattern[] { + + const startSize: int = this.possibleCenters.length; + if (startSize < 3) { + // Couldn't find enough finder patterns + throw NotFoundException.getNotFoundInstance(); + } - let totalModuleSize: float = 0.0; - for (const possibleCenter of possibleCenters) { - totalModuleSize += possibleCenter.getEstimatedModuleSize(); - } + this.possibleCenters.sort(FinderPatternFinder.moduleComparator.compare); + + let distortion: double = Double.MAX_VALUE; + const squares: Float64Array = Float64Array.from({ length: 3 }); + const bestPatterns: FinderPattern[] = new FinderPattern[3]; + + for (let i /*int*/ = 0; i < this.possibleCenters.length - 2; i++) { + const fpi: FinderPattern = this.possibleCenters[i]; + const minModuleSize: float = fpi.getEstimatedModuleSize(); + + for (let j /*int*/ = i + 1; j < this.possibleCenters.length - 1; j++) { + const fpj: FinderPattern = this.possibleCenters[j]; + const squares0: double = FinderPatternFinder.squaredDistance(fpi, fpj); + + for (let k /*int*/ = j + 1; k < this.possibleCenters.length; k++) { + const fpk: FinderPattern = this.possibleCenters[k]; + const maxModuleSize: float = fpk.getEstimatedModuleSize(); + if (maxModuleSize > minModuleSize * 1.4) { + // module size is not similar + continue; + } + + squares[0] = squares0; + squares[1] = FinderPatternFinder.squaredDistance(fpj, fpk); + squares[2] = FinderPatternFinder.squaredDistance(fpi, fpk); + Arrays.sort(squares); + + // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). + // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, + // we need to check both two equal sides separately. + // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity + // from isosceles right triangle. + const d: double = Math.abs(squares[2] - 2 * squares[1]) + Math.abs(squares[2] - 2 * squares[0]); + if (d < distortion) { + distortion = d; + bestPatterns[0] = fpi; + bestPatterns[1] = fpj; + bestPatterns[2] = fpk; + } + } + } + } - average = totalModuleSize / possibleCenters.length; - - possibleCenters.sort( - /** - *

Orders by {@link FinderPattern#getCount()}, descending.

- */ - // CenterComparator implements Comparator - (center1: FinderPattern, center2: FinderPattern) => { - if (center2.getCount() === center1.getCount()) { - const dA: float = Math.abs(center2.getEstimatedModuleSize() - average); - const dB: float = Math.abs(center1.getEstimatedModuleSize() - average); - return dA < dB ? 1 : dA > dB ? -1 : 0; - } else { - return center2.getCount() - center1.getCount(); - } - }); + if (distortion === Double.MAX_VALUE) { + throw NotFoundException.getNotFoundInstance(); + } - possibleCenters.splice(3); // this is not realy necessary as we only return first 3 anyway - } + return bestPatterns; + } - return [ - possibleCenters[0], - possibleCenters[1], - possibleCenters[2] - ]; - } } diff --git a/src/core/util/Arrays.ts b/src/core/util/Arrays.ts index e487efaa..1cd8a42e 100644 --- a/src/core/util/Arrays.ts +++ b/src/core/util/Arrays.ts @@ -176,4 +176,8 @@ export default class Arrays { public static numberComparator(a: number, b: number) { return a - b; } + + public static sort(squares: Array | Float64Array) { + return squares.sort(); + } } diff --git a/src/core/util/BarcodeFormaHelpers.ts b/src/core/util/BarcodeFormaHelpers.ts new file mode 100644 index 00000000..86293c3f --- /dev/null +++ b/src/core/util/BarcodeFormaHelpers.ts @@ -0,0 +1,6 @@ +import BarcodeFormat from '../BarcodeFormat'; + +export function isBarcodeFormatValue(num: number) { + const values = Object.keys(BarcodeFormat).map(i => Number(i)).filter(Number.isInteger); + return values.includes(num); +} diff --git a/src/core/util/Collections.ts b/src/core/util/Collections.ts index 67142fb7..f863a002 100644 --- a/src/core/util/Collections.ts +++ b/src/core/util/Collections.ts @@ -1,4 +1,5 @@ -import { Collection, int } from '../../customTypings'; +import { Collection, int, List } from '../../customTypings'; +import Comparator from './Comparator'; export default class Collections { @@ -9,6 +10,16 @@ export default class Collections { return [item]; } + /** + * Sorts the specified list according to the order induced by the specified comparator. + */ + static sort( + list: List | Array | TToBeCompared[], + comparator: Comparator, + ) { + list.sort(comparator.compare); + } + /** * The min(Collection, Comparator) method is used to return the minimum element of the given collection, according to the order induced by the specified comparator. */ diff --git a/src/core/util/Comparator.ts b/src/core/util/Comparator.ts new file mode 100644 index 00000000..5b39452b --- /dev/null +++ b/src/core/util/Comparator.ts @@ -0,0 +1,13 @@ +import { int } from 'src/customTypings'; + +/** + * Java Comparator interface polyfill. + */ +export default interface Comparator { + /** + * Compares its two arguments for order. Returns a negative integer, zero, + * or a positive integer as the first argument is less than, equal to, + * or greater than the second. + */ + compare(a: T, b: T): int; +} diff --git a/src/core/util/Double.ts b/src/core/util/Double.ts new file mode 100644 index 00000000..e5bda203 --- /dev/null +++ b/src/core/util/Double.ts @@ -0,0 +1,5 @@ +import { double } from 'src/customTypings'; + +export default class Double { + static MAX_VALUE: double = 1.7 * 10 ^ 308; +} diff --git a/src/core/util/Float.ts b/src/core/util/Float.ts index 86f8458d..4675f02f 100644 --- a/src/core/util/Float.ts +++ b/src/core/util/Float.ts @@ -1,3 +1,5 @@ +import { float, int } from 'src/customTypings'; + /** * Ponyfill for Java's Float class. */ @@ -8,6 +10,8 @@ export default class Float { */ static MAX_VALUE: number = Number.MAX_SAFE_INTEGER; + static NaN = NaN; + /** * SincTS has no difference between int and float, there's all numbers, * this is used only to polyfill Java code. @@ -15,4 +19,14 @@ export default class Float { public static floatToIntBits(f: number): number { return f; } + + public static isNaN(num: number) { + return isNaN(num); + } + + public static compare(x: float, y: float): int { + if (x === y) return 0; + if (x < y) return -1; + if (x > y) return 1; + } } diff --git a/src/core/util/Integer.ts b/src/core/util/Integer.ts index 80a9c36e..13f8556d 100644 --- a/src/core/util/Integer.ts +++ b/src/core/util/Integer.ts @@ -1,3 +1,5 @@ +import { int } from "src/customTypings"; + /** * Ponyfill for Java's Integer class. */ @@ -6,6 +8,21 @@ export default class Integer { static MIN_VALUE_32_BITS = -2147483648; static MAX_VALUE: number = Number.MAX_SAFE_INTEGER; + /** + * Parameter : + * x : the first int to compare + * y : the second int to compare + * Return : + * This method returns the value zero if (x==y), + * if (x < y) then it returns a value less than zero + * and if (x > y) then it returns a value greater than zero. + */ + static compare(x: int, y: int): number { + if (x === y) return 0; + if (x < y) return -1; + if (x > y) return 1; + } + public static numberOfTrailingZeros(i: number): number { let y: number; diff --git a/src/core/util/StringBuilder.ts b/src/core/util/StringBuilder.ts index 1d4f34df..58b085cd 100644 --- a/src/core/util/StringBuilder.ts +++ b/src/core/util/StringBuilder.ts @@ -1,5 +1,4 @@ import CharacterSetECI from '../common/CharacterSetECI'; -import StringEncoding from './StringEncoding'; import { int, char } from '../../customTypings'; import StringUtils from '../common/StringUtils'; diff --git a/src/customTypings.ts b/src/customTypings.ts index 78d284aa..b3765644 100644 --- a/src/customTypings.ts +++ b/src/customTypings.ts @@ -11,7 +11,9 @@ export declare type byte = number; export declare type short = number; export declare type int = number; +export declare type long = number; export declare type float = number; +export declare type double = number; // special formats export type char = number; diff --git a/src/index.ts b/src/index.ts index 372596c5..d069610b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,5 +114,20 @@ export { default as Code128Reader } from './core/oned/Code128Reader'; export { default as ITFReader } from './core/oned/ITFReader'; export { default as Code39Reader } from './core/oned/Code39Reader'; export { default as RSS14Reader } from './core/oned/rss/RSS14Reader'; -export { default as RSSExpandedReader } from './core/oned/rss/expanded/RSSExpandedReader'; export { default as MultiFormatOneDReader } from './core/oned/MultiFormatOneDReader'; + + +// core/oned/rss/expanded +export { default as RSSExpandedReader } from './core/oned/rss/expanded/RSSExpandedReader'; +export { default as AbstractExpandedDecoder } from './core/oned/rss/expanded/decoders/AbstractExpandedDecoder'; +export { createDecoder as createAbstractExpandedDecoder } from './core/oned/rss/expanded/decoders/AbstractExpandedDecoderComplement'; + + +// core/multi +export { default as MultipleBarcodeReader } from './core/multi/MultipleBarcodeReader'; + + +// core/multi/qrcode +export { default as QRCodeMultiReader } from './core/multi/qrcode/QRCodeMultiReader'; +export { default as MultiDetector } from './core/multi/qrcode/detector/MultiDetector'; +export { default as MultiFinderPatternFinder } from './core/multi/qrcode/detector/MultiFinderPatternFinder'; diff --git a/src/test/core/common/AbstractBlackBox.ts b/src/test/core/common/AbstractBlackBox.ts index f1bb89f3..b2a8f3fb 100644 --- a/src/test/core/common/AbstractBlackBox.ts +++ b/src/test/core/common/AbstractBlackBox.ts @@ -16,19 +16,23 @@ /*package com.google.zxing.common;*/ +import { + BarcodeFormat, + BinaryBitmap, + DecodeHintType, + HybridBinarizer, + LuminanceSource, + Reader, + Result, + ResultMetadataType, + ZXingStringEncoding +} from '@zxing/library'; +import * as fs from 'fs'; +import * as path from 'path'; +import TestResult from '../common/TestResult'; +import SharpImageLuminanceSource from '../SharpImageLuminanceSource'; import { assertEquals } from '../util/AssertUtils'; import SharpImage from '../util/SharpImage'; -import SharpImageLuminanceSource from '../SharpImageLuminanceSource'; -import { BarcodeFormat } from '@zxing/library'; -import { BinaryBitmap } from '@zxing/library'; -import { DecodeHintType } from '@zxing/library'; -import { LuminanceSource } from '@zxing/library'; -import { Reader } from '@zxing/library'; -import { Result } from '@zxing/library'; -import { ResultMetadataType } from '@zxing/library'; -import TestResult from '../common/TestResult'; -import { HybridBinarizer } from '@zxing/library'; -import { ZXingStringEncoding } from '@zxing/library'; /*import javax.imageio.ImageIO;*/ diff --git a/src/test/core/multi/qrcode/MultiQRCode.spec.ts b/src/test/core/multi/qrcode/MultiQRCode.spec.ts new file mode 100644 index 00000000..612a60ab --- /dev/null +++ b/src/test/core/multi/qrcode/MultiQRCode.spec.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2016 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. + */ + +import { + BarcodeFormat, + BinaryBitmap, + HybridBinarizer, + LuminanceSource, + MultipleBarcodeReader, + QRCodeMultiReader, + Result, + ResultMetadataType +} from '@zxing/library'; +import * as path from 'path'; +import Arrays from '../../../../core/util/Arrays'; +import { Collection, List } from '../../../../customTypings'; +import AbstractBlackBoxSpec from '../../common/AbstractBlackBox'; +import SharpImageLuminanceSource from '../../SharpImageLuminanceSource'; +import { assertArrayEquals, assertEquals, assertNotNull } from '../../util/AssertUtils'; +import SharpImage from '../../util/SharpImage'; + +// package com.google.zxing.multi.qrcode; + +// import javax.imageio.ImageIO; +// import java.awt.image.BufferedImage; +// import java.nio.file.Path; +// import java.util.Arrays; +// import java.util.Collection; +// import java.util.HashSet; +// import java.util.List; + +// import com.google.zxing.BarcodeFormat; +// import com.google.zxing.BinaryBitmap; +// import com.google.zxing.BufferedImageLuminanceSource; +// import com.google.zxing.LuminanceSource; +// import com.google.zxing.Result; +// import com.google.zxing.ResultMetadataType; +// import com.google.zxing.ResultPoint; +// import com.google.zxing.common.AbstractBlackBoxTestCase; +// import com.google.zxing.common.HybridBinarizer; +// import com.google.zxing.multi.MultipleBarcodeReader; +// import org.junit.Assert; +// import org.junit.Test; + +/** + * Tests {@link QRCodeMultiReader}. + */ +describe('MultiQRCodeTestCase', () => { + + it('testMultiQRCodes', async () => { + // Very basic test for now + const testBase: string = AbstractBlackBoxSpec.buildTestBase('src/test/resources/blackbox/multi-qrcode-1'); + + const testImage: string = path.resolve(testBase, '1.png'); + const image: SharpImage = await SharpImage.loadWithRotation(testImage, 0); + const source: LuminanceSource = new SharpImageLuminanceSource(image); + const bitmap: BinaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + + const reader: MultipleBarcodeReader = new QRCodeMultiReader(); + const results: Result[] = reader.decodeMultiple(bitmap); + assertNotNull(results); + assertEquals(4, results.length); + + const barcodeContents: Collection = []; + for (const result of results) { + barcodeContents.push(result.getText()); + assertEquals(BarcodeFormat.QR_CODE, result.getBarcodeFormat()); + assertNotNull(result.getResultMetadata()); + } + const expectedContents: Collection = []; + // TYPESCRIPTPORT: following lines are in different order from Java because JavaScript's push works in a different way HashSet<>.add, but the results are actually the same + expectedContents.push('You get to CREATE OUR JOURNAL PROMPT FOR THE DAY! Yay! Way to go! '); + expectedContents.push('You earned the class 5 EXTRA MINUTES OF RECESS!! Fabulous!! Way to go!!'); + expectedContents.push('You earned the class a 5 MINUTE DANCE PARTY!! Awesome! Way to go! Let\'s boogie!'); + expectedContents.push('You get to SIT AT MRS. SIGMON\'S DESK FOR A DAY!! Awesome!! Way to go!! Guess I better clean up! :)'); + assertArrayEquals(expectedContents, barcodeContents); + }); + + it('testProcessStructuredAppend', () => { + const sa1: Result = new Result('SA1', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa2: Result = new Result('SA2', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + const sa3: Result = new Result('SA3', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + sa1.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, 2); + sa1.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); + sa2.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (1 << 4) + 2); + sa2.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); + sa3.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE, (2 << 4) + 2); + sa3.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); + + const nsa: Result = new Result('NotSA', new Uint8Array(0), [], BarcodeFormat.QR_CODE); + nsa.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, 'L'); + + const inputs: List = Arrays.asList(sa3, sa1, nsa, sa2); + + const results: List = QRCodeMultiReader.processStructuredAppend(inputs); + assertNotNull(results); + assertEquals(2, results.length); + + const barcodeContents: Collection = []; + for (const result of results) { + barcodeContents.push(result.getText()); + } + const expectedContents: Collection = []; + // TYPESCRIPTPORT: following lines are in different order from Java because JavaScript's push works in a different way HashSet<>.add, but the results are actually the same + expectedContents.push('NotSA'); + expectedContents.push('SA1SA2SA3'); + assertArrayEquals(expectedContents, barcodeContents); + }); +}); diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts b/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts index 760f5c08..69ebfd4b 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.spec.ts @@ -1,4 +1,4 @@ -import BitArray from '../../../../../core/common/BitArray'; +import { BitArray } from '@zxing/library'; import { assertEquals } from '../../../util/AssertUtils'; import BinaryUtil from './BinaryUtil'; diff --git a/src/test/core/oned/rss/expanded/BinaryUtil.ts b/src/test/core/oned/rss/expanded/BinaryUtil.ts index 66b21924..7fb0bdc7 100644 --- a/src/test/core/oned/rss/expanded/BinaryUtil.ts +++ b/src/test/core/oned/rss/expanded/BinaryUtil.ts @@ -1,5 +1,4 @@ -import { BitArray } from '../../../../..'; -import IllegalStateException from '../../../../../core/IllegalStateException'; +import { BitArray, IllegalStateException } from '@zxing/library'; import StringBuilder from '../../../../../core/util/StringBuilder'; /* diff --git a/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts b/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts index 373e7fd8..c2958832 100644 --- a/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts +++ b/src/test/core/oned/rss/expanded/ExpandedInformationDecoder.spec.ts @@ -1,10 +1,3 @@ -import { BitArray } from '../../../../..'; -import AbstractExpandedDecoder from '../../../../../core/oned/rss/expanded/decoders/AbstractExpandedDecoder'; -import { assertEquals } from '../../../util/AssertUtils'; -import BinaryUtil from './BinaryUtil'; - - - /* * Copyright (C) 2010 ZXing authors * @@ -39,6 +32,10 @@ import BinaryUtil from './BinaryUtil'; // import org.junit.Assert; // import org.junit.Test; +import { BitArray, AbstractExpandedDecoder, createAbstractExpandedDecoder } from '@zxing/library'; +import { assertEquals } from '../../../util/AssertUtils'; +import BinaryUtil from './BinaryUtil'; + /** * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) @@ -48,7 +45,7 @@ it('ExpandedInformationDecoderTest', () => { it('testNoAi', () => { let information: BitArray = BinaryUtil.buildBitArrayFromString(' .......X ..XX..X. X.X....X .......X ....'); - let decoder: AbstractExpandedDecoder = AbstractExpandedDecoder.createDecoder(information); + let decoder: AbstractExpandedDecoder = createAbstractExpandedDecoder(information); let decoded: String = decoder.parseInformation(); assertEquals('(10)12A', decoded); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts index f69457a9..2c4d0283 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox1.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,8 +42,8 @@ class RSSExpandedBlackBox1Spec extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox1Spec', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox1Spec(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts index 75a470d9..3cae643c 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox2.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,9 +42,9 @@ class RSSExpandedBlackBox2TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox2TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox2TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts index 42c60c91..0ff4fa4e 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedBlackBox3.spec.ts @@ -26,8 +26,7 @@ // package com.google.zxing.oned; -import BarcodeFormat from '../../../../../core/BarcodeFormat'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /** @@ -43,8 +42,8 @@ class RSSExpandedBlackBox3TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedBlackBox3TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedBlackBox3TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts index fc92f186..2e24e846 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox1.spec.ts @@ -1,4 +1,4 @@ -import { BarcodeFormat, MultiFormatReader } from '../../../../..'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /* @@ -48,9 +48,9 @@ class RSSExpandedStackedBlackBox1TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedStackedBlackBox1TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedStackedBlackBox1TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts index 755350f9..9bf4fb8b 100644 --- a/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts +++ b/src/test/core/oned/rss/expanded/RSSExpandedStackedBlackBox2.spec.ts @@ -1,5 +1,4 @@ -import { BarcodeFormat } from '../../../../..'; -import MultiFormatReader from '../../../../../core/MultiFormatReader'; +import { BarcodeFormat, MultiFormatReader } from '@zxing/library'; import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; /* @@ -49,9 +48,9 @@ class RSSExpandedStackedBlackBox2TestCase extends AbstractBlackBoxSpec { } describe('RSSExpandedStackedBlackBox2TestCase', () => { - it('testBlackBox', done => { + it('testBlackBox', async () => { const test = new RSSExpandedStackedBlackBox2TestCase(); - return test.testBlackBox(done); + await test.testBlackBox(); }); }); diff --git a/src/test/core/oned/rss/expanded/TestCaseUtil.ts b/src/test/core/oned/rss/expanded/TestCaseUtil.ts index 96d28138..0a596030 100644 --- a/src/test/core/oned/rss/expanded/TestCaseUtil.ts +++ b/src/test/core/oned/rss/expanded/TestCaseUtil.ts @@ -1,7 +1,7 @@ -import { BinaryBitmap, GlobalHistogramBinarizer } from "../../../../.."; -import AbstractBlackBoxSpec from "../../../common/AbstractBlackBox"; -import SharpImageLuminanceSource from "../../../SharpImageLuminanceSource"; -import SharpImage from "../../../util/SharpImage"; +import { BinaryBitmap, GlobalHistogramBinarizer } from '@zxing/library'; +import AbstractBlackBoxSpec from '../../../common/AbstractBlackBox'; +import SharpImageLuminanceSource from '../../../SharpImageLuminanceSource'; +import SharpImage from '../../../util/SharpImage'; /* * Copyright (C) 2012 ZXing authors diff --git a/src/test/core/util/AssertUtils.ts b/src/test/core/util/AssertUtils.ts index a04df3ae..8f8c127d 100644 --- a/src/test/core/util/AssertUtils.ts +++ b/src/test/core/util/AssertUtils.ts @@ -20,7 +20,7 @@ export default class AssertUtils { } -export const assertEquals = assert.strictEqual; +export const assertEquals: any = assert.strictEqual; export const assertArrayEquals = (a: Array | Uint8Array | Int32Array, b: Array | Uint8Array | Int32Array) => assert.deepStrictEqual(a, b); export const assertFalse = x => assert.strictEqual(!!x, false); export const assertTrue = x => assert.strictEqual(!!x, true); diff --git a/src/test/core/util/SharpImage.ts b/src/test/core/util/SharpImage.ts index 91b29e7d..38d2665d 100644 --- a/src/test/core/util/SharpImage.ts +++ b/src/test/core/util/SharpImage.ts @@ -50,6 +50,19 @@ export default class SharpImage { return new SharpImage(wrapper, undefined, undefined, undefined); } + public static async loadAsync(path: string): Promise { + + const wrapper = sharp(path).raw(); + + const { data, info } = await wrapper.toBuffer({ resolveWithObject: true }); + + const width = info.width; + const height = info.height; + const buffer = SharpImage.toGrayscaleBuffer(new Uint8ClampedArray(data.buffer), info.width, info.height, info.channels); + + return new SharpImage(wrapper, buffer, width, height); + } + public static async loadAsBitMatrix(path: string): Promise { const wrapper = sharp(path).raw(); @@ -66,11 +79,20 @@ export default class SharpImage { const height = info.height; const grayscaleBuffer = SharpImage.toGrayscaleBuffer(new Uint8ClampedArray(data.buffer), width, height, channels); // const image = new SharpImage(wrapper, grayscaleBuffer, info.width, info.height) + + return SharpImage.bufferToBitMatrix(grayscaleBuffer, width, height); + } + + private static bufferToBitMatrix( + imageBuffer: Uint8ClampedArray, + width: number, + height: number + ): BitMatrix { const matrix = new BitMatrix(width, height); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - const pixel = grayscaleBuffer[y * width + x]; + const pixel = imageBuffer[y * width + x]; if (pixel <= 0x7F) { matrix.set(x, y); } diff --git a/src/test/resources/blackbox/multi-qrcode-1/1.png b/src/test/resources/blackbox/multi-qrcode-1/1.png new file mode 100644 index 00000000..c82508b2 Binary files /dev/null and b/src/test/resources/blackbox/multi-qrcode-1/1.png differ diff --git a/yarn.lock b/yarn.lock index 02955950..74ee223f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -509,6 +509,11 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= +array-flatten@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" + integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== + array-from@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" @@ -640,6 +645,11 @@ base64-js@^1.0.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base64id@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" @@ -677,13 +687,14 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== -bl@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" - integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== +bl@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" + integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" blob@0.0.5: version "0.0.5" @@ -859,6 +870,14 @@ buffer@^5.0.6: base64-js "^1.0.2" ieee754 "^1.1.4" +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -987,7 +1006,7 @@ chokidar@^2.0.3: optionalDependencies: fsevents "^1.2.7" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== @@ -1098,21 +1117,21 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== +color-string@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" + integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" -color@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== +color@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== dependencies: color-convert "^1.9.1" - color-string "^1.5.2" + color-string "^1.5.4" colors@^1.1.0: version "1.3.3" @@ -1426,6 +1445,13 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -1594,13 +1620,20 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== dependencies: once "^1.4.0" +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + engine.io-client@~3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" @@ -2060,11 +2093,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-copy-file-sync@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz#11bf32c096c10d126e5f6b36d06eece776062918" - integrity sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ== - fs-minipass@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" @@ -2412,6 +2440,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -3182,6 +3215,13 @@ lru-cache@4.1.x: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -3295,6 +3335,11 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -3322,7 +3367,7 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@^1.2.5: +minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -3355,6 +3400,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -3406,7 +3456,7 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -3480,6 +3530,11 @@ node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-addon-api@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681" + integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg== + node-pre-gyp@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" @@ -3695,7 +3750,7 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= @@ -3940,25 +3995,24 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prebuild-install@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" - integrity sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg== +prebuild-install@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.0.0.tgz#669022bcde57c710a869e39c5ca6bf9cd207f316" + integrity sha512-h2ZJ1PXHKWZpp1caLw0oX9sagVpL2YTk+ZwInQbQ3QqNd4J03O6MpFNmMTJlkfgPENWqe5kP0WjQLqz5OjLfsw== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" github-from-package "0.0.0" - minimist "^1.2.0" - mkdirp "^0.5.1" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" node-abi "^2.7.0" noop-logger "^0.1.1" npmlog "^4.0.1" - os-homedir "^1.0.1" - pump "^2.0.1" + pump "^3.0.0" rc "^1.2.7" - simple-get "^2.7.0" - tar-fs "^1.13.0" + simple-get "^3.0.3" + tar-fs "^2.0.0" tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" @@ -4016,18 +4070,10 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -4139,7 +4185,7 @@ readable-stream@^1.1.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4152,6 +4198,15 @@ readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -4400,7 +4455,7 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4447,6 +4502,13 @@ semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -4480,20 +4542,20 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@^0.22.1: - version "0.22.1" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.22.1.tgz#a67c0e75567f03dd5a7861b901fec04072c5b0f4" - integrity sha512-lXzSk/FL5b/MpWrT1pQZneKe25stVjEbl6uhhJcTULm7PhmJgKKRbTDM/vtjyUuC/RLqL2PRyC4rpKwbv3soEw== +sharp@^0.26.3: + version "0.26.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.26.3.tgz#9de8577a986b22538e6e12ced1f7e8a53f9728de" + integrity sha512-NdEJ9S6AMr8Px0zgtFo1TJjMK/ROMU92MkDtYn2BBrDjIx3YfH9TUyGdzPC+I/L619GeYQc690Vbaxc5FPCCWg== dependencies: - color "^3.1.1" + array-flatten "^3.0.0" + color "^3.1.3" detect-libc "^1.0.3" - fs-copy-file-sync "^1.1.1" - nan "^2.13.2" + node-addon-api "^3.0.2" npmlog "^4.1.2" - prebuild-install "^5.3.0" - semver "^6.0.0" - simple-get "^3.0.3" - tar "^4.4.8" + prebuild-install "^6.0.0" + semver "^7.3.2" + simple-get "^4.0.0" + tar-fs "^2.1.1" tunnel-agent "^0.6.0" shebang-command@^1.2.0: @@ -4548,15 +4610,6 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== - dependencies: - decompress-response "^3.3.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-get@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.0.3.tgz#924528ac3f9d7718ce5e9ec1b1a69c0be4d62efa" @@ -4566,6 +4619,15 @@ simple-get@^3.0.3: once "^1.3.1" simple-concat "^1.0.0" +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -4881,6 +4943,13 @@ string_decoder@^1.0.3: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -4988,30 +5057,28 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" -tar-fs@^1.13.0: - version "1.16.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" - integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== +tar-fs@^2.0.0, tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" -tar-stream@^1.1.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" - integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== +tar-stream@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" + integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== dependencies: - bl "^1.0.0" - buffer-alloc "^1.2.0" - end-of-stream "^1.0.0" + bl "^4.0.3" + end-of-stream "^1.4.1" fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.1" - xtend "^4.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" -tar@^4, tar@^4.4.8: +tar@^4: version "4.4.10" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== @@ -5091,11 +5158,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-buffer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -5359,7 +5421,7 @@ useragent@2.3.0: lru-cache "4.1.x" tmp "0.0.x" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -5548,6 +5610,11 @@ yallist@^3.0.0, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"