Răsfoiți Sursa

更新海报

shish 2 luni în urmă
părinte
comite
118a6d8771

+ 4 - 4
app-ghs/controllers/ItemController.php

@@ -956,14 +956,14 @@ class ItemController extends BaseController
         $staff = $this->shopAdmin;
         $staffName = $staff->name ?? '';
 
-        $url = PosterUtil::buildMallItemPosterUrl($prefix, [
+        $url = PosterUtil::buildMallItemPosterUrl($prefix, PosterUtil::withShopAndRemark([
             'cover' => $cover,
             'miniCode' => $miniCode,
             'priceText' => '¥' . floatval($price),
             'name' => $item->name ?? '',
             'staffText' => $staffName . ' 为您推荐',
             'mainId' => $this->mainId,
-        ]);
+        ], $this->shop, $this->sj ?? null, $item));
 
         util::success(['imgUrl' => $url]);
     }
@@ -1046,14 +1046,14 @@ class ItemController extends BaseController
         $staff = $this->shopAdmin;
         $staffName = $staff->name ?? '';
 
-        $url = PosterUtil::buildGhsHdItemPosterUrl($prefix, [
+        $url = PosterUtil::buildGhsHdItemPosterUrl($prefix, PosterUtil::withShopAndRemark([
             'cover' => $cover,
             'miniCode' => $miniCode,
             'priceText' => '¥' . floatval($price),
             'name' => $item->name ?? '',
             'staffText' => $staffName . ' 为您推荐',
             'mainId' => $this->mainId,
-        ]);
+        ], $this->shop, $this->sj ?? null, $item));
 
         util::success(['imgUrl' => $url]);
     }

+ 2 - 2
app-hd/controllers/GoodsController.php

@@ -216,7 +216,7 @@ class GoodsController extends BaseController
         $staffName = $staff->name ?? '';
         $staffText = $staffName . ' 为您推荐';
 
-        $url = PosterUtil::buildMallGoodsPosterUrl($prefix, [
+        $url = PosterUtil::buildMallGoodsPosterUrl($prefix, PosterUtil::withShopAndRemark([
             'cover' => $cover,
             'miniCode' => $miniCode,
             'priceText' => $priceText,
@@ -224,7 +224,7 @@ class GoodsController extends BaseController
             'flowerNum' => (int)($goods->flowerNum ?? 0),
             'staffText' => $staffText,
             'mainId' => $this->mainId,
-        ]);
+        ], $this->shop, $this->sj ?? null, ''));
 
         util::success(['imgUrl' => $url]);
     }

+ 2 - 2
app-hd/controllers/ItemController.php

@@ -308,14 +308,14 @@ class ItemController extends BaseController
         $staff = $this->shopAdmin;
         $staffName = $staff->name ?? '';
 
-        $url = PosterUtil::buildMallItemPosterUrl($prefix, [
+        $url = PosterUtil::buildMallItemPosterUrl($prefix, PosterUtil::withShopAndRemark([
             'cover' => $cover,
             'miniCode' => $miniCode,
             'priceText' => '¥' . floatval($price),
             'name' => $item->name ?? '',
             'staffText' => $staffName . ' 为您推荐',
             'mainId' => $this->mainId,
-        ]);
+        ], $this->shop, $this->sj ?? null, $item));
 
         util::success(['imgUrl' => $url]);
     }

+ 262 - 89
common/components/PosterUtil.php

@@ -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';