:octocat: improve minimum version detection and related tests

This commit is contained in:
smiley
2023-03-25 19:37:37 +01:00
parent b30c369810
commit b79bf85570
9 changed files with 214 additions and 90 deletions
+45 -21
View File
@@ -13,6 +13,7 @@ namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Mode, Version};
use chillerlan\Settings\SettingsContainerInterface;
use function count;
use function sprintf;
/**
@@ -135,35 +136,48 @@ final class QRData{
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
public function estimateTotalBitLength():int{
$length = 4; // 4 bits for the terminator
$margin = 0;
$length = 0;
foreach($this->dataSegments as $segment){
// data length in bits of the current segment +4 bits for each mode descriptor
$length += ($segment->getLengthInBits() + Mode::getLengthBitsForVersion($segment::DATAMODE, 1) + 4);
if(!$segment instanceof ECI){
// mode length bits margin to the next breakpoint
$margin += ($segment instanceof Byte) ? 8 : 2;
}
// data length of the current segment
$length += $segment->getLengthInBits();
// +4 bits for the mode descriptor
$length += 4;
// Hanzi mode sets an additional 4 bit long subset identifier
if($segment instanceof Hanzi){
// Hanzi mode sets an additional 4 bit long subset identifier
$length += 4;
}
}
$provisionalVersion = null;
foreach($this->maxBitsForEcc as $version => $maxBits){
if($length <= $maxBits){
$provisionalVersion = $version;
}
}
foreach([9, 26, 40] as $breakpoint){
if($provisionalVersion !== null){
// length bits for the first breakpoint have already been added
if($breakpoint > 9){
$length += $margin;
// add character count indicator bits for the provisional version
foreach($this->dataSegments as $segment){
$length += Mode::getLengthBitsForVersion($segment::DATAMODE, $provisionalVersion);
}
if($length < $this->maxBitsForEcc[$breakpoint]){
// it seems that in some cases the estimated total length is not 100% accurate,
// so we substract 4 bits from the total when not in mixed mode
if(count($this->dataSegments) <= 1){
$length -= 4;
}
// we've got a match!
// or let's see if there's a higher version number available
if($length <= $this->maxBitsForEcc[$provisionalVersion] || isset($this->maxBitsForEcc[($provisionalVersion + 1)])){
return $length;
}
}
throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length));
@@ -199,11 +213,10 @@ final class QRData{
* @throws \chillerlan\QRCode\QRCodeException on data overflow
*/
private function writeBitBuffer():void{
$version = $this->version->getVersionNumber();
$MAX_BITS = $this->maxBitsForEcc[$version];
$MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version);
foreach($this->dataSegments as $segment){
$segment->write($this->bitBuffer, $version);
$segment->write($this->bitBuffer, $this->version->getVersionNumber());
}
// overflow, likely caused due to invalid version setting
@@ -215,7 +228,7 @@ final class QRData{
// add terminator (ISO/IEC 18004:2000 Table 2)
if(($this->bitBuffer->getLength() + 4) <= $MAX_BITS){
$this->bitBuffer->put(0, 4);
$this->bitBuffer->put(Mode::TERMINATOR, 4);
}
// Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion
@@ -223,6 +236,11 @@ final class QRData{
// if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long
// by the addition of padding bits with binary value 0
while(($this->bitBuffer->getLength() % 8) !== 0){
if($this->bitBuffer->getLength() === $MAX_BITS){
break;
}
$this->bitBuffer->putBit(false);
}
@@ -231,12 +249,18 @@ final class QRData{
// Codewords 11101100 and 00010001 alternately.
$alternate = false;
while($this->bitBuffer->getLength() <= $MAX_BITS){
while(($this->bitBuffer->getLength() + 8) <= $MAX_BITS){
$this->bitBuffer->put(($alternate) ? 0b00010001 : 0b11101100, 8);
$alternate = !$alternate;
}
// In certain versions of symbol, it may be necessary to add 3, 4 or 7 Remainder Bits (all zeros)
// to the end of the message in order exactly to fill the symbol capacity
while($this->bitBuffer->getLength() <= $MAX_BITS){
$this->bitBuffer->putBit(false);
}
}
}