|
|
@@ -3,40 +3,57 @@
|
|
|
namespace common\components;
|
|
|
|
|
|
/**
|
|
|
- * 散客分享海报(OSS:poster/blank.png 动态画布 + 水印合成)
|
|
|
+ * 散客分享海报(OSS:poster/blank.png 动态画布 + 水印合成,布局参考快团团)
|
|
|
*/
|
|
|
class PosterUtil
|
|
|
{
|
|
|
- /** 与 poster/1234.jpg 同目录的 10×10 白底,经 m_pad 拉成本次海报画布 */
|
|
|
const BLANK_CANVAS = 'poster/blank.png';
|
|
|
|
|
|
const FONT = 'd3F5LW1pY3JvaGVp';
|
|
|
|
|
|
const POSTER_W = 750;
|
|
|
|
|
|
- /** 画布左右、顶部留白(各 20px) */
|
|
|
const CANVAS_PAD_X = 20;
|
|
|
|
|
|
const CANVAS_PAD_Y = 20;
|
|
|
|
|
|
- /** 底部文案每行最多字数 */
|
|
|
- const NAME_CHARS_PER_LINE = 13;
|
|
|
+ /** 文案每行最多字数(商品名、注意事项) */
|
|
|
+ const TEXT_CHARS_PER_LINE = 13;
|
|
|
|
|
|
- /** 商品图显示宽度 = POSTER_W - 2×CANVAS_PAD_X */
|
|
|
const SLOT_W = 710;
|
|
|
|
|
|
- /** 底部价格/标题/太阳码固定区 */
|
|
|
- const FOOTER_H = 360;
|
|
|
+ const FOOTER_H = 300;
|
|
|
|
|
|
- /** 主图区温和下限,避免扁图海报过矮 */
|
|
|
const IMG_H_MIN = 240;
|
|
|
|
|
|
const IMG_H_MAX = 880;
|
|
|
|
|
|
- const TOTAL_H_MAX = 1600;
|
|
|
+ const TOTAL_H_MAX = 2000;
|
|
|
|
|
|
const CANVAS_COLOR = 'FFFFFF';
|
|
|
|
|
|
+ const SHOP_LOGO_SIZE = 72;
|
|
|
+
|
|
|
+ const SHOP_LOGO_GAP = 12;
|
|
|
+
|
|
|
+ const SHOP_TITLE_SIZE = 30;
|
|
|
+
|
|
|
+ const SHOP_TITLE_MAX_LEN = 18;
|
|
|
+
|
|
|
+ const HEADER_BOTTOM_GAP = 16;
|
|
|
+
|
|
|
+ const MID_AFTER_COVER_GAP = 16;
|
|
|
+
|
|
|
+ const MID_BLOCK_GAP = 10;
|
|
|
+
|
|
|
+ const MID_LINE_GAP = 6;
|
|
|
+
|
|
|
+ const MID_NAME_SIZE = 32;
|
|
|
+
|
|
|
+ const MID_REMARK_SIZE = 26;
|
|
|
+
|
|
|
+ const REMARK_PREFIX = '注意:';
|
|
|
+
|
|
|
const QR_SIZE = 220;
|
|
|
|
|
|
const QR_PAD_RIGHT = 36;
|
|
|
@@ -53,13 +70,76 @@ class PosterUtil
|
|
|
|
|
|
const FOOTER_PRICE_SIZE = 56;
|
|
|
|
|
|
- const FOOTER_NAME_SIZE = 36;
|
|
|
-
|
|
|
const FOOTER_STAFF_SIZE = 26;
|
|
|
|
|
|
/**
|
|
|
- * 海报标题过长时截断,避免压住底部文案区
|
|
|
+ * 与 admin/home/me 一致:门店展示名、Logo 对象路径、itemRemark
|
|
|
*/
|
|
|
+ public static function withShopAndRemark(array $options, $shop, $sj, $itemOrRemark = null)
|
|
|
+ {
|
|
|
+ $meta = self::resolveShopPosterMeta($shop, $sj);
|
|
|
+ $options['shopTitle'] = $meta['shopTitle'];
|
|
|
+ $options['shopLogo'] = $meta['shopLogo'];
|
|
|
+ if (is_string($itemOrRemark)) {
|
|
|
+ $options['itemRemark'] = trim($itemOrRemark);
|
|
|
+ } elseif ($itemOrRemark !== null) {
|
|
|
+ $options['itemRemark'] = trim($itemOrRemark->itemRemark ?? '');
|
|
|
+ } else {
|
|
|
+ $options['itemRemark'] = trim($options['itemRemark'] ?? '');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $options;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * hdApp/ghsApp me 页门店标题:sjName·shopName(首店仅 sjName)
|
|
|
+ */
|
|
|
+ public static function buildShopDisplayTitle($sjName, $shopName)
|
|
|
+ {
|
|
|
+ $sjName = trim((string)$sjName);
|
|
|
+ $shopName = trim((string)$shopName);
|
|
|
+ if ($shopName === '' || $shopName === '首店') {
|
|
|
+ return $sjName;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $sjName . '·' . $shopName;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function resolveShopPosterMeta($shop, $sj = null)
|
|
|
+ {
|
|
|
+ $sjName = '';
|
|
|
+ if ($shop) {
|
|
|
+ $sjName = trim((string)($shop->merchantName ?? ''));
|
|
|
+ }
|
|
|
+ if ($sj !== null) {
|
|
|
+ if (is_array($sj)) {
|
|
|
+ $sjName = trim((string)($sj['name'] ?? $sjName));
|
|
|
+ } elseif (is_object($sj) && isset($sj->name)) {
|
|
|
+ $sjName = trim((string)$sj->name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $shopName = $shop ? trim((string)($shop->shopName ?? '')) : '';
|
|
|
+ $title = self::buildShopDisplayTitle($sjName, $shopName);
|
|
|
+ if (mb_strlen($title, 'UTF-8') > self::SHOP_TITLE_MAX_LEN) {
|
|
|
+ $title = mb_substr($title, 0, self::SHOP_TITLE_MAX_LEN, 'UTF-8') . '…';
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'shopTitle' => $title,
|
|
|
+ 'shopLogo' => self::resolveShopLogoObjectPath($shop),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function resolveShopLogoObjectPath($shop)
|
|
|
+ {
|
|
|
+ $avatar = '';
|
|
|
+ if ($shop && !empty($shop->avatar)) {
|
|
|
+ $avatar = $shop->avatar;
|
|
|
+ }
|
|
|
+
|
|
|
+ return imgUtil::normalizeOssObjectPath($avatar);
|
|
|
+ }
|
|
|
+
|
|
|
public static function truncateText($text, $maxLen = 14)
|
|
|
{
|
|
|
$text = trim((string)$text);
|
|
|
@@ -69,32 +149,30 @@ class PosterUtil
|
|
|
if (mb_strlen($text, 'UTF-8') > $maxLen) {
|
|
|
return mb_substr($text, 0, $maxLen, 'UTF-8') . '…';
|
|
|
}
|
|
|
+
|
|
|
return $text;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 商品名拆成最多两行:首行 13 字;次行剩余,超过 13 字则省略
|
|
|
- *
|
|
|
* @return array{0:string,1:string}
|
|
|
*/
|
|
|
- public static function splitPosterNameLines($name, $maxPerLine = self::NAME_CHARS_PER_LINE)
|
|
|
+ public static function splitPosterTextLines($text, $maxPerLine = self::TEXT_CHARS_PER_LINE)
|
|
|
{
|
|
|
- $name = trim((string)$name);
|
|
|
- if ($name === '') {
|
|
|
+ $text = trim((string)$text);
|
|
|
+ if ($text === '') {
|
|
|
return ['', ''];
|
|
|
}
|
|
|
$maxPerLine = (int)$maxPerLine;
|
|
|
if ($maxPerLine < 1) {
|
|
|
- $maxPerLine = self::NAME_CHARS_PER_LINE;
|
|
|
+ $maxPerLine = self::TEXT_CHARS_PER_LINE;
|
|
|
}
|
|
|
- $len = mb_strlen($name, 'UTF-8');
|
|
|
+ $len = mb_strlen($text, 'UTF-8');
|
|
|
if ($len <= $maxPerLine) {
|
|
|
- return [$name, ''];
|
|
|
+ return [$text, ''];
|
|
|
}
|
|
|
- $line1 = mb_substr($name, 0, $maxPerLine, 'UTF-8');
|
|
|
- $rest = mb_substr($name, $maxPerLine, null, 'UTF-8');
|
|
|
- $restLen = mb_strlen($rest, 'UTF-8');
|
|
|
- if ($restLen <= $maxPerLine) {
|
|
|
+ $line1 = mb_substr($text, 0, $maxPerLine, 'UTF-8');
|
|
|
+ $rest = mb_substr($text, $maxPerLine, null, 'UTF-8');
|
|
|
+ if (mb_strlen($rest, 'UTF-8') <= $maxPerLine) {
|
|
|
return [$line1, $rest];
|
|
|
}
|
|
|
$line2 = mb_substr($rest, 0, $maxPerLine, 'UTF-8') . '…';
|
|
|
@@ -102,12 +180,28 @@ class PosterUtil
|
|
|
return [$line1, $line2];
|
|
|
}
|
|
|
|
|
|
+ /** @deprecated 兼容旧调用 */
|
|
|
+ public static function splitPosterNameLines($name, $maxPerLine = self::TEXT_CHARS_PER_LINE)
|
|
|
+ {
|
|
|
+ return self::splitPosterTextLines($name, $maxPerLine);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function buildPosterTwoLineText($text, $maxPerLine = self::TEXT_CHARS_PER_LINE)
|
|
|
+ {
|
|
|
+ list($line1, $line2) = self::splitPosterTextLines($text, $maxPerLine);
|
|
|
+ if ($line2 === '') {
|
|
|
+ return $line1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $line1 . "\n" . $line2;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * 商品名水印文案(支持 \\n 两行),支数/副标题尽量接在末行
|
|
|
+ * 商品名(图下,最多两行)
|
|
|
*/
|
|
|
public static function buildPosterNameText($name, $flowerNum = 0, $subtitle = '')
|
|
|
{
|
|
|
- list($line1, $line2) = self::splitPosterNameLines($name);
|
|
|
+ list($line1, $line2) = self::splitPosterTextLines($name);
|
|
|
$suffix = '';
|
|
|
if ($flowerNum > 0) {
|
|
|
$suffix = ' · ' . (int)$flowerNum . '支';
|
|
|
@@ -115,21 +209,18 @@ class PosterUtil
|
|
|
$suffix = ' · ' . trim((string)$subtitle);
|
|
|
}
|
|
|
if ($suffix !== '') {
|
|
|
- $target = $line2 !== '' ? 'line2' : 'line1';
|
|
|
- if ($target === 'line1') {
|
|
|
+ if ($line2 === '') {
|
|
|
$merged = $line1 . $suffix;
|
|
|
- if (mb_strlen($merged, 'UTF-8') <= self::NAME_CHARS_PER_LINE) {
|
|
|
+ if (mb_strlen($merged, 'UTF-8') <= self::TEXT_CHARS_PER_LINE) {
|
|
|
$line1 = $merged;
|
|
|
} else {
|
|
|
- $line2 = self::truncateText($suffix, self::NAME_CHARS_PER_LINE);
|
|
|
+ $line2 = self::truncateText($suffix, self::TEXT_CHARS_PER_LINE);
|
|
|
}
|
|
|
} else {
|
|
|
$merged = $line2 . $suffix;
|
|
|
- if (mb_strlen($merged, 'UTF-8') <= self::NAME_CHARS_PER_LINE) {
|
|
|
- $line2 = $merged;
|
|
|
- } else {
|
|
|
- $line2 = self::truncateText($merged, self::NAME_CHARS_PER_LINE);
|
|
|
- }
|
|
|
+ $line2 = mb_strlen($merged, 'UTF-8') <= self::TEXT_CHARS_PER_LINE
|
|
|
+ ? $merged
|
|
|
+ : self::truncateText($merged, self::TEXT_CHARS_PER_LINE);
|
|
|
}
|
|
|
}
|
|
|
if ($line2 === '') {
|
|
|
@@ -140,31 +231,75 @@ class PosterUtil
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 底部文案 y(g_sw,自下而上:推荐语 → 价格 → 商品名)
|
|
|
+ * 注意事项(图下、名称下,无内容返回空串)
|
|
|
*/
|
|
|
- private static function footerTextYPositions($nameLineCount)
|
|
|
+ public static function buildPosterRemarkText($itemRemark)
|
|
|
+ {
|
|
|
+ $itemRemark = trim((string)$itemRemark);
|
|
|
+ if ($itemRemark === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ return self::buildPosterTwoLineText(self::REMARK_PREFIX . $itemRemark);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function countTextLines($text)
|
|
|
+ {
|
|
|
+ $text = trim((string)$text);
|
|
|
+ if ($text === '') {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return substr_count($text, "\n") + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function textBlockHeight($lineCount, $fontSize)
|
|
|
+ {
|
|
|
+ if ($lineCount <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $lineH = (int)ceil($fontSize * 1.2);
|
|
|
+
|
|
|
+ return $lineCount * $lineH + max(0, $lineCount - 1) * self::MID_LINE_GAP;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function calcMidSectionHeight($nameText, $remarkText)
|
|
|
+ {
|
|
|
+ $h = 0;
|
|
|
+ $nameLines = self::countTextLines($nameText);
|
|
|
+ if ($nameLines > 0) {
|
|
|
+ $h += self::MID_AFTER_COVER_GAP + self::textBlockHeight($nameLines, self::MID_NAME_SIZE);
|
|
|
+ }
|
|
|
+ $remarkLines = self::countTextLines($remarkText);
|
|
|
+ if ($remarkLines > 0) {
|
|
|
+ $h += ($nameLines > 0 ? self::MID_BLOCK_GAP : self::MID_AFTER_COVER_GAP);
|
|
|
+ $h += self::textBlockHeight($remarkLines, self::MID_REMARK_SIZE);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $h;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function coverStartY()
|
|
|
+ {
|
|
|
+ return self::CANVAS_PAD_Y + self::SHOP_LOGO_SIZE + self::HEADER_BOTTOM_GAP;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function footerTextYPositions()
|
|
|
{
|
|
|
$staffY = self::FOOTER_STAFF_Y;
|
|
|
$staffH = (int)ceil(self::FOOTER_STAFF_SIZE * 1.3);
|
|
|
$priceY = $staffY + $staffH + self::FOOTER_TEXT_GAP;
|
|
|
- $priceH = (int)ceil(self::FOOTER_PRICE_SIZE * 1.1);
|
|
|
- $nameLineCount = max(1, min(2, (int)$nameLineCount));
|
|
|
- $nameLineH = (int)ceil(self::FOOTER_NAME_SIZE * 1.15);
|
|
|
- $nameY = $priceY + $priceH + self::FOOTER_TEXT_GAP + ($nameLineCount - 1) * $nameLineH;
|
|
|
|
|
|
return [
|
|
|
'staff' => $staffY,
|
|
|
'price' => $priceY,
|
|
|
- 'name' => $nameY,
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 按商品图比例计算主图区高度与画布总高(能多矮就多矮,仅保留温和下限)
|
|
|
- *
|
|
|
- * @return array{imgH:int,totalH:int,scaledH:int,posterW:int}
|
|
|
+ * @return array{imgH:int,totalH:int,scaledH:int,posterW:int,coverY:int,nameY:int,remarkY:int}
|
|
|
*/
|
|
|
- public static function calcPosterLayout($coverWidth, $coverHeight)
|
|
|
+ public static function calcPosterLayout($coverWidth, $coverHeight, array $textOptions = [])
|
|
|
{
|
|
|
$coverWidth = (int)$coverWidth;
|
|
|
$coverHeight = (int)$coverHeight;
|
|
|
@@ -178,21 +313,42 @@ class PosterUtil
|
|
|
$imgH = self::IMG_H_MIN;
|
|
|
}
|
|
|
|
|
|
- $totalH = $imgH + self::FOOTER_H;
|
|
|
- $absoluteMin = self::FOOTER_H + 120;
|
|
|
+ $nameText = self::buildPosterNameText(
|
|
|
+ $textOptions['name'] ?? '',
|
|
|
+ (int)($textOptions['flowerNum'] ?? 0),
|
|
|
+ $textOptions['subtitle'] ?? ''
|
|
|
+ );
|
|
|
+ $remarkText = self::buildPosterRemarkText($textOptions['itemRemark'] ?? '');
|
|
|
+
|
|
|
+ $coverY = self::coverStartY();
|
|
|
+ $midH = self::calcMidSectionHeight($nameText, $remarkText);
|
|
|
+ $totalH = $coverY + $imgH + $midH + self::FOOTER_H;
|
|
|
+ $absoluteMin = self::coverStartY() + self::IMG_H_MIN + self::FOOTER_H;
|
|
|
$totalH = max($absoluteMin, min(self::TOTAL_H_MAX, $totalH));
|
|
|
|
|
|
+ $nameY = $coverY + $imgH + ($nameText !== '' ? self::MID_AFTER_COVER_GAP : 0);
|
|
|
+ $remarkY = 0;
|
|
|
+ if ($remarkText !== '') {
|
|
|
+ $remarkY = $nameY;
|
|
|
+ if ($nameText !== '') {
|
|
|
+ $remarkY += self::textBlockHeight(self::countTextLines($nameText), self::MID_NAME_SIZE);
|
|
|
+ $remarkY += self::MID_BLOCK_GAP;
|
|
|
+ } else {
|
|
|
+ $remarkY = $coverY + $imgH + self::MID_AFTER_COVER_GAP;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return [
|
|
|
'imgH' => $imgH,
|
|
|
'totalH' => $totalH,
|
|
|
'scaledH' => $scaledH,
|
|
|
'posterW' => self::POSTER_W,
|
|
|
+ 'coverY' => $coverY,
|
|
|
+ 'nameY' => $nameY,
|
|
|
+ 'remarkY' => $remarkY,
|
|
|
];
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 商品图:常规按宽 SLOT_W 等比(无左右垫白);超高图用 m_fill 裁高至 IMG_H_MAX
|
|
|
- */
|
|
|
private static function buildCoverWatermarkPath($cover, $scaledH)
|
|
|
{
|
|
|
$scaledH = (int)$scaledH;
|
|
|
@@ -203,28 +359,32 @@ class PosterUtil
|
|
|
return $cover . '?x-oss-process=image/resize,m_lfit,w_' . self::SLOT_W . ',limit_0';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 太阳码水平中心 X(用于扫码文案 g_south 居中)
|
|
|
- */
|
|
|
+ private static function buildShopLogoWatermarkPath($logoObject)
|
|
|
+ {
|
|
|
+ $logoObject = imgUtil::normalizeOssObjectPath($logoObject);
|
|
|
+
|
|
|
+ return $logoObject . '?x-oss-process=image/resize,m_fill,h_' . self::SHOP_LOGO_SIZE
|
|
|
+ . ',w_' . self::SHOP_LOGO_SIZE . ',limit_0';
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function shopTitleY()
|
|
|
+ {
|
|
|
+ return self::CANVAS_PAD_Y + (int)((self::SHOP_LOGO_SIZE - self::SHOP_TITLE_SIZE) / 2);
|
|
|
+ }
|
|
|
+
|
|
|
private static function qrCenterX()
|
|
|
{
|
|
|
return self::POSTER_W - self::QR_PAD_RIGHT - (int)(self::QR_SIZE / 2);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 扫码提示文案:位于太阳码正上方居中(g_south = 文字下边中点对齐)
|
|
|
- */
|
|
|
private static function scanHintYFromBottom()
|
|
|
{
|
|
|
return self::QR_PAD_BOTTOM + self::QR_SIZE + self::QR_HINT_GAP;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 动态白底画布(blank.png → 750 × totalH)
|
|
|
- */
|
|
|
public static function buildCanvasBaseUrl($prefix, $totalH)
|
|
|
{
|
|
|
- $absoluteMin = self::FOOTER_H + 120;
|
|
|
+ $absoluteMin = self::coverStartY() + self::IMG_H_MIN + self::FOOTER_H;
|
|
|
$totalH = max($absoluteMin, min(self::TOTAL_H_MAX, (int)$totalH));
|
|
|
$process = '?x-oss-process=image/resize,m_pad,w_' . self::POSTER_W . ',h_' . $totalH
|
|
|
. ',color_' . self::CANVAS_COLOR;
|
|
|
@@ -232,9 +392,6 @@ class PosterUtil
|
|
|
return $prefix . self::BLANK_CANVAS . $process;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 和炫鲜花等门店角标
|
|
|
- */
|
|
|
public static function buildHyxhLogoWatermark($mainId)
|
|
|
{
|
|
|
if (!in_array((int)$mainId, [52, 1459, 13495], true)) {
|
|
|
@@ -247,36 +404,25 @@ class PosterUtil
|
|
|
if ($mainId == 13495) {
|
|
|
$logo = 'poster/hyxh_hzd_logo.png?x-oss-process=image/resize,m_fill,h_72,w_72';
|
|
|
}
|
|
|
+
|
|
|
return stringUtil::ossBase64($logo);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 花束海报
|
|
|
- */
|
|
|
public static function buildMallGoodsPosterUrl($prefix, array $options)
|
|
|
{
|
|
|
return self::buildMallSharePosterUrl($prefix, $options);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 花材海报(商城散客)
|
|
|
- */
|
|
|
public static function buildMallItemPosterUrl($prefix, array $options)
|
|
|
{
|
|
|
return self::buildMallSharePosterUrl($prefix, $options);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 花农端花店采购海报(ghs 花材详情太阳码)
|
|
|
- */
|
|
|
public static function buildGhsHdItemPosterUrl($prefix, array $options)
|
|
|
{
|
|
|
return self::buildMallSharePosterUrl($prefix, $options);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 散客/花束分享海报:动态画布 + 主图槽位 + 底部信息区
|
|
|
- */
|
|
|
private static function buildMallSharePosterUrl($prefix, array $options)
|
|
|
{
|
|
|
$cover = $options['cover'] ?? '';
|
|
|
@@ -287,6 +433,9 @@ class PosterUtil
|
|
|
$mainId = (int)($options['mainId'] ?? 0);
|
|
|
$flowerNum = (int)($options['flowerNum'] ?? 0);
|
|
|
$subtitle = trim((string)($options['subtitle'] ?? ''));
|
|
|
+ $itemRemark = trim((string)($options['itemRemark'] ?? ''));
|
|
|
+ $shopTitle = trim((string)($options['shopTitle'] ?? ''));
|
|
|
+ $shopLogo = trim((string)($options['shopLogo'] ?? ''));
|
|
|
$scanHint = trim((string)($options['scanHint'] ?? self::SCAN_HINT));
|
|
|
if ($scanHint === '微信扫码采购') {
|
|
|
$scanHint = self::SCAN_HINT;
|
|
|
@@ -298,21 +447,48 @@ class PosterUtil
|
|
|
list($coverW, $coverH) = imgUtil::getOssImageSize($cover, $prefix);
|
|
|
}
|
|
|
|
|
|
- $layout = self::calcPosterLayout($coverW, $coverH);
|
|
|
- $imgH = $layout['imgH'];
|
|
|
- $totalH = $layout['totalH'];
|
|
|
-
|
|
|
+ $layout = self::calcPosterLayout($coverW, $coverH, [
|
|
|
+ 'name' => $name,
|
|
|
+ 'flowerNum' => $flowerNum,
|
|
|
+ 'subtitle' => $subtitle,
|
|
|
+ 'itemRemark' => $itemRemark,
|
|
|
+ ]);
|
|
|
$displayName = self::buildPosterNameText($name, $flowerNum, $subtitle);
|
|
|
- $nameLineCount = ($displayName !== '' && strpos($displayName, "\n") !== false) ? 2 : 1;
|
|
|
- $footerY = self::footerTextYPositions($nameLineCount);
|
|
|
+ $displayRemark = self::buildPosterRemarkText($itemRemark);
|
|
|
+ $footerY = self::footerTextYPositions();
|
|
|
|
|
|
$coverWatermark = self::buildCoverWatermarkPath($cover, $layout['scaledH']);
|
|
|
$qrWatermark = $miniCode . '?x-oss-process=image/resize,m_fill,h_' . self::QR_SIZE . ',w_' . self::QR_SIZE;
|
|
|
|
|
|
- $url = self::buildCanvasBaseUrl($prefix, $totalH);
|
|
|
+ $url = self::buildCanvasBaseUrl($prefix, $layout['totalH']);
|
|
|
+
|
|
|
+ if ($shopLogo !== '') {
|
|
|
+ $url .= '/watermark,image_' . stringUtil::ossBase64(self::buildShopLogoWatermarkPath($shopLogo))
|
|
|
+ . ',g_nw,x_' . self::CANVAS_PAD_X . ',y_' . self::CANVAS_PAD_Y;
|
|
|
+ }
|
|
|
+ if ($shopTitle !== '') {
|
|
|
+ $titleX = self::CANVAS_PAD_X + self::SHOP_LOGO_SIZE + self::SHOP_LOGO_GAP;
|
|
|
+ $url .= '/watermark,text_' . stringUtil::ossBase64($shopTitle)
|
|
|
+ . ',g_nw,x_' . $titleX . ',y_' . self::shopTitleY()
|
|
|
+ . ',color_333333,type_' . self::FONT . ',size_' . self::SHOP_TITLE_SIZE;
|
|
|
+ }
|
|
|
+
|
|
|
$url .= '/watermark,image_' . stringUtil::ossBase64($coverWatermark)
|
|
|
- . ',g_nw,x_' . self::CANVAS_PAD_X . ',y_' . self::CANVAS_PAD_Y;
|
|
|
- $url .= '/watermark,image_' . stringUtil::ossBase64($qrWatermark) . ',g_se,x_' . self::QR_PAD_RIGHT . ',y_' . self::QR_PAD_BOTTOM;
|
|
|
+ . ',g_nw,x_' . self::CANVAS_PAD_X . ',y_' . $layout['coverY'];
|
|
|
+
|
|
|
+ if ($displayName !== '') {
|
|
|
+ $url .= '/watermark,text_' . stringUtil::ossBase64($displayName)
|
|
|
+ . ',g_nw,x_' . self::CANVAS_PAD_X . ',y_' . $layout['nameY']
|
|
|
+ . ',color_333333,type_' . self::FONT . ',size_' . self::MID_NAME_SIZE;
|
|
|
+ }
|
|
|
+ if ($displayRemark !== '') {
|
|
|
+ $url .= '/watermark,text_' . stringUtil::ossBase64($displayRemark)
|
|
|
+ . ',g_nw,x_' . self::CANVAS_PAD_X . ',y_' . $layout['remarkY']
|
|
|
+ . ',color_888888,type_' . self::FONT . ',size_' . self::MID_REMARK_SIZE;
|
|
|
+ }
|
|
|
+
|
|
|
+ $url .= '/watermark,image_' . stringUtil::ossBase64($qrWatermark)
|
|
|
+ . ',g_se,x_' . self::QR_PAD_RIGHT . ',y_' . self::QR_PAD_BOTTOM;
|
|
|
|
|
|
$logoB64 = self::buildHyxhLogoWatermark($mainId);
|
|
|
if ($logoB64 !== '') {
|
|
|
@@ -325,9 +501,6 @@ class PosterUtil
|
|
|
$url .= '/watermark,text_' . stringUtil::ossBase64($priceText)
|
|
|
. ',g_sw,x_' . self::CANVAS_PAD_X . ',y_' . $footerY['price']
|
|
|
. ',color_EE2C2C,type_' . self::FONT . ',size_' . self::FOOTER_PRICE_SIZE;
|
|
|
- $url .= '/watermark,text_' . stringUtil::ossBase64($displayName)
|
|
|
- . ',g_sw,x_' . self::CANVAS_PAD_X . ',y_' . $footerY['name']
|
|
|
- . ',color_333333,type_' . self::FONT . ',size_' . self::FOOTER_NAME_SIZE;
|
|
|
$url .= '/watermark,text_' . stringUtil::ossBase64($scanHint)
|
|
|
. ',g_south,x_' . self::qrCenterX() . ',y_' . self::scanHintYFromBottom()
|
|
|
. ',color_888888,type_' . self::FONT . ',size_24';
|