diff --git a/composer.json b/composer.json index b1854b2f..e7007369 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "beberlei/doctrineextensions": "^1.2", "doctrine/annotations": "^1.6", "doctrine/doctrine-bundle": "^2.0", + "dompdf/dompdf": "^0.8.5", "florianv/swap": "^4.0", "friendsofsymfony/ckeditor-bundle": "^2.0", "gregwar/captcha-bundle": "^2.1.0", diff --git a/composer.lock b/composer.lock index f4a111f1..6d5691e3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aef1b0fadcee2305438721839abb1400", + "content-hash": "6590c3a2eb19714f486c24335194ad9b", "packages": [ { "name": "beberlei/assert", @@ -1266,6 +1266,72 @@ ], "time": "2020-03-27T11:06:43+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v0.8.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "6782abfc090b132134cd6cea0ec6d76f0fce2c56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/6782abfc090b132134cd6cea0ec6d76f0fce2c56", + "reference": "6782abfc090b132134cd6cea0ec6d76f0fce2c56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "phenx/php-font-lib": "^0.5.1", + "phenx/php-svg-lib": "^0.3.3", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.7-dev" + } + }, + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + }, + { + "name": "Brian Sweeney", + "email": "eclecticgeek@gmail.com" + }, + { + "name": "Gabriel Bull", + "email": "me@gabrielbull.com" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "time": "2020-02-20T03:52:51+00:00" + }, { "name": "egulias/email-validator", "version": "2.1.17", @@ -2623,6 +2689,83 @@ ], "time": "2018-07-02T15:55:56+00:00" }, + { + "name": "phenx/php-font-lib", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-font-lib.git", + "reference": "760148820110a1ae0936e5cc35851e25a938bc97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97", + "reference": "760148820110a1ae0936e5cc35851e25a938bc97", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/PhenX/php-font-lib", + "time": "2017-09-13T16:14:37+00:00" + }, + { + "name": "phenx/php-svg-lib", + "version": "v0.3.3", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-svg-lib.git", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "shasum": "" + }, + "require": { + "sabberworm/php-css-parser": "^8.3" + }, + "require-dev": { + "phpunit/phpunit": "^5.5|^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/PhenX/php-svg-lib", + "time": "2019-09-11T20:02:13+00:00" + }, { "name": "php-http/discovery", "version": "1.7.4", @@ -3773,6 +3916,51 @@ ], "time": "2020-03-31T16:44:09+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "8.3.0", + "source": { + "type": "git", + "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "codacy/coverage": "^1.4", + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-0": { + "Sabberworm\\CSS": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "time": "2019-02-22T07:42:52+00:00" + }, { "name": "scheb/two-factor-bundle", "version": "v4.14.0", diff --git a/config/packages/fos_ckeditor.yaml b/config/packages/fos_ckeditor.yaml index f4da15ea..1d2bef5b 100644 --- a/config/packages/fos_ckeditor.yaml +++ b/config/packages/fos_ckeditor.yaml @@ -19,7 +19,8 @@ fos_ck_editor: toolbar: description_toolbar label_config: height: 100 - toolbar: config_toolbar + enterMode: 2 + toolbar: label_toolbar plugins: bbcode: @@ -35,13 +36,14 @@ fos_ck_editor: toolbars: configs: - config_toolbar: + label_toolbar: - [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] - - ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'] + - ['JustifyLeft', 'JustifyCenter', 'JustifyRight'] - ["SpecialChar"] - ["Source"] - "/" - - ['Format', 'FontSize' ] + - ['Format', 'FontSize'] + - ['Table', 'HorizontalRule'] description_toolbar: - [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] - ["SpecialChar"] diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php new file mode 100644 index 00000000..6f82304b --- /dev/null +++ b/src/Controller/LabelController.php @@ -0,0 +1,59 @@ +. + */ + +namespace App\Controller; + + +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parts\Part; +use App\Helpers\LabelResponse; +use App\Services\LabelSystem\LabelGenerator; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("/label") + * @package App\Controller + */ +class LabelController extends AbstractController +{ + protected $labelGenerator; + + public function __construct(LabelGenerator $labelGenerator) + { + $this->labelGenerator = $labelGenerator; + } + + /** + * @Route("/{profile}/{part}/view") + */ + public function view(LabelProfile $profile, Part $part) + { + $label = $this->labelGenerator->generateLabel($profile->getOptions(), $part); + + $response = new LabelResponse($label); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, 'label.pdf'); + + return $response; + } +} \ No newline at end of file diff --git a/src/Helpers/LabelResponse.php b/src/Helpers/LabelResponse.php new file mode 100644 index 00000000..5c61d5f0 --- /dev/null +++ b/src/Helpers/LabelResponse.php @@ -0,0 +1,110 @@ +. + */ + +namespace App\Helpers; + + +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LabelResponse extends Response +{ + public function __construct($content = '', int $status = 200, array $headers = []) + { + parent::__construct($content, $status, $headers); + } + + public function setContent($content) + { + parent::setContent($content); + + $this->setAutoEtag(); + $this->setAutoLastModified(); + return $this; + } + + public function prepare(Request $request) + { + parent::prepare($request); + + $this->headers->set('Content-Type','application/pdf'); + + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(new \DateTime()); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(base64_encode(hash('sha256', $this->content, true))); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition($disposition, $filename, $filenameFallback = '') + { + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + +} \ No newline at end of file diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php new file mode 100644 index 00000000..480b147d --- /dev/null +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -0,0 +1,81 @@ +. + */ + +namespace App\Services\LabelSystem; + + +use App\Entity\Contracts\NamedElementInterface; +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\Parts\Part; +use App\Services\ElementTypeNameGenerator; +use Dompdf\Dompdf; +use Twig\Environment; + +class LabelGenerator +{ + protected const CLASS_SUPPORT_MAPPING = [ + 'part' => Part::class, + ]; + + public const MM_TO_POINTS_FACTOR = 2.83465; + + protected $labelHTMLGenerator; + + public function __construct(LabelHTMLGenerator $labelHTMLGenerator) + { + $this->labelHTMLGenerator = $labelHTMLGenerator; + } + + public function generateLabel(LabelOptions $options, object $element): string + { + if (!$this->supports($options, $element)) { + throw new \InvalidArgumentException('The given options are not compatible with the given element!'); + } + + $dompdf = new Dompdf(); + $dompdf->loadHtml($this->labelHTMLGenerator->getLabelHTML($options, $element)); + + $dompdf->setPaper($this->mmToPointsArray($options->getWidth(), $options->getHeight())); + + $dompdf->render(); + return $dompdf->output(); + } + + /** + * Check if the given LabelOptions can be used with $element. + * @param LabelOptions $options + * @param object $element + * @return bool + */ + public function supports(LabelOptions $options, object $element) + { + $supported_type = $options->getSupportedElement(); + if (!isset(static::CLASS_SUPPORT_MAPPING[$supported_type])) { + throw new \InvalidArgumentException('Supported type name of the Label options not known!'); + } + + return is_a($element, static::CLASS_SUPPORT_MAPPING[$supported_type]); + } + + public function mmToPointsArray(float $width, float $height): array + { + return [0, 0, $width * self::MM_TO_POINTS_FACTOR, $height * self::MM_TO_POINTS_FACTOR]; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php new file mode 100644 index 00000000..bf009ce0 --- /dev/null +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -0,0 +1,55 @@ +. + */ + +namespace App\Services\LabelSystem; + +use App\Entity\Contracts\NamedElementInterface; +use App\Entity\LabelSystem\LabelOptions; +use App\Services\ElementTypeNameGenerator; +use Twig\Environment; + +class LabelHTMLGenerator +{ + protected $twig; + protected $elementTypeNameGenerator; + + public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, Environment $twig) + { + $this->twig = $twig; + $this->elementTypeNameGenerator = $elementTypeNameGenerator; + } + + public function getLabelHTML(LabelOptions $options, object $element): string + { + return $this->twig->render('labels/base_label.html.twig', [ + 'meta_title' => $this->getPDFTitle($options, $element), + 'lines' => $options->getLines(), + ]); + } + + protected function getPDFTitle(LabelOptions $options, object $element) + { + if ($element instanceof NamedElementInterface) { + return $this->elementTypeNameGenerator->getTypeNameCombination($element, false); + } + + return 'Part-DB label'; + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index fbaf0878..214f109d 100644 --- a/symfony.lock +++ b/symfony.lock @@ -120,6 +120,9 @@ "doctrine/reflection": { "version": "v1.0.0" }, + "dompdf/dompdf": { + "version": "v0.8.5" + }, "egulias/email-validator": { "version": "2.1.11" }, @@ -271,6 +274,12 @@ "paragonie/random_compat": { "version": "v9.99.99" }, + "phenx/php-font-lib": { + "version": "0.5.1" + }, + "phenx/php-svg-lib": { + "version": "v0.3.3" + }, "php": { "version": "7.2.5" }, @@ -373,6 +382,9 @@ "s9e/text-formatter": { "version": "2.1.2" }, + "sabberworm/php-css-parser": { + "version": "8.3.0" + }, "scheb/two-factor-bundle": { "version": "3.16", "recipe": { diff --git a/templates/labels/base_label.html.twig b/templates/labels/base_label.html.twig new file mode 100644 index 00000000..fd4b4ee3 --- /dev/null +++ b/templates/labels/base_label.html.twig @@ -0,0 +1,16 @@ + + + + + {{ meta_title }} + + + + + + + {{ lines | raw }} + + \ No newline at end of file diff --git a/templates/labels/label_style.css.twig b/templates/labels/label_style.css.twig new file mode 100644 index 00000000..22c61c0a --- /dev/null +++ b/templates/labels/label_style.css.twig @@ -0,0 +1,17 @@ +@page { + margin: 5px 5px; +} + +body { + font-family: "DejaVu Sans Mono"; + font-size: 10pt; + line-height: 1.0; +} + +p { + margin: 0; +} + +hr { + margin: 2px; +} \ No newline at end of file