<?php
|
|
namespace app\common\service\business;
|
|
use app\common\model\plus\business\Template;
|
use app\common\service\qrcode\Base;
|
use Grafika\Color;
|
use Grafika\Grafika;
|
|
/**
|
* 生成名片
|
* Class Qrcode
|
* @package app\common\service
|
*/
|
class Poster extends Base
|
{
|
/* @var array $dealer 用户信息 */
|
private $dealer;
|
|
/* @var array $config 名片设置 */
|
private $config;
|
public $template;
|
private $businessName;
|
|
/**
|
* 构造方法
|
* Poster constructor.
|
* @param $dealer
|
* @throws \Exception
|
*/
|
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);
|
}
|
|
/**
|
* 获取用户名片
|
* @return string
|
* @throws \app\common\exception\BaseException
|
* @throws \think\exception\DbException
|
* @throws \Exception
|
*/
|
public function getImage($isType = true)
|
{
|
if (file_exists($this->getPosterPath($this->businessName)) && $isType) {
|
return $this->getPosterUrl($this->businessName);
|
}
|
// 小程序id
|
$appId = $this->dealer['app_id'];
|
// 1. 下载背景图
|
$backdrop = $this->saveTempImage($appId, $this->config['backdrop']['src'], 'backdrop');
|
if ($this->dealer['file_path']) {
|
// 2. 下载用户头像
|
$avatarUrl = $this->saveTempImage($appId, $this->dealer['file_path'], 'avatar');
|
} else {
|
// 2. 下载用户头像
|
$avatarUrl = $this->saveTempImage($appId, $this->dealer['user']['avatarUrl'], 'avatar');
|
}
|
$logo = '';
|
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, $this->businessName, $logo);
|
}
|
|
/**
|
* 名片图文件路径
|
* @return string
|
*/
|
public function getPosterPath($name)
|
{
|
// 保存路径
|
$tempPath = $_SERVER['DOCUMENT_ROOT']. '/temp/'.$this->template['app_id'] . '/'.$name.'/';
|
!is_dir($tempPath) && mkdir($tempPath, 0755, true);
|
return $tempPath . $this->getPosterName();
|
}
|
|
/**
|
* 名片文件名称
|
* @return string
|
*/
|
private function getPosterName()
|
{
|
return md5('poster_' . $this->dealer['business_card_id']) . '.png';
|
}
|
|
/**
|
* 名片url
|
* @return string
|
*/
|
private function getPosterUrl($name)
|
{
|
return base_url() . '/temp/' . $this->template['app_id'] . '/' .$name . '/' . $this->getPosterName() . '?t=' . time();
|
}
|
|
/**
|
* 拼接名片
|
* @param $backdrop
|
* @param $avatarUrl
|
* @param $imageUrl
|
* @param $logo
|
* @return string
|
* @throws \Exception
|
*/
|
private function savePoster($backdrop, $avatarUrl, $imageUrl, $logo)
|
{
|
|
// 获取背景图信息,判断图片类型
|
$backdropInfo = getimagesize($backdrop);
|
$width = $backdropInfo[0];
|
$height = $backdropInfo[1];
|
|
// 根据原图类型创建对应类型的画布,提高质量
|
$imageType = $backdropInfo[2];
|
|
// 创建真彩色画布,确保足够的色彩深度
|
$newImage = imagecreatetruecolor($width, $height);
|
|
// 启用抗锯齿
|
imageantialias($newImage, true);
|
|
// 启用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);
|
if ($this->config['avatar']['display'] == 1) {
|
// 生成圆形用户头像
|
$this->config['avatar']['style'] === 'circle' && $this->circular($avatarUrl, $avatarUrl);
|
// 重设用户头像宽高
|
$avatarWidth = $this->config['avatar']['width'];
|
//重设图片大小
|
$this->resizeExact($avatarUrl,$avatarWidth,$avatarWidth);
|
// 用户头像添加到背景图
|
$avatarX = $this->config['avatar']['left'];
|
$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) {
|
// 生成圆形logo
|
$this->config['logo']['style'] === 'circle' && $this->circular($logo, $logo);
|
//调整后大小
|
$logoWidth = round($this->config['logo']['width']);
|
$logoHeight = round($this->config['logo']['height']);
|
//重设图片大小
|
$this->resizeExact($logo,$logoWidth,$logoHeight);
|
// 用户logo添加到背景图
|
$logoX = $this->config['logo']['left'];
|
$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', '', 0, false, $width);
|
// 写入公司列表
|
$unitBaseTop = 0;
|
$unitFontSize = $this->config['unit'][0]['fontSize'];
|
foreach ($this->dealer['unit'] as $key => $value) {
|
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;
|
}
|
}
|
// 写入职位列表
|
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;
|
}
|
}
|
}
|
|
// 写入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) {
|
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', '手机:', 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', '邮箱:', 0, false, $width);
|
}
|
if ($this->dealer['phone']) {
|
$this->addText($newImage, 'phone', '电话:', 0, false, $width);
|
}
|
// 保存图片
|
// 根据背景图类型选择保存格式
|
$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);
|
}
|
|
/**
|
* 打开图片
|
* @param $image
|
* @return false|resource
|
*/
|
public function imagEcr($image)
|
{
|
// 创建画布
|
return imagecreatefromstring(file_get_contents($image));
|
}
|
|
/**
|
* 重设图片大小
|
* @param $image
|
* @param $newWidth
|
* @param $newHeight
|
* @return void
|
*/
|
public function resizeExact($imageUrl, $newWidth, $newHeight )
|
{
|
$image=imagecreatefromstring(file_get_contents($imageUrl));
|
// 调整图片大小
|
$targetImage = imagecreatetruecolor($newWidth, $newHeight);
|
imageantialias($targetImage, true);
|
//获取图片大小
|
$width = imagesx($image);
|
$height = imagesy($image);
|
// 这一句一定要有
|
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); // 保存时使用最高质量
|
$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 $newImage
|
* @param $data
|
* @return void
|
*/
|
public function addImagecopy($newImage,$data){
|
$src=$this->saveTempImage($this->template['app_id'], $data['src'], 'icon');
|
$this->resizeExact($src,$data['width'], $data['height']);
|
$backdropIm = $this->imagEcr($src);
|
// 将第一张图片覆盖在新图像上
|
imagecopy($newImage, $backdropIm, $data['left'], $data['top'], 0, 0, $data['width'], $data['height']);
|
}
|
|
/**
|
* 写入文字
|
* @param $editor
|
* @param $backdropImage
|
* @param $name
|
* @param $text
|
* @param $key
|
* @param $type
|
* @param $width
|
* @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) {
|
// 确保配置数组存在该索引,不存在则使用索引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);
|
// 智能计算最大宽度
|
$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];
|
$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'][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'][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>0) {
|
$strArr = self::mbStrSplit($title, $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) {
|
imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $v);
|
} else {
|
$y = $y + ($fontSize * $k);
|
imagettftext($editor, $fontSize, 0, $fontX + $the_box, $y, $colorResource, $fontPath, $v);
|
}
|
}
|
return true;
|
} else {
|
return imagettftext($editor, $fontSize, 0, $fontX, $fontY, $colorResource, $fontPath, $text . $title);
|
}
|
|
} 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);
|
// 智能计算最大宽度
|
$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 $string
|
* @param $len
|
* @return mixed
|
*/
|
private function mbStrSplit($string, $len = 1)
|
{
|
$start = 0;
|
$strlen = mb_strlen($string);
|
while ($strlen) {
|
$array[] = mb_substr($string, $start, $len, "utf8");
|
$string = mb_substr($string, $len, $strlen, "utf8");
|
$strlen = mb_strlen($string);
|
}
|
return $array;
|
}
|
|
/**
|
* 生成圆形图片
|
* @param static $imgpath 图片地址
|
* @param string $saveName 保存文件名,默认空。
|
*/
|
private function circular($imgpath, $saveName = '')
|
{
|
$srcImg = imagecreatefromstring(file_get_contents($imgpath));
|
$w = imagesx($srcImg);
|
$h = imagesy($srcImg);
|
$w = $h = min($w, $h);
|
$newImg = imagecreatetruecolor($w, $h);
|
// 这一句一定要有
|
imagesavealpha($newImg, true);
|
// 拾取一个完全透明的颜色,最后一个参数127为全透明
|
$bg = imagecolorallocatealpha($newImg, 255, 255, 255, 127);
|
imagefill($newImg, 0, 0, $bg);
|
$r = $w / 2; //圆半径
|
for ($x = 0; $x < $w; $x++) {
|
for ($y = 0; $y < $h; $y++) {
|
$rgbColor = imagecolorat($srcImg, $x, $y);
|
if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r))) {
|
imagesetpixel($newImg, $x, $y, $rgbColor);
|
}
|
}
|
}
|
// 输出图片到文件
|
imagepng($newImg, $saveName,0);
|
// 释放空间
|
imagedestroy($srcImg);
|
imagedestroy($newImg);
|
}
|
|
/**
|
* 获取名片模板
|
* @return string
|
* @throws \app\common\exception\BaseException
|
* @throws \think\exception\DbException
|
* @throws \Exception
|
*/
|
public function getImageE($config, $template_id = '')
|
{
|
$model = new Template();
|
$this->config = $config;
|
// 小程序id
|
$appId = $this->template['app_id'];
|
// 1. 下载背景图
|
$backdrop = $this->saveTempImage($appId, $this->config['backdrop']['src'], 'backdrop');
|
// 2. 下载用户头像
|
$avatarUrl = $this->saveTempImage($appId, $model::$base_url."/image/agent/avatar.jpg", 'avatar', $template_id);
|
// 2. 下载logo
|
$logo = $this->saveTempImage($appId, $this->config['logo']['src'], 'logo');
|
// 4. 拼接名片
|
return $this->savePoster($backdrop, $avatarUrl, 'template', $logo);
|
}
|
|
/**
|
* 手机号/座机号脱敏处理
|
* @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
|
* @return false|int
|
*/
|
private function colorResource($color, $editor)
|
{
|
$Color = new Color($color);
|
list($r, $g, $b, $alpha) = $Color->getRgba();
|
$scale = round(127 * $alpha);
|
$invert = 127 - $scale;
|
return imagecolorallocatealpha(
|
$editor,
|
$r, $g, $b,
|
$invert
|
);
|
}
|
|
/**
|
*计算字体大小和顶部跟左边的距离
|
* @param $fontSize
|
* @param $left
|
* @param $top
|
* @return array [fontSizePt, left, top]
|
*/
|
private function SizeLeftTop($fontSize,$left,$top){
|
// 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);
|
}
|
|
}
|