generated from mirzaev/pot-php
blanks, papers, pages, fit system, render 100% precision
This commit is contained in:
@@ -8,7 +8,9 @@ namespace svoboda\pechatalka\controllers;
|
|||||||
use svoboda\pechatalka\controllers\core,
|
use svoboda\pechatalka\controllers\core,
|
||||||
svoboda\pechatalka\models\paper,
|
svoboda\pechatalka\models\paper,
|
||||||
svoboda\pechatalka\models\product,
|
svoboda\pechatalka\models\product,
|
||||||
svoboda\pechatalka\models\enumerations\products\type as product_type;
|
/* svoboda\pechatalka\models\enumerations\paper\type as paper_type, */
|
||||||
|
svoboda\pechatalka\models\enumerations\paper\format as paper_format,
|
||||||
|
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||||
|
|
||||||
// Framework for PHP
|
// Framework for PHP
|
||||||
use mirzaev\minimal\http\enumerations\content,
|
use mirzaev\minimal\http\enumerations\content,
|
||||||
@@ -194,7 +196,14 @@ final class generator extends core
|
|||||||
if ($type === product_type::pin_37) {
|
if ($type === product_type::pin_37) {
|
||||||
// Pin 37mm
|
// Pin 37mm
|
||||||
|
|
||||||
$biba = paper::a5(type: $type, products: $products, canvas: $canvas);
|
$biba = paper::generate(
|
||||||
|
format: paper_format::a6,
|
||||||
|
type: $type,
|
||||||
|
products: $products,
|
||||||
|
canvas: $canvas,
|
||||||
|
dpi: 80,
|
||||||
|
fit: true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace svoboda\pechatalka\models\enumerations\paper;
|
||||||
|
|
||||||
|
// Files of the project
|
||||||
|
use svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of products
|
||||||
|
*
|
||||||
|
* @package svoboda\pechatalka\models\enumerations\paper
|
||||||
|
*
|
||||||
|
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
enum format
|
||||||
|
{
|
||||||
|
case a6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dimensions
|
||||||
|
*
|
||||||
|
* @return array The paper format dimensions in centimeters (cm) ['width', 'height']
|
||||||
|
*/
|
||||||
|
public function dimensions(): array
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
format::a6 => [
|
||||||
|
'width' => 10.5,
|
||||||
|
'height' => 14.8
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Places
|
||||||
|
*
|
||||||
|
* @param product_type $type The product type
|
||||||
|
* @param int|float $padding The paper padding (print borders)
|
||||||
|
*
|
||||||
|
* @return array The paper places by the product type in centimeters (cm)
|
||||||
|
*/
|
||||||
|
public function places(product_type $type, int|float $padding = 0): array|false
|
||||||
|
{
|
||||||
|
if ($this === format::a6) {
|
||||||
|
// A6
|
||||||
|
|
||||||
|
// Initializing the paper dimensions
|
||||||
|
$paper = $this->dimensions();
|
||||||
|
|
||||||
|
if ($type === product_type::pin_37) {
|
||||||
|
// Pin 37mm
|
||||||
|
|
||||||
|
// Initializing the product dimensions
|
||||||
|
$blank = $type->areas()['blank'];
|
||||||
|
|
||||||
|
// Calculating the general X coordinate
|
||||||
|
$x = $paper['width'] / 2 - $blank['width'] / 2 - $padding;
|
||||||
|
|
||||||
|
// Initializing the amount of products
|
||||||
|
$amount = 2;
|
||||||
|
|
||||||
|
// Calculating the summary products height
|
||||||
|
$height = $blank['height'] * $amount;
|
||||||
|
|
||||||
|
// Calculating the free space on the page
|
||||||
|
$free = $paper['height'] - $padding * 2 - $height;
|
||||||
|
|
||||||
|
// Calculating the offset between products and from products to the paper borders
|
||||||
|
$offset = $free / ($amount + 1);
|
||||||
|
|
||||||
|
// Exit (success)
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'x' => $x,
|
||||||
|
'y' => $offset
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'x' => $x,
|
||||||
|
'y' => $offset + $blank['height'] + $offset
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit (fail)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace svoboda\pechatalka\models\enumerations\products;
|
namespace svoboda\pechatalka\models\enumerations\product;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types of products
|
* Types of products
|
||||||
*
|
*
|
||||||
* @package svoboda\pechatalka\models\enumerations\products
|
* @package svoboda\pechatalka\models\enumerations\product
|
||||||
*
|
*
|
||||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
@@ -19,19 +19,19 @@ enum type
|
|||||||
/**
|
/**
|
||||||
* Areas
|
* Areas
|
||||||
*
|
*
|
||||||
* @return array ['visible' => ['width', 'height'], 'blank' => ['width', 'height']]
|
* @return array The product type areas in centimeters (cm) ['visible' => ['width', 'height'], 'blank' => ['width', 'height']]
|
||||||
*/
|
*/
|
||||||
public function areas(): array
|
public function areas(): array
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
type::pin_37 => [
|
type::pin_37 => [
|
||||||
'visible' => [
|
'visible' => [
|
||||||
'width' => 37,
|
'width' => 3.7,
|
||||||
'height' => 37
|
'height' => 3.7
|
||||||
],
|
],
|
||||||
'blank' => [
|
'blank' => [
|
||||||
'width' => 48.5,
|
'width' => 4.85,
|
||||||
'height' => 48.5
|
'height' => 4.85
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -6,7 +6,9 @@ namespace svoboda\pechatalka\models;
|
|||||||
|
|
||||||
// Files of the project
|
// Files of the project
|
||||||
use svoboda\pechatalka\models\core,
|
use svoboda\pechatalka\models\core,
|
||||||
svoboda\pechatalka\models\enumerations\products\type as product_type;
|
/* svoboda\pechatalka\models\enumerations\paper\type as paper_type, */
|
||||||
|
svoboda\pechatalka\models\enumerations\paper\format as paper_format,
|
||||||
|
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||||
|
|
||||||
// Svoboda time
|
// Svoboda time
|
||||||
use svoboda\time\statement as svoboda;
|
use svoboda\time\statement as svoboda;
|
||||||
@@ -23,6 +25,7 @@ use Zanzara\Telegram\Type\User as telegram;
|
|||||||
|
|
||||||
// Built-in libraries
|
// Built-in libraries
|
||||||
use Imagick as imagick,
|
use Imagick as imagick,
|
||||||
|
ImagickKernel as imagick_kernel,
|
||||||
ImagickPixel as imagick_pixel,
|
ImagickPixel as imagick_pixel,
|
||||||
ImagickDraw as imagick_draw,
|
ImagickDraw as imagick_draw,
|
||||||
Exception as exception,
|
Exception as exception,
|
||||||
@@ -39,120 +42,167 @@ use Imagick as imagick,
|
|||||||
final class paper extends core
|
final class paper extends core
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* A5
|
* Generate
|
||||||
*
|
|
||||||
*
|
*
|
||||||
|
* @param paper_format $format
|
||||||
|
* @param product_type $type
|
||||||
|
* @param array $products
|
||||||
|
* @param array $canvas
|
||||||
|
* @param int|float $dpi Dots (pixels) per Inch (used with centimeters)
|
||||||
|
* @param bool $fit Fit the maximum number of blanks on paper? (duplication)
|
||||||
*
|
*
|
||||||
* @return record The account record from the database
|
* @return record The account record from the database
|
||||||
*/
|
*/
|
||||||
public static function a5(product_type $type, array $products, array $canvas = []): mixed
|
public static function generate(paper_format $format, product_type $type, array $products, array $canvas = [], int|float $dpi = 300, bool $fit = true): mixed
|
||||||
{
|
{
|
||||||
// Initializing the print DPI
|
// Initializing the paper dimensions
|
||||||
define('dpi', 80);
|
$dimensions = $format->dimensions();
|
||||||
/* define('dpi', 300); */
|
$width = (int) round($dimensions['width'] * $dpi);
|
||||||
|
$height = (int) round($dimensions['height'] * $dpi);
|
||||||
// Initializing the A5 paper dimensions
|
unset($dimensions);
|
||||||
$a5 = [
|
|
||||||
'width' => (int) round(10.5 * dpi),
|
|
||||||
'height' => (int) round(14.8 * dpi)
|
|
||||||
];
|
|
||||||
|
|
||||||
// Initializing the paper
|
|
||||||
$paper = new imagick();
|
|
||||||
$paper->setResolution(dpi, dpi);
|
|
||||||
$paper->newImage($a5['width'], $a5['height'], new imagick_pixel("white"));
|
|
||||||
$paper->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
|
||||||
$paper->setImageFormat("jpg");
|
|
||||||
|
|
||||||
foreach ($products as $product) {
|
|
||||||
// Iterating over products
|
|
||||||
|
|
||||||
foreach ($product as $layer) {
|
|
||||||
// Iterating over the product layers
|
|
||||||
|
|
||||||
// Filtering
|
|
||||||
empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1;
|
|
||||||
|
|
||||||
// Initializing the product areas
|
// Initializing the product areas
|
||||||
$areas = $type->areas();
|
$areas = $type->areas();
|
||||||
|
|
||||||
// Calculating the product blank dimensions by the print DPI
|
// Calculating the product blank dimensions by the print $dpi
|
||||||
$blank = [
|
$blank = [
|
||||||
'width' => (int) round($areas['blank']['width'] / 10 * dpi),
|
'width' => (int) round($areas['blank']['width'] * $dpi),
|
||||||
'height' => (int) round($areas['blank']['height'] / 10 * dpi)
|
'height' => (int) round($areas['blank']['height'] * $dpi)
|
||||||
];
|
];
|
||||||
|
|
||||||
// Calculating the DPI ratio of the pechatalka interface to the blank dimensions
|
// Calculating the $dpi ratio of the pechatalka interface to the blank dimensions
|
||||||
$ratio = [
|
$ratio = [
|
||||||
'width' => $blank['width'] / $canvas['width'],
|
'width' => $blank['width'] / $canvas['width'],
|
||||||
'height' => $blank['height'] / $canvas['height']
|
'height' => $blank['height'] / $canvas['height']
|
||||||
];
|
];
|
||||||
|
|
||||||
// Initializing the circle mask
|
// Initializing the circle mask
|
||||||
$circle = new imagick();
|
$mask = new imagick();
|
||||||
$circle->setResolution(dpi, dpi);
|
$mask->setResolution($dpi, $dpi);
|
||||||
$circle->newImage($blank['width'], $blank['height'], '#0008');
|
$mask->newImage($blank['width'], $blank['height'], '#0000');
|
||||||
$circle->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
$mask->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||||
$circle->setimageformat('png');
|
$mask->setimageformat('png');
|
||||||
$circle->setimagematte(true);
|
$mask->setimagematte(true);
|
||||||
$draw = new imagick_draw();
|
$draw = new imagick_draw();
|
||||||
$draw->setfillcolor('#fff');
|
$draw->setfillcolor('#fff');
|
||||||
$draw->circle($blank['width'] / 2, $blank['height'] / 2, $blank['width'] / 2, $blank['height']);
|
$draw->circle($blank['width'] / 2, $blank['height'] / 2, $blank['width'] / 2, $blank['height']);
|
||||||
$circle->drawimage($draw);
|
$mask->drawimage($draw);
|
||||||
|
|
||||||
|
// Initializing the paper places
|
||||||
|
$places = $format->places(type: $type);
|
||||||
|
|
||||||
|
// Calculating the amount of products
|
||||||
|
$have = count($products);
|
||||||
|
|
||||||
|
// Calculating the amount of places per paper
|
||||||
|
$limit = count($places);
|
||||||
|
|
||||||
|
// Calculating the amount of missing products on the paper
|
||||||
|
$need = $limit - $have;
|
||||||
|
|
||||||
|
if ($fit && $have < $limit) {
|
||||||
|
// Received less products then can be fit on the page
|
||||||
|
|
||||||
|
for (; $need++ < $limit;) {
|
||||||
|
// Iterating over free places
|
||||||
|
|
||||||
|
// Dublicating the product
|
||||||
|
$products[] = $products[rand(0, $have - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculating the amount of papers
|
||||||
|
$amount = $have % $limit;
|
||||||
|
|
||||||
|
for ($page = 0; $page < $amount; $page++) {
|
||||||
|
// Iterating over pages
|
||||||
|
|
||||||
|
// Initializing the paper
|
||||||
|
$paper = new imagick();
|
||||||
|
$paper->setResolution($dpi, $dpi);
|
||||||
|
$paper->newImage($width, $height, new imagick_pixel("#fff"));
|
||||||
|
$paper->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||||
|
$paper->setImageFormat("jpg");
|
||||||
|
$paper->setimagematte(true);
|
||||||
|
|
||||||
|
// Calculating the products pages offset
|
||||||
|
$offset = $page === 0 ? 0 : $page * $amount;
|
||||||
|
|
||||||
|
foreach ($places as $index => $place) {
|
||||||
|
// Iterating over places
|
||||||
|
|
||||||
|
// Recalculating the place coordinates with DPI
|
||||||
|
$place['x'] *= $dpi;
|
||||||
|
$place['y'] *= $dpi;
|
||||||
|
|
||||||
|
// Initializing the product
|
||||||
|
$product = $products[$offset + $index];
|
||||||
|
|
||||||
|
foreach ($product as $layer) {
|
||||||
|
// Iterating over the product layers
|
||||||
|
|
||||||
|
// Filtering and normalizing the layer parameters
|
||||||
|
empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1;
|
||||||
|
|
||||||
// Initializing the layer image
|
// Initializing the layer image
|
||||||
$image = new imagick();
|
$image = new imagick();
|
||||||
$image->setResolution(dpi, dpi);
|
$image->setResolution($dpi, $dpi);
|
||||||
$image->readImage($layer['image']);
|
$image->readImage($layer['image']);
|
||||||
$image->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
$image->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||||
$image->setimagematte(true);
|
$image->setimagematte(true);
|
||||||
|
|
||||||
$width = (int) round($blank['width'] * $layer['scale']);
|
// Initializing the layer image height before resizing
|
||||||
$image->adaptiveResizeImage($width, 0);
|
$before = $image->getImageHeight();
|
||||||
/* $image->roundCornersImage(50, 50); */
|
|
||||||
|
|
||||||
$offset = [
|
// Resizing the layer image
|
||||||
'x' => ($blank['width'] - $image->getImageWidth()) / 2,
|
$image->adaptiveResizeImage((int) round($blank['width'] * $layer['scale']), 0);
|
||||||
'y' => ($blank['height'] - $image->getImageHeight()) / 2
|
$image->roundCornersImage($layer['corners'], $layer['corners']);
|
||||||
];
|
|
||||||
|
|
||||||
$vertical = $blank['height'] - $image->getImageHeight();
|
// Calculating the layer image coordinates by the layer image mask
|
||||||
|
$vertical = $blank['height'] - $before;
|
||||||
|
$layer['x'] = (int) round($layer['x'] * $ratio['width'] + ($blank['width'] - $image->getImageWidth()) / 2);
|
||||||
|
$layer['y'] = (int) round($layer['y'] * $ratio['height'] + ($blank['height'] - $image->getImageHeight() + ($vertical > 0 ? $vertical : 0)) / 2);
|
||||||
// Добавить нормальные комментарии после глубокго тестирования
|
unset($before, $vertical);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Reinitializing the product layer offsets
|
|
||||||
$layer['x'] = (int) round($layer['x'] * $ratio['width']);
|
|
||||||
$layer['y'] = (int) round($layer['y'] * $ratio['height']);
|
|
||||||
|
|
||||||
|
// Compositing the layer image mask with the layer image
|
||||||
$image->compositeImage(
|
$image->compositeImage(
|
||||||
$circle,
|
$mask,
|
||||||
imagick::COMPOSITE_DSTIN,
|
imagick::COMPOSITE_DSTIN,
|
||||||
(int) round(-$layer['x'] - $offset['x']),
|
(int) round(-$layer['x']),
|
||||||
/* (int) round(-$layer['y'] - $offset['y'] - $vertical / 2) */
|
(int) round(-$layer['y'])
|
||||||
(int) round(-$layer['y'] - $offset['y'])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Compositing the layer with the paper
|
// Drawing the cutting line
|
||||||
$paper->setImageVirtualPixelMethod(imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
|
$draw = new imagick_draw();
|
||||||
$paper->setImageArtifact('compose:args', "1,0,-0.5,0.5");
|
$draw->setfillcolor($canvas['background'] ?? '#fff');
|
||||||
|
$stroke = 1;
|
||||||
|
$draw->setStrokeOpacity($stroke);
|
||||||
|
$draw->setStrokeColor('#000');
|
||||||
|
$draw->setStrokeWidth(2);
|
||||||
|
$draw->circle(
|
||||||
|
$place['x'] + $blank['width'] / 2,
|
||||||
|
$place['y'] + $blank['height'] / 2,
|
||||||
|
round($place['x'] + $blank['width'] / 2 - $stroke),
|
||||||
|
round($place['y'] + $blank['height'])
|
||||||
|
);
|
||||||
|
$paper->drawimage($draw);
|
||||||
|
|
||||||
|
// Compositing the layer image with the paper
|
||||||
$paper->compositeImage(
|
$paper->compositeImage(
|
||||||
$image,
|
$image,
|
||||||
imagick::COMPOSITE_MATHEMATICS,
|
$image->getImageCompose(),
|
||||||
(int) round($a5['width'] / 2 - $blank['width'] / 2 + $layer['x'] + $offset['x']),
|
(int) round($place['x'] + $layer['x']),
|
||||||
/* (int) round($a5['height'] / 2 - $blank['height'] / 2 + $layer['y'] + $offset['y'] + $vertical / 2) */
|
(int) round($place['y'] + $layer['y'])
|
||||||
(int) round($a5['height'] / 2 - $blank['height'] / 2 + $layer['y'] + $offset['y'])
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Writing the paper file
|
// Writing the paper file
|
||||||
$paper->writeImage(INDEX . DIRECTORY_SEPARATOR . 'test.jpg');
|
$paper->writeImage(INDEX . DIRECTORY_SEPARATOR . 'test.jpg');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
// Exit (success)
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace svoboda\pechatalka\models;
|
|||||||
|
|
||||||
// Files of the project
|
// Files of the project
|
||||||
use svoboda\pechatalka\models\core,
|
use svoboda\pechatalka\models\core,
|
||||||
svoboda\pechatalka\models\enumerations\products\type as product_type;
|
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||||
|
|
||||||
// Svoboda time
|
// Svoboda time
|
||||||
use svoboda\time\statement as svoboda;
|
use svoboda\time\statement as svoboda;
|
||||||
|
|||||||
BIN
svoboda/pechatalka/system/public/test.jpg
Normal file
BIN
svoboda/pechatalka/system/public/test.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -155,7 +155,8 @@ div#pechatalka {
|
|||||||
width: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
width: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
||||||
height: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
height: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
align-content: center;
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
|
|||||||
Reference in New Issue
Block a user