AbstractPdf.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Sales\Model\Order\Pdf;
  7. use Magento\Framework\App\Filesystem\DirectoryList;
  8. /**
  9. * Sales Order PDF abstract model
  10. *
  11. * @api
  12. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  13. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  14. * @since 100.0.2
  15. */
  16. abstract class AbstractPdf extends \Magento\Framework\DataObject
  17. {
  18. /**
  19. * Y coordinate
  20. *
  21. * @var int
  22. */
  23. public $y;
  24. /**
  25. * Item renderers with render type key
  26. * model => the model name
  27. * renderer => the renderer model
  28. *
  29. * @var array
  30. */
  31. protected $_renderers = [];
  32. /**
  33. * Predefined constants
  34. */
  35. const XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID = 'sales_pdf/invoice/put_order_id';
  36. const XML_PATH_SALES_PDF_SHIPMENT_PUT_ORDER_ID = 'sales_pdf/shipment/put_order_id';
  37. const XML_PATH_SALES_PDF_CREDITMEMO_PUT_ORDER_ID = 'sales_pdf/creditmemo/put_order_id';
  38. /**
  39. * Zend PDF object
  40. *
  41. * @var \Zend_Pdf
  42. */
  43. protected $_pdf;
  44. /**
  45. * Retrieve PDF
  46. *
  47. * @return \Zend_Pdf
  48. */
  49. abstract public function getPdf();
  50. /**
  51. * Payment data
  52. *
  53. * @var \Magento\Payment\Helper\Data
  54. */
  55. protected $_paymentData;
  56. /**
  57. * @var \Magento\Framework\Stdlib\StringUtils
  58. */
  59. protected $string;
  60. /**
  61. * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
  62. */
  63. protected $_localeDate;
  64. /**
  65. * Core store config
  66. *
  67. * @var \Magento\Framework\App\Config\ScopeConfigInterface
  68. */
  69. protected $_scopeConfig;
  70. /**
  71. * @var \Magento\Framework\Filesystem\Directory\WriteInterface
  72. */
  73. protected $_mediaDirectory;
  74. /**
  75. * @var \Magento\Framework\Filesystem\Directory\ReadInterface
  76. */
  77. protected $_rootDirectory;
  78. /**
  79. * @var Config
  80. */
  81. protected $_pdfConfig;
  82. /**
  83. * @var \Magento\Sales\Model\Order\Pdf\Total\Factory
  84. */
  85. protected $_pdfTotalFactory;
  86. /**
  87. * @var \Magento\Sales\Model\Order\Pdf\ItemsFactory
  88. */
  89. protected $_pdfItemsFactory;
  90. /**
  91. * @var \Magento\Framework\Translate\Inline\StateInterface
  92. */
  93. protected $inlineTranslation;
  94. /**
  95. * @var \Magento\Sales\Model\Order\Address\Renderer
  96. */
  97. protected $addressRenderer;
  98. /**
  99. * @param \Magento\Payment\Helper\Data $paymentData
  100. * @param \Magento\Framework\Stdlib\StringUtils $string
  101. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  102. * @param \Magento\Framework\Filesystem $filesystem
  103. * @param Config $pdfConfig
  104. * @param Total\Factory $pdfTotalFactory
  105. * @param ItemsFactory $pdfItemsFactory
  106. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  107. * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
  108. * @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer
  109. * @param array $data
  110. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  111. */
  112. public function __construct(
  113. \Magento\Payment\Helper\Data $paymentData,
  114. \Magento\Framework\Stdlib\StringUtils $string,
  115. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  116. \Magento\Framework\Filesystem $filesystem,
  117. Config $pdfConfig,
  118. \Magento\Sales\Model\Order\Pdf\Total\Factory $pdfTotalFactory,
  119. \Magento\Sales\Model\Order\Pdf\ItemsFactory $pdfItemsFactory,
  120. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  121. \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation,
  122. \Magento\Sales\Model\Order\Address\Renderer $addressRenderer,
  123. array $data = []
  124. ) {
  125. $this->addressRenderer = $addressRenderer;
  126. $this->_paymentData = $paymentData;
  127. $this->_localeDate = $localeDate;
  128. $this->string = $string;
  129. $this->_scopeConfig = $scopeConfig;
  130. $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
  131. $this->_rootDirectory = $filesystem->getDirectoryRead(DirectoryList::ROOT);
  132. $this->_pdfConfig = $pdfConfig;
  133. $this->_pdfTotalFactory = $pdfTotalFactory;
  134. $this->_pdfItemsFactory = $pdfItemsFactory;
  135. $this->inlineTranslation = $inlineTranslation;
  136. parent::__construct($data);
  137. }
  138. /**
  139. * Returns the total width in points of the string using the specified font and size.
  140. *
  141. * This is not the most efficient way to perform this calculation. I'm
  142. * concentrating optimization efforts on the upcoming layout manager class.
  143. * Similar calculations exist inside the layout manager class, but widths are
  144. * generally calculated only after determining line fragments.
  145. *
  146. * @param string $string
  147. * @param \Zend_Pdf_Resource_Font $font
  148. * @param float $fontSize Font size in points
  149. * @return float
  150. */
  151. public function widthForStringUsingFontSize($string, $font, $fontSize)
  152. {
  153. $drawingString = '"libiconv"' == ICONV_IMPL ? iconv(
  154. 'UTF-8',
  155. 'UTF-16BE//IGNORE',
  156. $string
  157. ) : @iconv(
  158. 'UTF-8',
  159. 'UTF-16BE',
  160. $string
  161. );
  162. $characters = [];
  163. for ($i = 0; $i < strlen($drawingString); $i++) {
  164. $characters[] = ord($drawingString[$i++]) << 8 | ord($drawingString[$i]);
  165. }
  166. $glyphs = $font->glyphNumbersForCharacters($characters);
  167. $widths = $font->widthsForGlyphs($glyphs);
  168. $stringWidth = array_sum($widths) / $font->getUnitsPerEm() * $fontSize;
  169. return $stringWidth;
  170. }
  171. /**
  172. * Calculate coordinates to draw something in a column aligned to the right
  173. *
  174. * @param string $string
  175. * @param int $x
  176. * @param int $columnWidth
  177. * @param \Zend_Pdf_Resource_Font $font
  178. * @param int $fontSize
  179. * @param int $padding
  180. * @return int
  181. */
  182. public function getAlignRight($string, $x, $columnWidth, \Zend_Pdf_Resource_Font $font, $fontSize, $padding = 5)
  183. {
  184. $width = $this->widthForStringUsingFontSize($string, $font, $fontSize);
  185. return $x + $columnWidth - $width - $padding;
  186. }
  187. /**
  188. * Calculate coordinates to draw something in a column aligned to the center
  189. *
  190. * @param string $string
  191. * @param int $x
  192. * @param int $columnWidth
  193. * @param \Zend_Pdf_Resource_Font $font
  194. * @param int $fontSize
  195. * @return int
  196. */
  197. public function getAlignCenter($string, $x, $columnWidth, \Zend_Pdf_Resource_Font $font, $fontSize)
  198. {
  199. $width = $this->widthForStringUsingFontSize($string, $font, $fontSize);
  200. return $x + round(($columnWidth - $width) / 2);
  201. }
  202. /**
  203. * Insert logo to pdf page
  204. *
  205. * @param \Zend_Pdf_Page &$page
  206. * @param string|null $store
  207. * @return void
  208. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  209. */
  210. protected function insertLogo(&$page, $store = null)
  211. {
  212. $this->y = $this->y ? $this->y : 815;
  213. $image = $this->_scopeConfig->getValue(
  214. 'sales/identity/logo',
  215. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  216. $store
  217. );
  218. if ($image) {
  219. $imagePath = '/sales/store/logo/' . $image;
  220. if ($this->_mediaDirectory->isFile($imagePath)) {
  221. $image = \Zend_Pdf_Image::imageWithPath($this->_mediaDirectory->getAbsolutePath($imagePath));
  222. $top = 830;
  223. //top border of the page
  224. $widthLimit = 270;
  225. //half of the page width
  226. $heightLimit = 270;
  227. //assuming the image is not a "skyscraper"
  228. $width = $image->getPixelWidth();
  229. $height = $image->getPixelHeight();
  230. //preserving aspect ratio (proportions)
  231. $ratio = $width / $height;
  232. if ($ratio > 1 && $width > $widthLimit) {
  233. $width = $widthLimit;
  234. $height = $width / $ratio;
  235. } elseif ($ratio < 1 && $height > $heightLimit) {
  236. $height = $heightLimit;
  237. $width = $height * $ratio;
  238. } elseif ($ratio == 1 && $height > $heightLimit) {
  239. $height = $heightLimit;
  240. $width = $widthLimit;
  241. }
  242. $y1 = $top - $height;
  243. $y2 = $top;
  244. $x1 = 25;
  245. $x2 = $x1 + $width;
  246. //coordinates after transformation are rounded by Zend
  247. $page->drawImage($image, $x1, $y1, $x2, $y2);
  248. $this->y = $y1 - 10;
  249. }
  250. }
  251. }
  252. /**
  253. * Insert address to pdf page
  254. *
  255. * @param \Zend_Pdf_Page &$page
  256. * @param string|null $store
  257. * @return void
  258. */
  259. protected function insertAddress(&$page, $store = null)
  260. {
  261. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0));
  262. $font = $this->_setFontRegular($page, 10);
  263. $page->setLineWidth(0);
  264. $this->y = $this->y ? $this->y : 815;
  265. $top = 815;
  266. $values = explode(
  267. "\n",
  268. $this->_scopeConfig->getValue(
  269. 'sales/identity/address',
  270. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  271. $store
  272. )
  273. );
  274. foreach ($values as $value) {
  275. if ($value !== '') {
  276. $value = preg_replace('/<br[^>]*>/i', "\n", $value);
  277. foreach ($this->string->split($value, 45, true, true) as $_value) {
  278. $page->drawText(
  279. trim(strip_tags($_value)),
  280. $this->getAlignRight($_value, 130, 440, $font, 10),
  281. $top,
  282. 'UTF-8'
  283. );
  284. $top -= 10;
  285. }
  286. }
  287. }
  288. $this->y = $this->y > $top ? $top : $this->y;
  289. }
  290. /**
  291. * Format address
  292. *
  293. * @param string $address
  294. * @return array
  295. */
  296. protected function _formatAddress($address)
  297. {
  298. $return = [];
  299. foreach (explode('|', $address) as $str) {
  300. foreach ($this->string->split($str, 45, true, true) as $part) {
  301. if (empty($part)) {
  302. continue;
  303. }
  304. $return[] = $part;
  305. }
  306. }
  307. return $return;
  308. }
  309. /**
  310. * Calculate address height
  311. *
  312. * @param array $address
  313. * @return int Height
  314. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  315. */
  316. protected function _calcAddressHeight($address)
  317. {
  318. $y = 0;
  319. foreach ($address as $value) {
  320. if ($value !== '') {
  321. $text = [];
  322. foreach ($this->string->split($value, 55, true, true) as $_value) {
  323. $text[] = $_value;
  324. }
  325. foreach ($text as $part) {
  326. $y += 15;
  327. }
  328. }
  329. }
  330. return $y;
  331. }
  332. /**
  333. * Insert order to pdf page
  334. *
  335. * @param \Zend_Pdf_Page &$page
  336. * @param \Magento\Sales\Model\Order $obj
  337. * @param bool $putOrderId
  338. * @return void
  339. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  340. * @SuppressWarnings(PHPMD.NPathComplexity)
  341. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  342. */
  343. protected function insertOrder(&$page, $obj, $putOrderId = true)
  344. {
  345. if ($obj instanceof \Magento\Sales\Model\Order) {
  346. $shipment = null;
  347. $order = $obj;
  348. } elseif ($obj instanceof \Magento\Sales\Model\Order\Shipment) {
  349. $shipment = $obj;
  350. $order = $shipment->getOrder();
  351. }
  352. $this->y = $this->y ? $this->y : 815;
  353. $top = $this->y;
  354. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0.45));
  355. $page->setLineColor(new \Zend_Pdf_Color_GrayScale(0.45));
  356. $page->drawRectangle(25, $top, 570, $top - 55);
  357. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1));
  358. $this->setDocHeaderCoordinates([25, $top, 570, $top - 55]);
  359. $this->_setFontRegular($page, 10);
  360. if ($putOrderId) {
  361. $page->drawText(__('Order # ') . $order->getRealOrderId(), 35, $top -= 30, 'UTF-8');
  362. $top +=15;
  363. }
  364. $top -=30;
  365. $page->drawText(
  366. __('Order Date: ') .
  367. $this->_localeDate->formatDate(
  368. $this->_localeDate->scopeDate(
  369. $order->getStore(),
  370. $order->getCreatedAt(),
  371. true
  372. ),
  373. \IntlDateFormatter::MEDIUM,
  374. false
  375. ),
  376. 35,
  377. $top,
  378. 'UTF-8'
  379. );
  380. $top -= 10;
  381. $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92));
  382. $page->setLineColor(new \Zend_Pdf_Color_GrayScale(0.5));
  383. $page->setLineWidth(0.5);
  384. $page->drawRectangle(25, $top, 275, $top - 25);
  385. $page->drawRectangle(275, $top, 570, $top - 25);
  386. /* Calculate blocks info */
  387. /* Billing Address */
  388. $billingAddress = $this->_formatAddress($this->addressRenderer->format($order->getBillingAddress(), 'pdf'));
  389. /* Payment */
  390. $paymentInfo = $this->_paymentData->getInfoBlock($order->getPayment())->setIsSecureMode(true)->toPdf();
  391. $paymentInfo = htmlspecialchars_decode($paymentInfo, ENT_QUOTES);
  392. $payment = explode('{{pdf_row_separator}}', $paymentInfo);
  393. foreach ($payment as $key => $value) {
  394. if (strip_tags(trim($value)) == '') {
  395. unset($payment[$key]);
  396. }
  397. }
  398. reset($payment);
  399. /* Shipping Address and Method */
  400. if (!$order->getIsVirtual()) {
  401. /* Shipping Address */
  402. $shippingAddress = $this->_formatAddress(
  403. $this->addressRenderer->format($order->getShippingAddress(), 'pdf')
  404. );
  405. $shippingMethod = $order->getShippingDescription();
  406. }
  407. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0));
  408. $this->_setFontBold($page, 12);
  409. $page->drawText(__('Sold to:'), 35, $top - 15, 'UTF-8');
  410. if (!$order->getIsVirtual()) {
  411. $page->drawText(__('Ship to:'), 285, $top - 15, 'UTF-8');
  412. } else {
  413. $page->drawText(__('Payment Method:'), 285, $top - 15, 'UTF-8');
  414. }
  415. $addressesHeight = $this->_calcAddressHeight($billingAddress);
  416. if (isset($shippingAddress)) {
  417. $addressesHeight = max($addressesHeight, $this->_calcAddressHeight($shippingAddress));
  418. }
  419. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1));
  420. $page->drawRectangle(25, $top - 25, 570, $top - 33 - $addressesHeight);
  421. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0));
  422. $this->_setFontRegular($page, 10);
  423. $this->y = $top - 40;
  424. $addressesStartY = $this->y;
  425. foreach ($billingAddress as $value) {
  426. if ($value !== '') {
  427. $text = [];
  428. foreach ($this->string->split($value, 45, true, true) as $_value) {
  429. $text[] = $_value;
  430. }
  431. foreach ($text as $part) {
  432. $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8');
  433. $this->y -= 15;
  434. }
  435. }
  436. }
  437. $addressesEndY = $this->y;
  438. if (!$order->getIsVirtual()) {
  439. $this->y = $addressesStartY;
  440. foreach ($shippingAddress as $value) {
  441. if ($value !== '') {
  442. $text = [];
  443. foreach ($this->string->split($value, 45, true, true) as $_value) {
  444. $text[] = $_value;
  445. }
  446. foreach ($text as $part) {
  447. $page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8');
  448. $this->y -= 15;
  449. }
  450. }
  451. }
  452. $addressesEndY = min($addressesEndY, $this->y);
  453. $this->y = $addressesEndY;
  454. $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92));
  455. $page->setLineWidth(0.5);
  456. $page->drawRectangle(25, $this->y, 275, $this->y - 25);
  457. $page->drawRectangle(275, $this->y, 570, $this->y - 25);
  458. $this->y -= 15;
  459. $this->_setFontBold($page, 12);
  460. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0));
  461. $page->drawText(__('Payment Method'), 35, $this->y, 'UTF-8');
  462. $page->drawText(__('Shipping Method:'), 285, $this->y, 'UTF-8');
  463. $this->y -= 10;
  464. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1));
  465. $this->_setFontRegular($page, 10);
  466. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0));
  467. $paymentLeft = 35;
  468. $yPayments = $this->y - 15;
  469. } else {
  470. $yPayments = $addressesStartY;
  471. $paymentLeft = 285;
  472. }
  473. foreach ($payment as $value) {
  474. if (trim($value) != '') {
  475. //Printing "Payment Method" lines
  476. $value = preg_replace('/<br[^>]*>/i', "\n", $value);
  477. foreach ($this->string->split($value, 45, true, true) as $_value) {
  478. $page->drawText(strip_tags(trim($_value)), $paymentLeft, $yPayments, 'UTF-8');
  479. $yPayments -= 15;
  480. }
  481. }
  482. }
  483. if ($order->getIsVirtual()) {
  484. // replacement of Shipments-Payments rectangle block
  485. $yPayments = min($addressesEndY, $yPayments);
  486. $page->drawLine(25, $top - 25, 25, $yPayments);
  487. $page->drawLine(570, $top - 25, 570, $yPayments);
  488. $page->drawLine(25, $yPayments, 570, $yPayments);
  489. $this->y = $yPayments - 15;
  490. } else {
  491. $topMargin = 15;
  492. $methodStartY = $this->y;
  493. $this->y -= 15;
  494. foreach ($this->string->split($shippingMethod, 45, true, true) as $_value) {
  495. $page->drawText(strip_tags(trim($_value)), 285, $this->y, 'UTF-8');
  496. $this->y -= 15;
  497. }
  498. $yShipments = $this->y;
  499. $totalShippingChargesText = "("
  500. . __('Total Shipping Charges')
  501. . " "
  502. . $order->formatPriceTxt($order->getShippingAmount())
  503. . ")";
  504. $page->drawText($totalShippingChargesText, 285, $yShipments - $topMargin, 'UTF-8');
  505. $yShipments -= $topMargin + 10;
  506. $tracks = [];
  507. if ($shipment) {
  508. $tracks = $shipment->getAllTracks();
  509. }
  510. if (count($tracks)) {
  511. $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92));
  512. $page->setLineWidth(0.5);
  513. $page->drawRectangle(285, $yShipments, 510, $yShipments - 10);
  514. $page->drawLine(400, $yShipments, 400, $yShipments - 10);
  515. //$page->drawLine(510, $yShipments, 510, $yShipments - 10);
  516. $this->_setFontRegular($page, 9);
  517. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0));
  518. //$page->drawText(__('Carrier'), 290, $yShipments - 7 , 'UTF-8');
  519. $page->drawText(__('Title'), 290, $yShipments - 7, 'UTF-8');
  520. $page->drawText(__('Number'), 410, $yShipments - 7, 'UTF-8');
  521. $yShipments -= 20;
  522. $this->_setFontRegular($page, 8);
  523. foreach ($tracks as $track) {
  524. $maxTitleLen = 45;
  525. $endOfTitle = strlen($track->getTitle()) > $maxTitleLen ? '...' : '';
  526. $truncatedTitle = substr($track->getTitle(), 0, $maxTitleLen) . $endOfTitle;
  527. $page->drawText($truncatedTitle, 292, $yShipments, 'UTF-8');
  528. $page->drawText($track->getNumber(), 410, $yShipments, 'UTF-8');
  529. $yShipments -= $topMargin - 5;
  530. }
  531. } else {
  532. $yShipments -= $topMargin - 5;
  533. }
  534. $currentY = min($yPayments, $yShipments);
  535. // replacement of Shipments-Payments rectangle block
  536. $page->drawLine(25, $methodStartY, 25, $currentY);
  537. //left
  538. $page->drawLine(25, $currentY, 570, $currentY);
  539. //bottom
  540. $page->drawLine(570, $currentY, 570, $methodStartY);
  541. //right
  542. $this->y = $currentY;
  543. $this->y -= 15;
  544. }
  545. }
  546. /**
  547. * Insert title and number for concrete document type
  548. *
  549. * @param \Zend_Pdf_Page $page
  550. * @param string $text
  551. * @return void
  552. */
  553. public function insertDocumentNumber(\Zend_Pdf_Page $page, $text)
  554. {
  555. $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1));
  556. $this->_setFontRegular($page, 10);
  557. $docHeader = $this->getDocHeaderCoordinates();
  558. $page->drawText($text, 35, $docHeader[1] - 15, 'UTF-8');
  559. }
  560. /**
  561. * Sort totals list
  562. *
  563. * @param array $a
  564. * @param array $b
  565. * @return int
  566. */
  567. protected function _sortTotalsList($a, $b)
  568. {
  569. if (!isset($a['sort_order']) || !isset($b['sort_order'])) {
  570. return 0;
  571. }
  572. return $a['sort_order'] <=> $b['sort_order'];
  573. }
  574. /**
  575. * Return total list
  576. *
  577. * @return \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal[] Array of totals
  578. */
  579. protected function _getTotalsList()
  580. {
  581. $totals = $this->_pdfConfig->getTotals();
  582. usort($totals, [$this, '_sortTotalsList']);
  583. $totalModels = [];
  584. foreach ($totals as $totalInfo) {
  585. $class = empty($totalInfo['model']) ? null : $totalInfo['model'];
  586. $totalModel = $this->_pdfTotalFactory->create($class);
  587. $totalModel->setData($totalInfo);
  588. $totalModels[] = $totalModel;
  589. }
  590. return $totalModels;
  591. }
  592. /**
  593. * Insert totals to pdf page
  594. *
  595. * @param \Zend_Pdf_Page $page
  596. * @param \Magento\Sales\Model\AbstractModel $source
  597. * @return \Zend_Pdf_Page
  598. */
  599. protected function insertTotals($page, $source)
  600. {
  601. $order = $source->getOrder();
  602. $totals = $this->_getTotalsList();
  603. $lineBlock = ['lines' => [], 'height' => 15];
  604. foreach ($totals as $total) {
  605. $total->setOrder($order)->setSource($source);
  606. if ($total->canDisplay()) {
  607. $total->setFontSize(10);
  608. foreach ($total->getTotalsForDisplay() as $totalData) {
  609. $lineBlock['lines'][] = [
  610. [
  611. 'text' => $totalData['label'],
  612. 'feed' => 475,
  613. 'align' => 'right',
  614. 'font_size' => $totalData['font_size'],
  615. 'font' => 'bold',
  616. ],
  617. [
  618. 'text' => $totalData['amount'],
  619. 'feed' => 565,
  620. 'align' => 'right',
  621. 'font_size' => $totalData['font_size'],
  622. 'font' => 'bold'
  623. ],
  624. ];
  625. }
  626. }
  627. }
  628. $this->y -= 20;
  629. $page = $this->drawLineBlocks($page, [$lineBlock]);
  630. return $page;
  631. }
  632. /**
  633. * Parse item description
  634. *
  635. * @param \Magento\Framework\DataObject $item
  636. * @return array
  637. */
  638. protected function _parseItemDescription($item)
  639. {
  640. $matches = [];
  641. $description = $item->getDescription();
  642. if (preg_match_all('/<li.*?>(.*?)<\/li>/i', $description, $matches)) {
  643. return $matches[1];
  644. }
  645. return [$description];
  646. }
  647. /**
  648. * Before getPdf processing
  649. *
  650. * @return void
  651. */
  652. protected function _beforeGetPdf()
  653. {
  654. $this->inlineTranslation->suspend();
  655. }
  656. /**
  657. * After getPdf processing
  658. *
  659. * @return void
  660. */
  661. protected function _afterGetPdf()
  662. {
  663. $this->inlineTranslation->resume();
  664. }
  665. /**
  666. * Format option value process
  667. *
  668. * @param array|string $value
  669. * @param \Magento\Sales\Model\Order $order
  670. * @return string
  671. */
  672. protected function _formatOptionValue($value, $order)
  673. {
  674. $resultValue = '';
  675. if (is_array($value)) {
  676. if (isset($value['qty'])) {
  677. $resultValue .= sprintf('%d', $value['qty']) . ' x ';
  678. }
  679. $resultValue .= $value['title'];
  680. if (isset($value['price'])) {
  681. $resultValue .= " " . $order->formatPrice($value['price']);
  682. }
  683. return $resultValue;
  684. } else {
  685. return $value;
  686. }
  687. }
  688. /**
  689. * Initialize renderer process
  690. *
  691. * @param string $type
  692. * @return void
  693. */
  694. protected function _initRenderer($type)
  695. {
  696. $rendererData = $this->_pdfConfig->getRenderersPerProduct($type);
  697. foreach ($rendererData as $productType => $renderer) {
  698. $this->_renderers[$productType] = ['model' => $renderer, 'renderer' => null];
  699. }
  700. }
  701. /**
  702. * Retrieve renderer model
  703. *
  704. * @param string $type
  705. * @return \Magento\Sales\Model\Order\Pdf\Items\AbstractItems
  706. * @throws \Magento\Framework\Exception\LocalizedException
  707. */
  708. protected function _getRenderer($type)
  709. {
  710. if (!isset($this->_renderers[$type])) {
  711. $type = 'default';
  712. }
  713. if (!isset($this->_renderers[$type])) {
  714. throw new \Magento\Framework\Exception\LocalizedException(__('We found an invalid renderer model.'));
  715. }
  716. if ($this->_renderers[$type]['renderer'] === null) {
  717. $this->_renderers[$type]['renderer'] = $this->_pdfItemsFactory->get($this->_renderers[$type]['model']);
  718. }
  719. return $this->_renderers[$type]['renderer'];
  720. }
  721. /**
  722. * Public method of protected @see _getRenderer()
  723. *
  724. * Retrieve renderer model
  725. *
  726. * @param string $type
  727. * @return \Magento\Sales\Model\Order\Pdf\Items\AbstractItems
  728. */
  729. public function getRenderer($type)
  730. {
  731. return $this->_getRenderer($type);
  732. }
  733. /**
  734. * Draw Item process
  735. *
  736. * @param \Magento\Framework\DataObject $item
  737. * @param \Zend_Pdf_Page $page
  738. * @param \Magento\Sales\Model\Order $order
  739. * @return \Zend_Pdf_Page
  740. */
  741. protected function _drawItem(
  742. \Magento\Framework\DataObject $item,
  743. \Zend_Pdf_Page $page,
  744. \Magento\Sales\Model\Order $order
  745. ) {
  746. $type = $item->getOrderItem()->getProductType();
  747. $renderer = $this->_getRenderer($type);
  748. $renderer->setOrder($order);
  749. $renderer->setItem($item);
  750. $renderer->setPdf($this);
  751. $renderer->setPage($page);
  752. $renderer->setRenderedModel($this);
  753. $renderer->draw();
  754. return $renderer->getPage();
  755. }
  756. /**
  757. * Set font as regular
  758. *
  759. * @param \Zend_Pdf_Page $object
  760. * @param int $size
  761. * @return \Zend_Pdf_Resource_Font
  762. */
  763. protected function _setFontRegular($object, $size = 7)
  764. {
  765. $font = \Zend_Pdf_Font::fontWithPath(
  766. $this->_rootDirectory->getAbsolutePath('lib/internal/GnuFreeFont/FreeSerif.ttf')
  767. );
  768. $object->setFont($font, $size);
  769. return $font;
  770. }
  771. /**
  772. * Set font as bold
  773. *
  774. * @param \Zend_Pdf_Page $object
  775. * @param int $size
  776. * @return \Zend_Pdf_Resource_Font
  777. */
  778. protected function _setFontBold($object, $size = 7)
  779. {
  780. $font = \Zend_Pdf_Font::fontWithPath(
  781. $this->_rootDirectory->getAbsolutePath('lib/internal/GnuFreeFont/FreeSerifBold.ttf')
  782. );
  783. $object->setFont($font, $size);
  784. return $font;
  785. }
  786. /**
  787. * Set font as italic
  788. *
  789. * @param \Zend_Pdf_Page $object
  790. * @param int $size
  791. * @return \Zend_Pdf_Resource_Font
  792. */
  793. protected function _setFontItalic($object, $size = 7)
  794. {
  795. $font = \Zend_Pdf_Font::fontWithPath(
  796. $this->_rootDirectory->getAbsolutePath('lib/internal/GnuFreeFont/FreeSerifItalic.ttf')
  797. );
  798. $object->setFont($font, $size);
  799. return $font;
  800. }
  801. /**
  802. * Set PDF object
  803. *
  804. * @param \Zend_Pdf $pdf
  805. * @return $this
  806. */
  807. protected function _setPdf(\Zend_Pdf $pdf)
  808. {
  809. $this->_pdf = $pdf;
  810. return $this;
  811. }
  812. /**
  813. * Retrieve PDF object
  814. *
  815. * @throws \Magento\Framework\Exception\LocalizedException
  816. * @return \Zend_Pdf
  817. */
  818. protected function _getPdf()
  819. {
  820. if (!$this->_pdf instanceof \Zend_Pdf) {
  821. throw new \Magento\Framework\Exception\LocalizedException(__('Please define the PDF object before using.'));
  822. }
  823. return $this->_pdf;
  824. }
  825. /**
  826. * Create new page and assign to PDF object
  827. *
  828. * @param array $settings
  829. * @return \Zend_Pdf_Page
  830. */
  831. public function newPage(array $settings = [])
  832. {
  833. $pageSize = !empty($settings['page_size']) ? $settings['page_size'] : \Zend_Pdf_Page::SIZE_A4;
  834. $page = $this->_getPdf()->newPage($pageSize);
  835. $this->_getPdf()->pages[] = $page;
  836. $this->y = 800;
  837. return $page;
  838. }
  839. /**
  840. * Draw lines
  841. *
  842. * Draw items array format:
  843. * lines array;array of line blocks (required)
  844. * shift int; full line height (optional)
  845. * height int;line spacing (default 10)
  846. *
  847. * line block has line columns array
  848. *
  849. * column array format
  850. * text string|array; draw text (required)
  851. * feed int; x position (required)
  852. * font string; font style, optional: bold, italic, regular
  853. * font_file string; path to font file (optional for use your custom font)
  854. * font_size int; font size (default 7)
  855. * align string; text align (also see feed parameter), optional left, right
  856. * height int;line spacing (default 10)
  857. *
  858. * @param \Zend_Pdf_Page $page
  859. * @param array $draw
  860. * @param array $pageSettings
  861. * @throws \Magento\Framework\Exception\LocalizedException
  862. * @return \Zend_Pdf_Page
  863. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  864. * @SuppressWarnings(PHPMD.NPathComplexity)
  865. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  866. */
  867. public function drawLineBlocks(\Zend_Pdf_Page $page, array $draw, array $pageSettings = [])
  868. {
  869. foreach ($draw as $itemsProp) {
  870. if (!isset($itemsProp['lines']) || !is_array($itemsProp['lines'])) {
  871. throw new \Magento\Framework\Exception\LocalizedException(
  872. __('We don\'t recognize the draw line data. Please define the "lines" array.')
  873. );
  874. }
  875. $lines = $itemsProp['lines'];
  876. $height = isset($itemsProp['height']) ? $itemsProp['height'] : 10;
  877. if (empty($itemsProp['shift'])) {
  878. $shift = 0;
  879. foreach ($lines as $line) {
  880. $maxHeight = 0;
  881. foreach ($line as $column) {
  882. $lineSpacing = !empty($column['height']) ? $column['height'] : $height;
  883. if (!is_array($column['text'])) {
  884. $column['text'] = [$column['text']];
  885. }
  886. $top = 0;
  887. foreach ($column['text'] as $part) {
  888. $top += $lineSpacing;
  889. }
  890. $maxHeight = $top > $maxHeight ? $top : $maxHeight;
  891. }
  892. $shift += $maxHeight;
  893. }
  894. $itemsProp['shift'] = $shift;
  895. }
  896. if ($this->y - $itemsProp['shift'] < 15) {
  897. $page = $this->newPage($pageSettings);
  898. }
  899. foreach ($lines as $line) {
  900. $maxHeight = 0;
  901. foreach ($line as $column) {
  902. $fontSize = empty($column['font_size']) ? 10 : $column['font_size'];
  903. if (!empty($column['font_file'])) {
  904. $font = \Zend_Pdf_Font::fontWithPath($column['font_file']);
  905. $page->setFont($font, $fontSize);
  906. } else {
  907. $fontStyle = empty($column['font']) ? 'regular' : $column['font'];
  908. switch ($fontStyle) {
  909. case 'bold':
  910. $font = $this->_setFontBold($page, $fontSize);
  911. break;
  912. case 'italic':
  913. $font = $this->_setFontItalic($page, $fontSize);
  914. break;
  915. default:
  916. $font = $this->_setFontRegular($page, $fontSize);
  917. break;
  918. }
  919. }
  920. if (!is_array($column['text'])) {
  921. $column['text'] = [$column['text']];
  922. }
  923. $lineSpacing = !empty($column['height']) ? $column['height'] : $height;
  924. $top = 0;
  925. foreach ($column['text'] as $part) {
  926. if ($this->y - $lineSpacing < 15) {
  927. $page = $this->newPage($pageSettings);
  928. }
  929. $feed = $column['feed'];
  930. $textAlign = empty($column['align']) ? 'left' : $column['align'];
  931. $width = empty($column['width']) ? 0 : $column['width'];
  932. switch ($textAlign) {
  933. case 'right':
  934. if ($width) {
  935. $feed = $this->getAlignRight($part, $feed, $width, $font, $fontSize);
  936. } else {
  937. $feed = $feed - $this->widthForStringUsingFontSize($part, $font, $fontSize);
  938. }
  939. break;
  940. case 'center':
  941. if ($width) {
  942. $feed = $this->getAlignCenter($part, $feed, $width, $font, $fontSize);
  943. }
  944. break;
  945. default:
  946. break;
  947. }
  948. $page->drawText($part, $feed, $this->y - $top, 'UTF-8');
  949. $top += $lineSpacing;
  950. }
  951. $maxHeight = $top > $maxHeight ? $top : $maxHeight;
  952. }
  953. $this->y -= $maxHeight;
  954. }
  955. }
  956. return $page;
  957. }
  958. }