From 6a26d3afb879632bcf6830facc2001df1d76ff34 Mon Sep 17 00:00:00 2001 From: smiley Date: Mon, 16 Oct 2023 20:35:06 +0200 Subject: [PATCH] :book: --- docs/Customizing/QROutputAbstract.md | 256 +++++++++++++++++++++++++++ docs/Readme.md | 1 + docs/Usage/Configuration-settings.md | 34 +--- docs/index.rst | 1 + 4 files changed, 264 insertions(+), 28 deletions(-) create mode 100644 docs/Customizing/QROutputAbstract.md diff --git a/docs/Customizing/QROutputAbstract.md b/docs/Customizing/QROutputAbstract.md new file mode 100644 index 000000000..1313ab4b7 --- /dev/null +++ b/docs/Customizing/QROutputAbstract.md @@ -0,0 +1,256 @@ +# `QROutputAbstract` + +The abstract class `QROutputAbstract` contains several commonly used methods and properties and can be used as a basis for a custom output class. + + +## Properties + +### `$options` and `$matrix` + +The `QROptions` and `QRMatrix` instances that were passed to the constructor of the output class. +Both objects can be modified during runtime, for example to override settings or add matrix modifications. + + +### `$moduleCount`, `$scale` and `$length` + +These are convenience variables mostly to avoid multiple method calls to `QRMatrix::getSize()` and `QROptions::__get('scale')` inside loops, +the `$length` is calculated from the aforementioned values (`$moduleCount * $scale`). +The method `setMatrixDimensions()` can be called to update these 3 values after the matrix has been modified, e.g. by adding a quiet zone during output. + + +### `$moduleValues` + +The finalized map of `$M_TYPE` to value for the current output. This map is generated during invocation of the output class via `setModuleValues()`. + + +### Copies of `QROptions` values + +Some values from the `QROptions` instance are copied to properties to avoid calling the magic getters in long loops for a significant performance increase, e.g. in the module collector. +Currently, the following values are copied via `copyVars()` during invocation: +`$connectPaths`, `$excludeFromConnect`, `$eol`, `$drawLightModules`, `$drawCircularModules`, `$keepAsSquare`, `$circleRadius`. + + +## Methods + +### `setModuleValues()` + +This method calls the abstract/interface methods `moduleValueIsValid()`, `prepareModuleValue()` and `getDefaultModuleValue()` to prepare the module values map. + + +### `moduleValueIsValid()` + +This method is declared in the `QROutputInterface` and needs to be implemented by the output class; it is `static` so that it can be called before invocation. +The purpose is to determine whether the given `mixed` input is a valid module value for the current output class and returns `bool`. +It's also useful to check values from `QROptions` such as `$bgColor` or `$transparencyColor`. + +Below is a pseudo implementation, check the code of the several output classes for actual implementations +(e.g. [`QRImagick::moduleValueIsValid()`](https://github.com/chillerlan/php-qrcode/blob/4bd4b59fdec72397f5b1f70da9cadcb76764191b/src/Output/QRImagick.php#L68-L96)) + +```php +class MyOutput extends QROutputAbstract{ + + public static function moduleValueIsValid(mixed $value):bool{ + + // check the type of the input value first + if(!is_expected_type($value)){ + return false; + } + + // do some more checks to determine the value + if(!is_somehow_valid($value)){ + return false; + } + + // looks like we got a match + return true; + } + +} +``` + + +### `prepareModuleValue()` + +This method prepares the final replacement value from the given input. +It might still be necessary to validate the given value despite it being checked earlier by `moduleValueIsValid()` - +if nothing helps, this is a good place to throw an exception. +Below a pseudo implementation example (see [`QRGdImage::prepareModuleValue()`](https://github.com/chillerlan/php-qrcode/blob/4bd4b59fdec72397f5b1f70da9cadcb76764191b/src/Output/QRGdImage.php#L138-L158)): + +```php +class MyOutput extends QROutputAbstract{ + + protected function prepareModuleValue(mixed $value):mixed{ + + // extended validation to make sure the values are valid for output + // e.g. examine array values, clamp etc. + if(!is_valid($value)){ + throw new QRCodeOutputException('invalid module value'); + } + + return $this->modifyValue($value); + } + +} +``` + + +### `getDefaultModuleValue()` + +Finally, setting a default value is required, in case a value for an `$M_TYPE` is not set or it's invalid. + +```php +class MyOutput extends QROutputAbstract{ + + protected function getDefaultModuleValue(bool $isDark):mixed{ + $defaultValue = ($isDark === true) + ? 'default value for dark' + : 'default value for light'; + + return $this->prepareModuleValue($defaultValue); + } + +} +``` + + +### `getModuleValue()` and `getModuleValueAt()` + +Both methods return a module value, the main difference is that `getModuleValueAt()` is a convenience method +that makes an extra call to retrieve the `$M_TYPE` from the given matrix coordinate to return the value via `getModuleValue()`. + +A `foreach` loop over the matrix gives you the key (coordinate) *and* value of an array element: + +```php +class MyOutput extends QROutputAbstract{ + + public function dump(string $file = null):string{ + $lines = []; + + foreach($this->matrix->getMatrix() as $y => $row){ + $lines[$y] = ''; + + foreach($row as $x => $M_TYPE){ + $lines[$y] .= $this->getModuleValue($M_TYPE); + } + } + + return implode($this->options->eol, $lines); + } + +} +``` + +However, sometimes you might happen to use a `for` loop instead. The `for` loop leaves you only with the matrix coordinates, so you need to call `getModuleValueAt()`: + +```php +class MyOutput extends QROutputAbstract{ + + public function dump(string $file = null):string{ + $lines = []; + + for($y = 0; $y < $this->moduleCount; $y++){ + $lines[$y] = ''; + + for($x = 0; $x < $this->moduleCount; $x++){ + $lines[$y] .= $this->getModuleValueAt($x, $y); + } + + } + + return implode($this->options->eol, $lines); + } + +} +``` + + +### `setMatrixDimensions()` + +As mentioned before, this method is supposed to set the values for the properties `$moduleCount`, `$scale` and `$length`. +It is called in the constructor during invocation, but it might be necessary to call it again if the size of the matrix was changed in the output class +(see [the round quiet zone example](https://github.com/chillerlan/php-qrcode/blob/99b1f9cf454ab1316cb643950a71caed3a6c0f5a/examples/svgRoundQuietzone.php#L38-L44) for a use case). + + +### `getOutputDimensions()` + +This method provides a simple way for consistent width/height values for the output (if applicable) which then can be changed by simply overriding this method. +It returns a 2-element array that contains the values in a format that can be used by the output class, which is `QROutputAbstract::$length` (`$moduleCount * $scale`): + +```php +[$width, $height] = $this->getOutputDimensions(); +``` + +The output width and height can be changed in all places by simply overriding the method: + +```php +class MyOutput extends QROutputAbstract{ + + protected function getOutputDimensions():array{ + // adjust the height in order to add something under the QR Code + return [$this->length, ($this->length + 69)]; + } + +} +``` + + +### `collectModules()` + +The module collector is particularly useful for plain text based file formats, for example the various markup languages like SVG and HTML or other structured file formats such as EPS. +This method takes a `Closure` as a parameter, which is called with 4 parameters: the module coordinates `$x` and `$y`, the `$M_TYPE` and `$M_TYPE_LAYER`. +The `$M_TYPE_LAYER` is a copy of the `$M_TYPE` that represents the array key of the returned array and that may have been reassigned in the collector to another path layer, e.g. through `QROptions::$connectPaths`. + +```php +class MyOutput extends QROutputAbstract{ + + public function dump(string $file = null):string{ + + // collect the modules for the path elements + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => sprintf('%d %d %012b', $x, $y, $M_TYPE)); + + // loop over the paths + foreach($paths as $M_TYPE_LAYER => &$path){ + + if(empty($path)){ + continue; + } + + $path = implode($this->options->eol, $path); + } + + return implode($this->options->eol, $paths); + } + +} +``` + +Sometimes it can be necessary to override `collectModules()` in order to apply special effects such as random colors - you can find some implementations in [the SVG examples](https://github.com/chillerlan/php-qrcode/tree/main/examples). + + +### `saveToFile()` and `toBase64DataURI()` + +The void method `saveToFile()` takes a data blob and the `$file` given in `QROutputInterface::dump()` and save to the path if it is not `null` - the file path itself is not checked except for writability. + +The final output can be transformed to a [base64 data URI](https://en.wikipedia.org/wiki/Data_URI_scheme) with `toBase64DataURI()`, where the data blob and a valid mime type as parameters - the mime type is not checked. + + + +```php +class MyOutput extends QROutputAbstract{ + + public function dump(string $file = null):string{ + $output = 'qrcode data string'; + + // save the plain data to file + $this->saveToFile($output, $file); + + // base64 encoding may be called optionally + if($this->options->outputBase64){ + $output = $this->toBase64DataURI($output, 'text/plain'); + } + + return $output; + } + +} +``` diff --git a/docs/Readme.md b/docs/Readme.md index 259509ce4..ef606c769 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -67,6 +67,7 @@ The markdown sources for the [Read the Docs online manual](https://php-qrcode.re ### Customizing output - [Module values](./Customizing/Module-Values.md) +- [`QROutputAbstract`](./Customizing/QROutputAbstract.md) ### Built-In Output Modules diff --git a/docs/Usage/Configuration-settings.md b/docs/Usage/Configuration-settings.md index 169176ee3..8d1115c7a 100644 --- a/docs/Usage/Configuration-settings.md +++ b/docs/Usage/Configuration-settings.md @@ -341,16 +341,6 @@ Imagick output format A common css class -## markupDark - -Markup substitute for dark (CSS value) - - -## markupLight - -Markup substitute for light (CSS value) - - ## svgAddXmlHeader Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML @@ -358,15 +348,6 @@ Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML `` -## svgOpacity - -SVG path opacity - -Sets the value for the SVG "fill-opacity" on a `` element. Only in effect when non-empty values -for `QROptions::$markupDark` and `QROptions::$markupLight` are given. -The opacity value is the same for all paths - please use CSS for more sophisticated implementations. - - ## svgDefs Anything in the SVG `` tag @@ -385,19 +366,16 @@ Sets the value for the "preserveAspectRatio" on the `` element - [developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio) -## textDark +## svgUseFillAttributes -String substitute for dark +Whether to use the SVG `fill` attributes -**See also:** +If set to `true` (default), the `fill` attribute will be set with the module value for the `` element's `$M_TYPE`. +When set to `false`, the module values map will be ignored and the QR Code may be styled via CSS. -- [en.wikipedia.org/wiki/Block_Elements](https://en.wikipedia.org/wiki/Block_Elements) -- [en.wikipedia.org/wiki/ANSI_escape_code#8-bit](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) - - -## textLight +**See also:** -String substitute for light +- [developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill) ## textLineStart diff --git a/docs/index.rst b/docs/index.rst index bff629843..eedcb0c13 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,6 +27,7 @@ This work is licensed under the Creative Commons Attribution 4.0 International ( :caption: Customizing output Customizing/Module-Values.md + Customizing/QROutputAbstract.md .. toctree:: :maxdepth: 3