shish il y a 2 mois
Parent
commit
1e50d9e169
1 fichiers modifiés avec 115 ajouts et 66 suppressions
  1. 115 66
      common/components/PosterUtil.php

+ 115 - 66
common/components/PosterUtil.php

@@ -18,7 +18,10 @@ class PosterUtil
     const CANVAS_PAD_Y = 20;
 
     /** 文案每行最多字数(商品名、注意事项) */
-    const TEXT_CHARS_PER_LINE = 13;
+    const TEXT_CHARS_PER_LINE = 16;
+
+    /** OSS 单段文字水印长度上限(约 64,汉字按 3 计,16 字/行安全) */
+    const OSS_WATERMARK_TEXT_MAX = 21;
 
     const SLOT_W = 710;
 
@@ -190,28 +193,50 @@ class PosterUtil
     }
 
     /**
-     * @return array{0:string,1:string}
+     * 按行宽拆成多行(供多条 OSS 文字水印使用,不使用 \n)
+     *
+     * @return string[]
      */
-    public static function splitPosterTextLines($text, $maxPerLine = self::TEXT_CHARS_PER_LINE)
+    public static function splitPosterTextToLines($text, $maxPerLine = self::TEXT_CHARS_PER_LINE)
     {
         $text = self::filterPosterUtf8Text($text);
         if ($text === '') {
-            return ['', ''];
+            return [];
         }
         $maxPerLine = (int)$maxPerLine;
         if ($maxPerLine < 1) {
             $maxPerLine = self::TEXT_CHARS_PER_LINE;
         }
+        $maxPerLine = min($maxPerLine, self::OSS_WATERMARK_TEXT_MAX);
+
+        $lines = [];
         $len = mb_strlen($text, 'UTF-8');
-        if ($len <= $maxPerLine) {
-            return [$text, ''];
+        $offset = 0;
+        while ($offset < $len) {
+            $remain = $len - $offset;
+            if ($remain <= $maxPerLine) {
+                $lines[] = mb_substr($text, $offset, null, 'UTF-8');
+                break;
+            }
+            $lines[] = mb_substr($text, $offset, $maxPerLine, 'UTF-8');
+            $offset += $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];
+
+        return $lines;
+    }
+
+    /**
+     * @return array{0:string,1:string}
+     * @deprecated 仅兼容旧调用,新逻辑请用 splitPosterTextToLines
+     */
+    public static function splitPosterTextLines($text, $maxPerLine = self::TEXT_CHARS_PER_LINE)
+    {
+        $lines = self::splitPosterTextToLines($text, $maxPerLine);
+        $line1 = $lines[0] ?? '';
+        $line2 = $lines[1] ?? '';
+        if (count($lines) > 2) {
+            $line2 = ($lines[1] ?? '') . '…';
         }
-        $line2 = mb_substr($rest, 0, $maxPerLine, 'UTF-8') . '…';
 
         return [$line1, $line2];
     }
@@ -222,24 +247,24 @@ class PosterUtil
         return self::splitPosterTextLines($name, $maxPerLine);
     }
 
+    /** @deprecated 兼容旧调用,返回首行文案 */
     public static function buildPosterTwoLineText($text, $maxPerLine = self::TEXT_CHARS_PER_LINE)
     {
-        list($line1, $line2) = self::splitPosterTextLines($text, $maxPerLine);
-        if ($line2 === '') {
-            return $line1;
-        }
+        $lines = self::splitPosterTextToLines($text, $maxPerLine);
 
-        return $line1 . "\n" . $line2;
+        return $lines[0] ?? '';
     }
 
     /**
-     * 商品名(图下,最多两行)
+     * 商品名(图下,多行,每行独立 OSS 文字水印)
+     *
+     * @return string[]
      */
