mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-10-16 16:55:28 +00:00
:octocat: improve minimum version detection and related tests
This commit is contained in:
+45
-21
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user