| | |
| | | /* @var array $config 名片设置 */ |
| | | private $config; |
| | | public $template; |
| | | private $businessName; |
| | | |
| | | /** |
| | | * 构造方法 |
| | |
| | | * @param $dealer |
| | | * @throws \Exception |
| | | */ |
| | | public function __construct($dealer) |
| | | public function __construct($dealer,$businessName='business') |
| | | { |
| | | parent::__construct(); |
| | | // 用户信息 |
| | | $this->dealer = $dealer; |
| | | $this->businessName = $businessName; |
| | | $this->template = (new Template())->detail($dealer['template_id']); |
| | | // 名片设置 |
| | | $this->config = json_decode($this->template['style'], true); |
| | |
| | | */ |
| | | public function getImage($isType = true) |
| | | { |
| | | if (file_exists($this->getPosterPath('business')) && $isType) { |
| | | return $this->getPosterUrl('business'); |
| | | if (file_exists($this->getPosterPath($this->businessName)) && $isType) { |
| | | return $this->getPosterUrl($this->businessName); |
| | | } |
| | | // 小程序id |
| | | $appId = $this->dealer['app_id']; |
| | |
| | | $avatarUrl = $this->saveTempImage($appId, $this->dealer['user']['avatarUrl'], 'avatar'); |
| | | } |
| | | $logo = ''; |
| | | if ($this->dealer['logoImage']['file_path']) { |
| | | if (!empty($this->dealer['logoImage']['file_path'])) { |
| | | // 2. 下载logo |
| | | $logo = $this->saveTempImage($appId, $this->dealer['logoImage']['file_path'], 'logo'); |
| | | } |
| | | // 4. 拼接名片 |
| | | return $this->savePoster($backdrop, $avatarUrl, 'business', $logo); |
| | | return $this->savePoster($backdrop, $avatarUrl, $this->businessName, $logo); |
| | | } |
| | | |
| | | /** |
| | | * 名片图文件路径 |
| | | * @return string |
| | | */ |
| | | private function getPosterPath($name) |
| | | public function getPosterPath($name) |
| | | { |
| | | // 保存路径 |
| | | $tempPath = $_SERVER['DOCUMENT_ROOT']. '/temp/'.$this->template['app_id'] . '/'.$name.'/'; |
| | |
| | | private function savePoster($backdrop, $avatarUrl, $imageUrl, $logo) |
| | | { |
| | | |
| | | // 创建画布 |
| | | list($width, $height) = getimagesize($backdrop); |
| | | // 获取背景图信息,判断图片类型 |
| | | $backdropInfo = getimagesize($backdrop); |
| | | $width = $backdropInfo[0]; |
| | | $height = $backdropInfo[1]; |
| | | |
| | | // 根据原图类型创建对应类型的画布,提高质量 |
| | | $imageType = $backdropInfo[2]; |
| | | |
| | | // 创建真彩色画布,确保足够的色彩深度 |
| | | $newImage = imagecreatetruecolor($width, $height); |
| | | $backdropIm = $this->imagEcr($backdrop); |
| | | |
| | | // 启用抗锯齿 |
| | | imageantialias($newImage, true); |
| | | // 将第一张图片覆盖在新图像上 |
| | | imagecopy($newImage, $backdropIm, 0, 0, 0, 0, $width, $height); |
| | | |
| | | // 启用alpha混合模式,确保透明度正常 |
| | | imagealphablending($newImage, true); |
| | | |
| | | // 保存透明度 |
| | | imagesavealpha($newImage, true); |
| | | |
| | | // 加载背景图 |
| | | $backdropIm = $this->imagEcr($backdrop); |
| | | |
| | | // 将背景图复制到画布(使用更好的插值方法) |
| | | imagecopyresampled($newImage, $backdropIm, 0, 0, 0, 0, $width, $height, $width, $height); |
| | | if (!empty($this->config['icon'])) { |
| | | foreach ($this->config['icon'] as $key => $value) { |
| | | $this->addImagecopy($newImage, $value); |
| | | } |
| | | } |
| | | |
| | | // 脱敏处理 |
| | | if($this->businessName=='desensitization'){ |
| | | // 手机号脱敏:保留前3位和后4位 |
| | | if(!empty($this->dealer['mobile'])){ |
| | | $this->dealer['mobile'] = $this->maskPhoneNumber($this->dealer['mobile']); |
| | | } |
| | | if(!empty($this->dealer['mobile_phone'])){ |
| | | $this->dealer['mobile_phone'] = $this->maskPhoneNumber($this->dealer['mobile_phone']); |
| | | } |
| | | |
| | | // 微信脱敏:保留前2位和后2位 |
| | | if(!empty($this->dealer['wechat'])){ |
| | | $this->dealer['wechat'] = $this->maskWechat($this->dealer['wechat']); |
| | | } |
| | | |
| | | // 邮箱脱敏:保留域名和前2位用户名 |
| | | if(!empty($this->dealer['mailbox'])){ |
| | | $this->dealer['mailbox'] = $this->maskEmail($this->dealer['mailbox']); |
| | | } |
| | | |
| | | // 电话脱敏:保留前3位和后4位 |
| | | if(!empty($this->dealer['phone'])){ |
| | | $this->dealer['phone'] = $this->maskPhoneNumber($this->dealer['phone']); |
| | | } |
| | | } |
| | | // 写入地址 |
| | | //$this->addText($newImage, 'address', '地址:', 0, false, $width); |
| | | $this->addText($newImage, 'address', '地址:', 0, false, $width); |
| | | if ($this->config['avatar']['display'] == 1) { |
| | | // 生成圆形用户头像 |
| | | $this->config['avatar']['style'] === 'circle' && $this->circular($avatarUrl, $avatarUrl); |
| | |
| | | $avatarY = $this->config['avatar']['top']; |
| | | // 打开用户头像 |
| | | $avatarIm = $this->imagEcr($avatarUrl); |
| | | // 使用更好的插值方法复制头像(使用imagecopy保持原有质量) |
| | | imagecopy($newImage, $avatarIm, $avatarX, $avatarY, 0, 0, $avatarWidth, $avatarWidth); |
| | | } |
| | | if (is_file($logo) && $this->config['logo']['display'] == 1) { |
| | |
| | | $logoY = $this->config['logo']['top']; |
| | | // 打开用户logo |
| | | $logoImage = $this->imagEcr($logo); |
| | | // 使用更好的插值方法复制logo(使用imagecopy保持原有质量) |
| | | imagecopy($newImage, $logoImage, $logoX, $logoY, 0, 0, $logoWidth, $logoHeight); |
| | | } |
| | | |
| | | // 写入用户昵称 |
| | | $this->addText($newImage, 'name'); |
| | | $this->addText($newImage, 'name', '', 0, false, $width); |
| | | // 写入公司列表 |
| | | $unitBaseTop = 0; |
| | | $unitFontSize = $this->config['unit'][0]['fontSize']; |
| | | foreach ($this->dealer['unit'] as $key => $value) { |
| | | // 写入公司 |
| | | $this->addText($newImage, 'unit', '', $key, true); |
| | | if($key > 0){ |
| | | $this->config['unit'][$key] = $this->config['unit'][0]; |
| | | // 计算偏移量:每个公司增加基于前一个公司实际行数的距离 |
| | | $this->config['unit'][$key]['top'] = $this->config['unit'][0]['top'] + $unitBaseTop; |
| | | } |
| | | // 写入公司,获取实际占用的行数 |
| | | $lineCount = $this->addText($newImage, 'unit', '', $key, true, $width); |
| | | // 计算下一个公司的偏移量:(行数 - 1) * 行高 + 行间距 |
| | | $unitLineHeight = $this->getLineHeight($unitFontSize); |
| | | $unitLineSpacing = $this->getLineSpacing($unitFontSize); |
| | | if ($key > 0) { |
| | | $unitBaseTop += ($lineCount * $unitLineHeight) + $unitLineSpacing; |
| | | } else { |
| | | $unitBaseTop = ($lineCount * $unitLineHeight) + $unitLineSpacing; |
| | | } |
| | | } |
| | | foreach ($this->dealer['address'] as $key => $value) { |
| | | // 写入地址 |
| | | $this->addText($newImage, 'address', '', $key, true); |
| | | // 写入职位列表 |
| | | if (!empty($this->dealer['duties']) && !empty($this->config['duties'])) { |
| | | $dutiesBaseTop = 0; |
| | | $dutiesFontSize = $this->config['duties'][0]['fontSize']; |
| | | foreach ($this->dealer['duties'] as $key => $value) { |
| | | if($key > 0){ |
| | | $this->config['duties'][$key] = $this->config['duties'][0]; |
| | | // 计算偏移量:每个职位增加基于前一个职位实际行数的距离 |
| | | $this->config['duties'][$key]['top'] = $this->config['duties'][0]['top'] + $dutiesBaseTop; |
| | | } |
| | | // 写入职位,获取实际占用的行数 |
| | | $lineCount = $this->addText($newImage, 'duties','', $key, true, $width); |
| | | // 计算下一个职位的偏移量:(行数 - 1) * 行高 + 行间距 |
| | | $dutiesLineHeight = $this->getLineHeight($dutiesFontSize); |
| | | $dutiesLineSpacing = $this->getLineSpacing($dutiesFontSize); |
| | | if ($key > 0) { |
| | | $dutiesBaseTop += ($lineCount * $dutiesLineHeight) + $dutiesLineSpacing; |
| | | } else { |
| | | $dutiesBaseTop = ($lineCount * $dutiesLineHeight) + $dutiesLineSpacing; |
| | | } |
| | | } |
| | | } |
| | | foreach ($this->dealer['duties'] as $key => $value) { |
| | | // 写入职务 |
| | | $this->addText($newImage, 'duties', '', $key, true); |
| | | } |
| | | // 写入职位 |
| | | //$this->addText($newImage, 'duties'); |
| | | |
| | | // 写入position列表(备用字段) |
| | | if (!empty($this->dealer['position']) && !empty($this->config['position'])) { |
| | | $positionBaseTop = 0; |
| | | $positionFontSize = $this->config['position'][0]['fontSize']; |
| | | foreach ($this->dealer['position'] as $key => $value) { |
| | | // 写入公司 |
| | | $this->addText($newImage, 'position', '', $key, true); |
| | | if($key > 0){ |
| | | $this->config['position'][$key] = $this->config['position'][0]; |
| | | // 计算偏移量:每个position增加基于前一个实际行数的距离 |
| | | $this->config['position'][$key]['top'] = $this->config['position'][0]['top'] + $positionBaseTop; |
| | | } |
| | | // 写入position,获取实际占用的行数 |
| | | $lineCount = $this->addText($newImage, 'position', '', $key, true, $width); |
| | | // 计算下一个position的偏移量:(行数 - 1) * 行高 + 行间距 |
| | | $positionLineHeight = $this->getLineHeight($positionFontSize); |
| | | $positionLineSpacing = $this->getLineSpacing($positionFontSize); |
| | | if ($key > 0) { |
| | | $positionBaseTop += ($lineCount * $positionLineHeight) + $positionLineSpacing; |
| | | } else { |
| | | $positionBaseTop = ($lineCount * $positionLineHeight) + $positionLineSpacing; |
| | | } |
| | | } |
| | | } |
| | | // 写入手机号 |
| | | $this->addText($newImage, 'mobile', '手机:'); |
| | | |
| | | // 写入微信 |
| | | $this->addText($newImage, 'mobile', '手机:', 0, false, $width); |
| | | if (!empty($this->dealer['wechat']) && !empty($this->config['wechat'])) { |
| | | // 写入微信 |
| | | $this->addText($newImage, 'wechat', '微信:', 0, false, $width); |
| | | } |
| | | // 写入邮箱 |
| | | if ($this->dealer['mailbox']) { |
| | | // 写入邮箱 |
| | | $this->addText($newImage, 'mailbox', '邮箱:'); |
| | | $this->addText($newImage, 'mailbox', '邮箱:', 0, false, $width); |
| | | } |
| | | if ($this->dealer['phone']) { |
| | | $this->addText($newImage, 'phone', '电话:'); |
| | | $this->addText($newImage, 'phone', '电话:', 0, false, $width); |
| | | } |
| | | // 保存图片 |
| | | imagejpeg($newImage, $this->getPosterPath($imageUrl),100); // 根据需要选择合适的函数(如imagepng、imagegif等) |
| | | // 根据背景图类型选择保存格式 |
| | | $imageType = $backdropInfo[2]; |
| | | $savePath = $this->getPosterPath($imageUrl); |
| | | |
| | | if ($imageType == IMAGETYPE_PNG) { |
| | | // PNG格式,使用最高质量(9) |
| | | imagepng($newImage, $savePath, 9); |
| | | } elseif ($imageType == IMAGETYPE_GIF) { |
| | | // GIF格式 |
| | | imagegif($newImage, $savePath); |
| | | } else { |
| | | // JPEG格式,使用最高质量(100) |
| | | imagejpeg($newImage, $savePath, 100); |
| | | } |
| | | |
| | | // 清理内存 |
| | | imagedestroy($newImage); |
| | | imagedestroy($backdropIm); |
| | | return $this->getPosterUrl($imageUrl); |
| | | } |
| | | |
| | |
| | | imagesavealpha($targetImage, true); |
| | | $bg = imagecolorallocatealpha($targetImage, 255, 255, 255, 127); |
| | | imagefill($targetImage, 0, 0, $bg); |
| | | imagecopyresampled($targetImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); |
| | | imagepng($targetImage, $imageUrl,0); // 根据需要选择合适的函数(如imagepng、imagegif等) |
| | | imagecopyresampled($targetImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); // 保存时使用最高质量 |
| | | $ext = pathinfo($imageUrl, PATHINFO_EXTENSION); |
| | | if ($ext == 'png'){ |
| | | imagepng($targetImage, $imageUrl, 9); // PNG使用最高质量(9) |
| | | }else{ |
| | | imagejpeg($targetImage, $imageUrl, 100); // JPEG使用最高质量 |
| | | } |
| | | |
| | | // 清理内存 |
| | | imagedestroy($targetImage); |
| | | imagedestroy($image); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param $key |
| | | * @param $type |
| | | * @param $width |
| | | * @return array|bool |
| | | * @return array|bool|int 返回实际占用的行数(仅对type=true时有效) |
| | | */ |
| | | public function addText($editor, $name, $text = '', $key = 0, $type = false, $width = 0) |
| | | { |
| | | $fontPath = $_SERVER['DOCUMENT_ROOT']. '/fonts/MSYH.TTC'; |
| | | if ($type) { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$key]['fontSize'], $this->config[$name][$key]['left'],$this->config[$name][$key]['top']); |
| | | $colorResource=self::colorResource($this->config[$name][$key]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $this->dealer[$name][$key]); |
| | | // 确保配置数组存在该索引,不存在则使用索引0 |
| | | $configKey = isset($this->config[$name][$key]) ? $key : 0; |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$configKey]['fontSize'], $this->config[$name][$configKey]['left'],$this->config[$name][$configKey]['top']); |
| | | $colorResource=self::colorResource($this->config[$name][$configKey]['color'],$editor); |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name][$configKey]['left'], $this->config[$name][$configKey]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | return $this->writeTextWithWrap($editor, $text . $this->dealer[$name][$key], $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } else if ($name == 'mobile') { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name]['fontSize'], $this->config[$name]['left'],$this->config[$name]['top']); |
| | | $text = $text . $this->dealer['mobile']; |
| | | |
| | | if (!empty($this->dealer['mobile_phone'])) { |
| | | $text = $text . ' / ' . $this->dealer['mobile_phone']; |
| | | } |
| | | $colorResource=self::colorResource($this->config[$name]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text); |
| | | } /*else if ($name == 'duties') { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$key]['fontSize'], $this->config[$name][$key]['left'],$this->config[$name][$key]['top']); |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name]['left'], $this->config[$name]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | $this->writeTextWithWrap($editor, $text, $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } else if ($name == 'duties') { |
| | | // 确保配置数组存在该索引,不存在则使用索引0 |
| | | $configKey = isset($this->config[$name][$key]) ? $key : 0; |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$configKey]['fontSize'], $this->config[$name][$configKey]['left'],$this->config[$name][$configKey]['top']); |
| | | $duties = $this->dealer['duties'][$key]; |
| | | if (!empty($this->dealer['duties'][1])) { |
| | | $duties = $duties . ' / ' . $this->dealer['duties'][1]; |
| | | } |
| | | if (!empty($this->dealer['duties'][2])) { |
| | | $duties = $duties . ' / ' . $this->dealer['duties'][2]; |
| | | } |
| | | $colorResource=self::colorResource($this->config['duties'][$key]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $duties); |
| | | }*/ else if ($name == 'address') { |
| | | $colorResource=self::colorResource($this->config['duties'][0]['color'],$editor); |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name][$configKey]['left'], $this->config[$name][$configKey]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | return $this->writeTextWithWrap($editor, $text . $duties, $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } else if ($name == 'address') { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name][$key]['fontSize'], $this->config[$name][$key]['left'],$this->config[$name][$key]['top']); |
| | | $title = $this->dealer['address'][$key]; |
| | | $title = $this->dealer['address'][0]; |
| | | if(!empty($this->dealer['region'])){ |
| | | $title = $this->dealer['region']['province'] . $this->dealer['region']['city']. $this->dealer['region']['region'] . $title; |
| | | } |
| | | $strlen = mb_strlen($title, 'utf-8'); |
| | | $left = $width - $this->config['address'][$key]['left'] - $fontSize; |
| | | $titleNum = bcdiv($left, $this->config['address'][$key]['fontSize']); |
| | | $the_box = $this->config['address'][$key]['fontSize'] * 3; |
| | | while ($width < ($titleNum * $this->config['address'][$key]['fontSize'] + $the_box + $fontX)) { |
| | | $left = $width - $this->config['address'][0]['left'] - $fontSize; |
| | | $titleNum = bcdiv($left, $this->config['address'][0]['fontSize']); |
| | | $the_box = $this->config['address'][0]['fontSize'] * 3; |
| | | while ($width < ($titleNum * $this->config['address'][0]['fontSize'] + $the_box + $fontX)) { |
| | | $titleNum--; |
| | | }; |
| | | if ($strlen > $titleNum && $titleNum) { |
| | | if ($strlen > $titleNum && $titleNum>0) { |
| | | $strArr = self::mbStrSplit($title, $titleNum); |
| | | } |
| | | $colorResource=self::colorResource($this->config['address'][$key]['color'],$editor); |
| | | if ($strlen > $titleNum && $titleNum) { |
| | | $colorResource=self::colorResource($this->config['address'][0]['color'],$editor); |
| | | if ($strlen > $titleNum && $titleNum>0) { |
| | | $y = $fontY + 10; |
| | | foreach ($strArr as $k => $v) { |
| | | if ($k == 0) { |
| | |
| | | } else { |
| | | list($fontSize, $fontX,$fontY) = self::SizeLeftTop($this->config[$name]['fontSize'], $this->config[$name]['left'],$this->config[$name]['top']); |
| | | $colorResource=self::colorResource($this->config[$name]['color'],$editor); |
| | | return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $this->dealer[$name]); // path/to/font.ttf为自定义字体路径 |
| | | // 智能计算最大宽度 |
| | | $maxWidth = $this->calculateFieldMaxWidth($this->config[$name]['left'], $this->config[$name]['top'], $width); |
| | | // 写入支持换行的文本 |
| | | $this->writeTextWithWrap($editor, $text . $this->dealer[$name], $fontX, $fontY, $fontSize, $colorResource, $fontPath, $maxWidth); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 手机号/座机号脱敏处理 |
| | | * @param $phone |
| | | * @return string |
| | | */ |
| | | private function maskPhoneNumber($phone){ |
| | | $len = strlen($phone); |
| | | if($len <= 7) return $phone; // 太短的号码不脱敏 |
| | | |
| | | // 检查是否是带区号的座机号(包含-或空格) |
| | | if(strpos($phone, '-') !== false || strpos($phone, ' ') !== false){ |
| | | // 座机号脱敏:保留区号和最后4位 |
| | | $parts = preg_split('/[-\s]/', $phone); |
| | | |
| | | // 确保有至少两部分(区号和号码) |
| | | if(count($parts) >= 2){ |
| | | $areaCode = $parts[0]; // 区号 |
| | | $number = end($parts); // 号码部分 |
| | | $separator = strpos($phone, '-') !== false ? '-' : ' '; // 保持原始分隔符 |
| | | |
| | | $numLen = strlen($number); |
| | | if($numLen <= 4) return $phone; // 号码太短不脱敏 |
| | | |
| | | $numSuffix = substr($number, -4); // 保留号码后4位 |
| | | $stars = str_repeat('*', $numLen - 4); |
| | | |
| | | // 重新组合:区号 + 分隔符 + 掩码 + 后4位 |
| | | return $areaCode . $separator . $stars . $numSuffix; |
| | | } |
| | | } |
| | | |
| | | // 普通手机号脱敏:保留前3位和后4位 |
| | | $prefix = substr($phone, 0, 3); |
| | | $suffix = substr($phone, -4); |
| | | $stars = str_repeat('*', $len - 7); |
| | | |
| | | return $prefix . $stars . $suffix; |
| | | } |
| | | |
| | | /** |
| | | * 微信脱敏处理 |
| | | * @param $wechat |
| | | * @return string |
| | | */ |
| | | private function maskWechat($wechat){ |
| | | $len = strlen($wechat); |
| | | if($len <= 4) return $wechat; // 太短的微信号不脱敏 |
| | | |
| | | $prefix = substr($wechat, 0, 2); |
| | | $suffix = substr($wechat, -2); |
| | | $stars = str_repeat('*', $len - 4); |
| | | |
| | | return $prefix . $stars . $suffix; |
| | | } |
| | | |
| | | /** |
| | | * 邮箱脱敏处理 |
| | | * @param $email |
| | | * @return string |
| | | */ |
| | | private function maskEmail($email){ |
| | | $parts = explode('@', $email); |
| | | if(count($parts) != 2) return $email; |
| | | |
| | | $username = $parts[0]; |
| | | $domain = $parts[1]; |
| | | |
| | | $len = strlen($username); |
| | | if($len <= 2) return $email; // 太短的用户名不脱敏 |
| | | |
| | | $prefix = substr($username, 0, 2); |
| | | $stars = str_repeat('*', $len - 2); |
| | | |
| | | return $prefix . $stars . '@' . $domain; |
| | | } |
| | | |
| | | /** |
| | | * 获取颜色 |
| | | * @param $color |
| | | * @param $editor |
| | |
| | | * @param $fontSize |
| | | * @param $left |
| | | * @param $top |
| | | * @return array |
| | | * @return array [fontSizePt, left, top] |
| | | */ |
| | | private function SizeLeftTop($fontSize,$left,$top){ |
| | | $data[0] = $fontSize * 0.76; |
| | | $data[1] = $left; |
| | | $data[2] = $top + $fontSize; |
| | | // px到pt转换系数:1px ≈ 0.75pt (GD库使用磅pt作为字体单位) |
| | | // 使用更精确的转换系数 |
| | | $fontSizePt = $fontSize * 0.75; |
| | | |
| | | // 调整top位置,使文字基线对齐 |
| | | // imagettftext的y坐标是文字基线位置,不是文字顶部 |
| | | // 需要加上字体大小来使文字显示在期望的位置 |
| | | $data[0] = $fontSizePt; // 字体大小(pt) |
| | | $data[1] = (float)$left; // 左边距(px) |
| | | $data[2] = (float)$top + $fontSize; // 顶部位置(px),加字体大小以校正基线 |
| | | return $data; |
| | | } |
| | | |
| | | /** |
| | | * 获取文本实际高度 |
| | | * @param $fontSizePx 字体大小(px) |
| | | * @return float 行高(px) |
| | | */ |
| | | private function getLineHeight($fontSizePx) { |
| | | // 行高 = 字体大小 * 1.5 (更舒适的行间距) |
| | | return $fontSizePx * 1.5; |
| | | } |
| | | |
| | | /** |
| | | * 获取行间距 |
| | | * @param $fontSizePx 字体大小(px) |
| | | * @return float 行间距(px) |
| | | */ |
| | | private function getLineSpacing($fontSizePx) { |
| | | // 行间距 = 字体大小 * 0.5 (50%的字体大小作为间距) |
| | | return $fontSizePx * 0.5; |
| | | } |
| | | |
| | | /** |
| | | * 智能计算字段的最大宽度 |
| | | * 根据右边是否有其他字段来动态计算maxWidth |
| | | * @param $left |
| | | * @param $top |
| | | * @param $backdropWidth |
| | | * @return float |
| | | */ |
| | | private function calculateFieldMaxWidth($left, $top, $backdropWidth = 375) |
| | | { |
| | | $rightFields = []; |
| | | $tolerance = 20; // 容差:判断是否在同一行的像素范围 |
| | | |
| | | // 收集所有可能的字段 |
| | | $fields = []; |
| | | |
| | | // 公司 |
| | | if (!empty($this->config['unit'][0])) { |
| | | $fields[] = [ |
| | | 'name' => 'unit', |
| | | 'left' => $this->config['unit'][0]['left'], |
| | | 'top' => $this->config['unit'][0]['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 职位 |
| | | if (!empty($this->config['duties'][0])) { |
| | | $fields[] = [ |
| | | 'name' => 'duties', |
| | | 'left' => $this->config['duties'][0]['left'], |
| | | 'top' => $this->config['duties'][0]['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 姓名 |
| | | if (!empty($this->config['name'])) { |
| | | $fields[] = [ |
| | | 'name' => 'name', |
| | | 'left' => $this->config['name']['left'], |
| | | 'top' => $this->config['name']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 地址 |
| | | if (!empty($this->config['address'][0])) { |
| | | $fields[] = [ |
| | | 'name' => 'address', |
| | | 'left' => $this->config['address'][0]['left'], |
| | | 'top' => $this->config['address'][0]['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 邮箱 |
| | | if (!empty($this->config['mailbox'])) { |
| | | $fields[] = [ |
| | | 'name' => 'mailbox', |
| | | 'left' => $this->config['mailbox']['left'], |
| | | 'top' => $this->config['mailbox']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 手机 |
| | | if (!empty($this->config['mobile'])) { |
| | | $fields[] = [ |
| | | 'name' => 'mobile', |
| | | 'left' => $this->config['mobile']['left'], |
| | | 'top' => $this->config['mobile']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 微信 |
| | | if (!empty($this->config['wechat'])) { |
| | | $fields[] = [ |
| | | 'name' => 'wechat', |
| | | 'left' => $this->config['wechat']['left'], |
| | | 'top' => $this->config['wechat']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 电话 |
| | | if (!empty($this->config['phone'])) { |
| | | $fields[] = [ |
| | | 'name' => 'phone', |
| | | 'left' => $this->config['phone']['left'], |
| | | 'top' => $this->config['phone']['top'] |
| | | ]; |
| | | } |
| | | |
| | | // 找出右边最近的字段 |
| | | // 判断标准: left > 当前left, 且top在合理范围内(±20px) |
| | | foreach ($fields as $field) { |
| | | if ($field['left'] > $left && abs($field['top'] - $top) <= $tolerance) { |
| | | $rightFields[] = $field; |
| | | } |
| | | } |
| | | |
| | | // 如果右边有字段,取最近的一个的左边距 |
| | | if (count($rightFields) > 0) { |
| | | // 按left排序 |
| | | usort($rightFields, function($a, $b) { |
| | | return $a['left'] - $b['left']; |
| | | }); |
| | | return max($rightFields[0]['left'] - $left - 10, 50); |
| | | } |
| | | |
| | | // 如果右边没有字段,使用名片边缘 |
| | | return max($backdropWidth - $left - 10, 100); |
| | | } |
| | | |
| | | /** |
| | | * 写入支持换行的文本 |
| | | * @param $editor |
| | | * @param $text |
| | | * @param $x |
| | | * @param $y |
| | | * @param $fontSize |
| | | * @param $color |
| | | * @param $fontPath |
| | | * @param $maxWidth |
| | | * @return int 返回实际使用的行数 |
| | | */ |
| | | private function writeTextWithWrap($editor, $text, $x, $y, $fontSize, $color, $fontPath, $maxWidth) |
| | | { |
| | | $fontSizePt = $fontSize * 0.75; // px转pt |
| | | $lineHeight = $this->getLineHeight($fontSize); // 获取精确的行高 |
| | | $lines = []; |
| | | $currentLine = ''; |
| | | |
| | | // 按字符分割文本 |
| | | $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY); |
| | | |
| | | foreach ($chars as $char) { |
| | | $testLine = $currentLine . $char; |
| | | $bbox = imagettfbbox($fontSizePt, 0, $fontPath, $testLine); |
| | | $lineWidth = $bbox[2] - $bbox[0]; |
| | | |
| | | // 使用maxWidth减去一点边距,确保不超出边界 |
| | | if ($lineWidth > $maxWidth - 2 && $currentLine !== '') { |
| | | $lines[] = $currentLine; |
| | | $currentLine = $char; |
| | | } else { |
| | | $currentLine = $testLine; |
| | | } |
| | | } |
| | | |
| | | if ($currentLine !== '') { |
| | | $lines[] = $currentLine; |
| | | } |
| | | |
| | | // 写入每一行 |
| | | foreach ($lines as $index => $line) { |
| | | // 第一行使用原始y坐标,后续行加上行高偏移 |
| | | $lineY = $y + ($index * $lineHeight); |
| | | imagettftext($editor, $fontSizePt, 0, $x, $lineY, $color, $fontPath, $line); |
| | | } |
| | | |
| | | // 返回实际占用的行数 |
| | | return count($lines); |
| | | } |
| | | |
| | | } |