-    public static function buildPosterNameText($name, $flowerNum = 0, $subtitle = '')
+    public static function buildPosterNameLines($name, $flowerNum = 0, $subtitle = '')
     {
         $name = self::filterPosterUtf8Text($name);
         $subtitle = self::filterPosterUtf8Text($subtitle);
-        list($line1, $line2) = self::splitPosterTextLines($name);
+        $lines = self::splitPosterTextToLines($name);
         $suffix = '';
         if ($flowerNum > 0) {
             $suffix = ' · ' . (int)$flowerNum . '支';
@@ -247,48 +272,76 @@ class PosterUtil
             $suffix = ' · ' . trim((string)$subtitle);
         }
         if ($suffix !== '') {
-            if ($line2 === '') {
-                $merged = $line1 . $suffix;
+            if ($lines === []) {
+                $lines = self::splitPosterTextToLines($suffix);
+            } else {
+                $last = count($lines) - 1;
+                $merged = $lines[$last] . $suffix;
                 if (mb_strlen($merged, 'UTF-8') <= self::TEXT_CHARS_PER_LINE) {
-                    $line1 = $merged;
+                    $lines[$last] = $merged;
                 } else {
-                    $line2 = self::truncateText($suffix, self::TEXT_CHARS_PER_LINE);
+                    $lines = array_merge($lines, self::splitPosterTextToLines($suffix));
                 }
-            } else {
-                $merged = $line2 . $suffix;
-                $line2 = mb_strlen($merged, 'UTF-8') <= self::TEXT_CHARS_PER_LINE
-                    ? $merged
-                    : self::truncateText($merged, self::TEXT_CHARS_PER_LINE);
             }
         }
-        if ($line2 === '') {
-            return $line1;
-        }
 
-        return $line1 . "\n" . $line2;
+        return $lines;
+    }
+
+    /** @deprecated 请用 buildPosterNameLines */
+    public static function buildPosterNameText($name, $flowerNum = 0, $subtitle = '')
+    {
+        $lines = self::buildPosterNameLines($name, $flowerNum, $subtitle);
+
+        return $lines[0] ?? '';
     }
 
     /**
-     * 注意事项(图下、名称下,无内容返回空串)
+     * 注意事项(图下、名称下,多行)
+     *
+     * @return string[]
      */
-    public static function buildPosterRemarkText($itemRemark)
+    public static function buildPosterRemarkLines($itemRemark)
     {
         $itemRemark = self::filterPosterUtf8Text($itemRemark);
         if ($itemRemark === '') {
-            return '';
+            return [];
         }
 
-        return self::buildPosterTwoLineText(self::REMARK_PREFIX . $itemRemark);
+        return self::splitPosterTextToLines(self::REMARK_PREFIX . $itemRemark);
     }
 
-    private static function countTextLines($text)
+    /** @deprecated 请用 buildPosterRemarkLines */
+    public static function buildPosterRemarkText($itemRemark)
     {
-        $text = trim((string)$text);
-        if ($text === '') {
-            return 0;
+        $lines = self::buildPosterRemarkLines($itemRemark);
+
+        return $lines[0] ?? '';
+    }
+
+    private static function textLineStep($fontSize)
+    {
+        return (int)ceil($fontSize * 1.2) + self::MID_LINE_GAP;
+    }
+
+    /**
+     * 多条文字水印纵向排列(OSS 不支持 \n 换行)
+     */
+    private static function appendTextLineWatermarks($url, array $lines, $startY, $fontSize, $colorHex)
+    {
+        $step = self::textLineStep($fontSize);
+        foreach ($lines as $i => $line) {
+            $line = trim((string)$line);
+            if ($line === '') {
+                continue;
+            }
+            $y = (int)$startY + $i * $step;
+            $url .= '/watermark,text_' . stringUtil::ossBase64($line)
+                . ',g_nw,x_' . self::CANVAS_PAD_X . ',y_' . $y
+                . ',color_' . $colorHex . ',type_' . self::FONT . ',size_' . (int)$fontSize;
         }
 
-        return substr_count($text, "\n") + 1;
+        return $url;
     }
 
     private static function textBlockHeight($lineCount, $fontSize)
@@ -301,17 +354,17 @@ class PosterUtil
         return $lineCount * $lineH + max(0, $lineCount - 1) * self::MID_LINE_GAP;
     }
 
-    private static function calcMidSectionHeight($nameText, $remarkText)
+    private static function calcMidSectionHeight(array $nameLines, array $remarkLines)
     {
         $h = 0;
-        $nameLines = self::countTextLines($nameText);
-        if ($nameLines > 0) {
-            $h += self::MID_AFTER_COVER_GAP + self::textBlockHeight($nameLines, self::MID_NAME_SIZE);
+        $nameCount = count($nameLines);
+        if ($nameCount > 0) {
+            $h += self::MID_AFTER_COVER_GAP + self::textBlockHeight($nameCount, 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);
+        $remarkCount = count($remarkLines);
+        if ($remarkCount > 0) {
+            $h += ($nameCount > 0 ? self::MID_BLOCK_GAP : self::MID_AFTER_COVER_GAP);
+            $h += self::textBlockHeight($remarkCount, self::MID_REMARK_SIZE);
         }
 
         return $h;
@@ -362,25 +415,25 @@ class PosterUtil
             $imgH = self::IMG_H_MIN;
         }
 
-        $nameText = self::buildPosterNameText(
+        $nameLines = self::buildPosterNameLines(
             $textOptions['name'] ?? '',
             (int)($textOptions['flowerNum'] ?? 0),
             $textOptions['subtitle'] ?? ''
         );
-        $remarkText = self::buildPosterRemarkText($textOptions['itemRemark'] ?? '');
+        $remarkLines = self::buildPosterRemarkLines($textOptions['itemRemark'] ?? '');
 
         $coverY = self::coverStartY();
-        $midH = self::calcMidSectionHeight($nameText, $remarkText);
+        $midH = self::calcMidSectionHeight($nameLines, $remarkLines);
         $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);
+        $nameY = $coverY + $imgH + (count($nameLines) > 0 ? self::MID_AFTER_COVER_GAP : 0);
         $remarkY = 0;
-        if ($remarkText !== '') {
+        if (count($remarkLines) > 0) {
             $remarkY = $nameY;
-            if ($nameText !== '') {
-                $remarkY += self::textBlockHeight(self::countTextLines($nameText), self::MID_NAME_SIZE);
+            if (count($nameLines) > 0) {
+                $remarkY += self::textBlockHeight(count($nameLines), self::MID_NAME_SIZE);
                 $remarkY += self::MID_BLOCK_GAP;
             } else {
                 $remarkY = $coverY + $imgH + self::MID_AFTER_COVER_GAP;
@@ -490,8 +543,8 @@ class PosterUtil
             'subtitle' => $subtitle,
             'itemRemark' => $itemRemark,
         ]);
-        $displayName = self::buildPosterNameText($name, $flowerNum, $subtitle);
-        $displayRemark = self::buildPosterRemarkText($itemRemark);
+        $nameLines = self::buildPosterNameLines($name, $flowerNum, $subtitle);
+        $remarkLines = self::buildPosterRemarkLines($itemRemark);
         $footer = self::calcFooterLayout();
 
         $coverWatermark = self::buildCoverWatermarkPath($cover, $layout['scaledH']);
@@ -513,15 +566,11 @@ class PosterUtil
         $url .= '/watermark,image_' . stringUtil::ossBase64($coverWatermark)
             . ',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 (count($nameLines) > 0) {
+            $url = self::appendTextLineWatermarks($url, $nameLines, $layout['nameY'], self::MID_NAME_SIZE, '333333');
         }
-        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;
+        if (count($remarkLines) > 0) {
+            $url = self::appendTextLineWatermarks($url, $remarkLines, $layout['remarkY'], self::MID_REMARK_SIZE, '888888');
         }
 
         $url .= '/watermark,image_' . stringUtil::ossBase64($qrWatermark)