| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- # Custom `QROutputInterface`
- Let's suppose that we want to create our own output interface because there's no built-in output class that supports the format we need for our application.
- In this example we'll create a string output class that outputs the coordinates for each module, separated by module type.
- ## Class skeleton
- We'll start with a skeleton that extends `QROutputAbstract` and implements the methods that are required by `QROutputInterface`:
- ```php
- class MyCustomOutput extends QROutputAbstract{
- public static function moduleValueIsValid($value):bool{}
- protected function prepareModuleValue($value){}
- protected function getDefaultModuleValue(bool $isDark){}
- public function dump(string $file = null){}
- }
- ```
- ## Module values
- The validator should check whether the given input value and range is valid for the output class and if it can be given to the `QROutputAbstract::prepareModuleValue()` method.
- For example in the built-in GD output it would check if the value is an array that has a minimum of 3 elements (for RGB), each of which is numeric.
- In this example we'll accept string values, the characters `a-z` (case-insensitive) and a hyphen `-`:
- ```php
- public static function moduleValueIsValid($value):bool{
- return is_string($value) && preg_match('/^[a-z-]+$/i', $value) === 1;
- }
- ```
- To prepare the final module substitute, we should transform the given (validated) input value in a way so that it can be accessed without any further calls or transformation.
- In the built-in output for example this means it would return an `ImagickPixel` instance or the integer value returned by `imagecolorallocate()` on the current `GdImage` instance.
- For our example, we'll lowercase the validated string:
- ```php
- protected function prepareModuleValue($value):string{
- return strtolower($value);
- }
- ```
- Finally, we need to provide a default value for dark and light, we can call `prepareModuleValue()` here if necessary.
- We'll return an empty string `''` as we're going to use the `QROutputInterface::LAYERNAMES` constant for non-existing values
- (returning `null` would run into an exception in `QROutputAbstract::getModuleValue()`).
- ```php
- protected function getDefaultModuleValue(bool $isDark):string{
- return '';
- }
- ```
- ## Transform the output
- In our example, we want to collect the modules by type and have the collections listed under a header for each type.
- In order to do so, we need to collect the modules per `$M_TYPE` before we can render the final output.
- ```php
- public function dump(string $file = null):string{
- $collections = [];
- // loop over the matrix and collect the modules per layer
- foreach($this->matrix->getMatrix() as $y => $row){
- foreach($row as $x => $M_TYPE){
- $collections[$M_TYPE][] = $this->module($x, $y, $M_TYPE);
- }
- }
- // build the final output
- $out = [];
- foreach($collections as $M_TYPE => $collection){
- $name = ($this->getModuleValue($M_TYPE) ?: $this::LAYERNAMES[$M_TYPE]);
- // the section header
- $out[] = sprintf("%s (%012b)\n", $name, $M_TYPE);
- // the list of modules
- $out[] = sprintf("%s\n", implode("\n", $collection));
- }
- return implode("\n", $out);
- }
- ```
- Speaking of option settings, there's also `QROptions::$connectPaths` which we haven't taken care of yet - the good news is that we don't need to as it is already implemented!
- We'll modify the above `dump()` method to use `QROutputAbstract::collectModules()` instead.
- The module collector calls a method `moduleTransform()` internally, which is called with 4 parameters:
- - `$x` : current column
- - `$y` : current row
- - `$M_TYPE` : field value
- - `$M_TYPE_LAYER`: (possibly modified) field value that acts as layer id
- We'## introduce another method that handles the module rendering, which incooperates handling of the `QROptions::$drawLightModules` setting:
- ```php
- protected function moduleTransform(int $x, int $y, int $M_TYPE, int $M_TYPE_LAYER):string{
- if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
- return '';
- }
- return sprintf('x: %s, y: %s', $x, $y);
- }
- ```
- This is our final output method then:
- ```php
- public function dump(string $file = null):string{
- $collections = $this->collectModules();
- // build the final output
- $out = [];
- foreach($collections as $M_TYPE => $collection){
- $name = ($this->getModuleValue($M_TYPE) ?: $this::LAYERNAMES[$M_TYPE]);
- // the section header
- $out[] = sprintf("%s (%012b)\n", $name, $M_TYPE);
- // the list of modules
- $out[] = sprintf("%s\n", implode("\n", $collection));
- }
- return implode("\n", $out);
- }
- ```
- ## Run the custom output
- To run the output we just need to set the `QROptions::$outputInterface` to our custom class:
- ```php
- $options = new QROptions;
- $options->outputInterface = MyCustomOutput::class;
- $options->connectPaths = true;
- $options->drawLightModules = true;
- // our custom module values
- $options->moduleValues = [
- QRMatrix::M_DATA => 'these-modules-are-light',
- QRMatrix::M_DATA_DARK => 'here-is-a-dark-module',
- ];
- $qrcode = new QRCode($options);
- $qrcode->addByteSegment('test');
- var_dump($qrcode->render());
- ```
- Alternatively, you can invoke the `QROutputInterface` manually:
- ```php
- $qrcode = new QRCode($options);
- $qrOutputInterface = new MyCustomOutput($options, $qrcode->getMatrix($data));
- //dump the output, which is equivalent to QRCode::render()
- $qrOutputInterface->dump();
- // render to file
- $qrOutputInterface->dump('/path/to/qrcode.svg');
- ```
- The output looks similar to the following:
- ```
- these-modules-are-light (000000000010)
- x: 0, y: 0
- x: 1, y: 0
- x: 2, y: 0
- ...
- here-is-a-dark-module (100000000010)
- x: 4, y: 4
- x: 5, y: 4
- x: 6, y: 4
- ...
- ```
- Profit!
- ## Summary
- We've learned how to create a custom output class for a string based format similar to several of the built-in formats such as SVG or EPS.
- The full code of our custom class below:
- ```php
- class MyCustomOutput extends QROutputAbstract{
- protected function prepareModuleValue($value):string{
- return strtolower($value);
- }
- protected function getDefaultModuleValue(bool $isDark):string{
- return '';
- }
- public static function moduleValueIsValid($value):bool{
- return is_string($value) && preg_match('/^[a-z-]+$/i', $value) === 1;
- }
- public function dump(string $file = null):string{
- $collections = $this->collectModules($this->module(...));
- // build the final output
- $out = [];
- foreach($collections as $M_TYPE => $collection){
- $name = ($this->getModuleValue($M_TYPE) ?: $this::LAYERNAMES[$M_TYPE]);
- // the section header
- $out[] = sprintf("%s (%012b)\n", $name, $M_TYPE);
- // the list of modules
- $out[] = sprintf("%s\n", implode("\n", $collection));
- }
- return implode("\n", $out);
- }
- protected function moduleTransform(int $x, int $y, int $M_TYPE, int $M_TYPE_LAYER):string{
- if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
- return '';
- }
- return sprintf('x: %s, y: %s', $x, $y);
- }
- }
- ```
|