|
|
@@ -407,7 +407,7 @@ class PrintPlanner
|
|
|
self::makeElement('title', 'text', '生产流程单', '', '', 15, 4, 70, 6, 20, 'center', true, false),
|
|
|
];
|
|
|
}
|
|
|
- $elements = self::alignFlowCardFields(self::alignHeaderFields(self::bindPrintKeys($elements)));
|
|
|
+ $elements = self::resolveQrField(self::alignFlowCardFields(self::alignHeaderFields(self::bindPrintKeys($elements))));
|
|
|
$fields = [];
|
|
|
foreach ($elements as $el) {
|
|
|
if (($el['type'] ?? '') === 'field' || ($el['key'] ?? '') !== '') {
|
|
|
@@ -488,8 +488,10 @@ class PrintPlanner
|
|
|
if ($pageH <= 0) {
|
|
|
$pageH = 297;
|
|
|
}
|
|
|
+ $typeface = self::overlayTypeface($schema);
|
|
|
$html = '<div style="position:relative;width:' . $pageW . 'mm;height:' . $pageH
|
|
|
- . 'mm;margin:0;overflow:hidden;background:transparent;font-family:SimSun,serif;line-height:1.2;">';
|
|
|
+ . 'mm;margin:0;overflow:hidden;background:transparent;font-family:' . $typeface['css']
|
|
|
+ . ';font-size:' . $typeface['px'] . 'px;font-weight:normal;line-height:1.2;">';
|
|
|
if ($includeImage && $src !== '') {
|
|
|
$html .= '<img src="' . $src . '" style="position:absolute;left:0;top:0;width:100%;height:100%;border:0;display:block;" alt="流程单底图" />';
|
|
|
}
|
|
|
@@ -503,17 +505,14 @@ class PrintPlanner
|
|
|
$top = self::pct($field['y'] ?? 0);
|
|
|
$width = self::pct($field['w'] ?? 10);
|
|
|
$height = self::pct($field['h'] ?? 4);
|
|
|
- $size = max(8, (int)($field['fontSize'] ?? 13));
|
|
|
$align = in_array($field['align'] ?? 'left', ['left', 'center', 'right'], true) ? $field['align'] : 'left';
|
|
|
$color = self::color($field['color'] ?? '#111111', false, '#111111');
|
|
|
- $weight = !empty($field['bold']) ? 'bold' : 'normal';
|
|
|
- $font = htmlspecialchars(self::fontFamily($field['fontFamily'] ?? 'SimSun'), ENT_QUOTES, 'UTF-8');
|
|
|
$content = $key !== ''
|
|
|
? '{' . $key . '}'
|
|
|
: htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
|
|
$html .= '<div style="position:absolute;left:' . $left . '%;top:' . $top
|
|
|
- . '%;width:' . $width . '%;height:' . $height . '%;font-size:' . $size
|
|
|
- . 'px;font-weight:' . $weight . ';font-family:' . $font
|
|
|
+ . '%;width:' . $width . '%;height:' . $height . '%;font-size:' . $typeface['px']
|
|
|
+ . 'px;font-weight:normal;font-style:normal;font-family:' . $typeface['css']
|
|
|
. ';overflow:hidden;white-space:nowrap;text-align:' . $align
|
|
|
. ';color:' . $color . ';background:transparent;">' . $content . '</div>';
|
|
|
}
|
|
|
@@ -539,8 +538,10 @@ class PrintPlanner
|
|
|
if ($pageH <= 0) {
|
|
|
$pageH = 297;
|
|
|
}
|
|
|
+ $fields = self::fillElements($schema);
|
|
|
+ $typeface = self::overlayTypeface($schema, $fields);
|
|
|
$items = [];
|
|
|
- foreach (self::fillElements($schema) as $field) {
|
|
|
+ foreach ($fields as $field) {
|
|
|
$key = trim((string)($field['key'] ?? ''));
|
|
|
$text = $key !== ''
|
|
|
? self::fillText($key, $fill, $processes)
|
|
|
@@ -555,18 +556,12 @@ class PrintPlanner
|
|
|
if ($payload === '') {
|
|
|
continue;
|
|
|
}
|
|
|
- $size = round(max(18, min($wMm, $hMm) - 0.8), 2);
|
|
|
- if ($size > min($wMm, $hMm)) {
|
|
|
- $size = round(min($wMm, $hMm), 2);
|
|
|
- }
|
|
|
- $items[] = [
|
|
|
- 'type' => 'qrcode',
|
|
|
- 'top' => round($yMm + max(0, ($hMm - $size) / 2), 2) . 'mm',
|
|
|
- 'left' => round($xMm + max(0, ($wMm - $size) / 2), 2) . 'mm',
|
|
|
- 'width' => $size . 'mm',
|
|
|
- 'height' => $size . 'mm',
|
|
|
- 'text' => $payload,
|
|
|
- ];
|
|
|
+ $qrW = max(8, self::pct($field['w'] ?? 10) / 100 * $pageW);
|
|
|
+ $qrH = max(8, self::pct($field['h'] ?? 4) / 100 * $pageH);
|
|
|
+ $items[] = array_merge(
|
|
|
+ ['type' => 'qrcode', 'text' => $payload],
|
|
|
+ self::centerQrInCell($xMm, $yMm, $qrW, $qrH)
|
|
|
+ );
|
|
|
continue;
|
|
|
}
|
|
|
if ($text === '') {
|
|
|
@@ -579,14 +574,6 @@ class PrintPlanner
|
|
|
$xMm += $padX;
|
|
|
$hMm = max(3.2, $hMm - $padY * 1.4);
|
|
|
$wMm = max(6, $wMm - $padX);
|
|
|
- $fontSize = (int)max(7, min(11, round($hMm * 2.1)));
|
|
|
- $charCount = max(1, mb_strlen($text, 'UTF-8'));
|
|
|
- $fitWidth = (int)floor($wMm / $charCount / 0.36);
|
|
|
- if ($fitWidth >= 7) {
|
|
|
- $fontSize = min($fontSize, $fitWidth);
|
|
|
- } else {
|
|
|
- $fontSize = min($fontSize, 8);
|
|
|
- }
|
|
|
$align = $field['align'] ?? 'left';
|
|
|
$items[] = [
|
|
|
'top' => round($yMm, 2) . 'mm',
|
|
|
@@ -594,15 +581,93 @@ class PrintPlanner
|
|
|
'width' => round($wMm, 2) . 'mm',
|
|
|
'height' => round($hMm, 2) . 'mm',
|
|
|
'text' => $text,
|
|
|
- 'font' => self::fontFamily($field['fontFamily'] ?? 'SimSun'),
|
|
|
- 'size' => $fontSize,
|
|
|
+ 'font' => $typeface['family'],
|
|
|
+ 'size' => $typeface['pt'],
|
|
|
'align' => $align === 'center' ? 2 : ($align === 'right' ? 3 : 1),
|
|
|
- 'bold' => !empty($field['bold']) ? 1 : 0,
|
|
|
+ 'bold' => $typeface['bold'],
|
|
|
];
|
|
|
}
|
|
|
return $items;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 二维码居中于识版格子内(不做文字字段的内边距)。
|
|
|
+ * @param float $xMm
|
|
|
+ * @param float $yMm
|
|
|
+ * @param float $wMm
|
|
|
+ * @param float $hMm
|
|
|
+ * @return array{top:string,left:string,width:string,height:string}
|
|
|
+ */
|
|
|
+ protected static function centerQrInCell($xMm, $yMm, $wMm, $hMm)
|
|
|
+ {
|
|
|
+ $wMm = max(8, (float)$wMm);
|
|
|
+ $hMm = max(8, (float)$hMm);
|
|
|
+ $inset = min(1.0, $wMm * 0.04, $hMm * 0.04);
|
|
|
+ $innerW = max(8, $wMm - $inset * 2);
|
|
|
+ $innerH = max(8, $hMm - $inset * 2);
|
|
|
+ $size = min($innerW, $innerH);
|
|
|
+ $left = $xMm + ($wMm - $size) / 2;
|
|
|
+ $top = $yMm + ($hMm - $size) / 2;
|
|
|
+ return [
|
|
|
+ 'top' => round($top, 2) . 'mm',
|
|
|
+ 'left' => round($left, 2) . 'mm',
|
|
|
+ 'width' => round($size, 2) . 'mm',
|
|
|
+ 'height' => round($size, 2) . 'mm',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同一张套打单上的填写文字用同一字体、同一字号、同一字重。
|
|
|
+ * @param array $schema
|
|
|
+ * @param array|null $fields
|
|
|
+ * @return array{family:string,css:string,px:int,pt:int,bold:int}
|
|
|
+ */
|
|
|
+ protected static function overlayTypeface(array $schema, array $fields = null)
|
|
|
+ {
|
|
|
+ if ($fields === null) {
|
|
|
+ $fields = self::fillElements($schema);
|
|
|
+ }
|
|
|
+ $sizes = [];
|
|
|
+ foreach ($fields as $field) {
|
|
|
+ if (trim((string)($field['key'] ?? '')) === 'qr') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $px = max(8, (int)($field['fontSize'] ?? 13));
|
|
|
+ $sizes[] = $px;
|
|
|
+ }
|
|
|
+ $px = 13;
|
|
|
+ if ($sizes) {
|
|
|
+ $freq = array_count_values($sizes);
|
|
|
+ arsort($freq);
|
|
|
+ $px = (int)key($freq);
|
|
|
+ }
|
|
|
+ $pt = (int)max(8, min(12, round($px * 72 / 96)));
|
|
|
+ return [
|
|
|
+ 'family' => self::overlayFontFamily(),
|
|
|
+ 'css' => self::overlayFontCss(),
|
|
|
+ 'px' => $px,
|
|
|
+ 'pt' => $pt,
|
|
|
+ 'bold' => 0,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 套打 overlay 专用字体(不影响 compose 排版)。
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ protected static function overlayFontFamily()
|
|
|
+ {
|
|
|
+ return '微软雅黑';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ protected static function overlayFontCss()
|
|
|
+ {
|
|
|
+ return "'Microsoft YaHei','微软雅黑',sans-serif";
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 流程单二维码:写入可被微信打开的信息页地址(纯文本微信会提示无法识别)。
|
|
|
* @param array $fill
|
|
|
@@ -1084,12 +1149,6 @@ class PrintPlanner
|
|
|
$wideW = $w;
|
|
|
$wideIdx = $idx;
|
|
|
}
|
|
|
- if ($y < 22 && $h > 8 && $w > 8 && $x > 50) {
|
|
|
- $elements[$idx]['key'] = 'qr';
|
|
|
- $elements[$idx]['label'] = '二维码区域';
|
|
|
- $elements[$idx]['type'] = 'field';
|
|
|
- $elements[$idx]['role'] = 'fill';
|
|
|
- }
|
|
|
if ($y < 22 && $w >= 10 && $w <= 28 && $h >= 2.5 && $h <= 8) {
|
|
|
$topSmall[] = $idx;
|
|
|
}
|
|
|
@@ -1116,6 +1175,70 @@ class PrintPlanner
|
|
|
return $elements;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 流程卡右侧大块空白通常是二维码区;优先绑定面积最大的右侧格子。
|
|
|
+ * @param array $elements
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ protected static function resolveQrField(array $elements)
|
|
|
+ {
|
|
|
+ $reserved = [
|
|
|
+ 'product.name', 'product.code', 'product.spec', 'qty_plan',
|
|
|
+ 'workorder.batch_no', 'workorder.no', 'workorder.pack_no', 'workorder.remark',
|
|
|
+ 'sheet.qty', 'sheet.no_text', 'operator', 'print_time', 'process_names',
|
|
|
+ ];
|
|
|
+ $qrIndices = [];
|
|
|
+ $bestIdx = null;
|
|
|
+ $bestArea = 0;
|
|
|
+ foreach ($elements as $idx => $el) {
|
|
|
+ if (!is_array($el)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $key = trim((string)($el['key'] ?? ''));
|
|
|
+ $label = trim((string)($el['label'] ?? ''));
|
|
|
+ if ($key === 'qr' || preg_match('/二维码/u', $label)) {
|
|
|
+ $qrIndices[] = $idx;
|
|
|
+ }
|
|
|
+ $x = (float)($el['x'] ?? 0);
|
|
|
+ $y = (float)($el['y'] ?? 0);
|
|
|
+ $w = (float)($el['w'] ?? 0);
|
|
|
+ $h = (float)($el['h'] ?? 0);
|
|
|
+ if ($x < 30 || $w < 12 || $h < 12) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($y < 6 && $h < 16) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($key !== '' && ($key === 'qr' || preg_match('/^process\./', $key) || in_array($key, $reserved, true))) {
|
|
|
+ if ($key !== 'qr') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $area = $w * $h;
|
|
|
+ if ($area > $bestArea) {
|
|
|
+ $bestArea = $area;
|
|
|
+ $bestIdx = $idx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($bestIdx === null) {
|
|
|
+ return $elements;
|
|
|
+ }
|
|
|
+ foreach ($qrIndices as $idx) {
|
|
|
+ if ($idx === $bestIdx) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $area = (float)($elements[$idx]['w'] ?? 0) * (float)($elements[$idx]['h'] ?? 0);
|
|
|
+ if ($area < $bestArea * 0.75) {
|
|
|
+ $elements[$idx]['key'] = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $elements[$bestIdx]['key'] = 'qr';
|
|
|
+ $elements[$bestIdx]['label'] = '二维码区域';
|
|
|
+ $elements[$bestIdx]['type'] = 'field';
|
|
|
+ $elements[$bestIdx]['role'] = 'fill';
|
|
|
+ return $elements;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 产品工序流程卡:色块标签常把「产品名称 / 总投料数」和工序列认反。
|
|
|
* 按位置纠正——工序名称列是左侧一叠格子,其上一行从左到右是名称、总投料数、规格。
|
|
|
@@ -1471,16 +1594,19 @@ class PrintPlanner
|
|
|
protected static function fontFamily($name)
|
|
|
{
|
|
|
$name = trim((string)$name);
|
|
|
- $allow = ['SimSun', 'SimHei', 'KaiTi', 'Microsoft YaHei', 'FangSong', 'Arial', 'serif', 'sans-serif'];
|
|
|
+ $allow = ['SimSun', 'SimHei', 'KaiTi', 'Microsoft YaHei', '微软雅黑', 'FangSong', 'Arial', 'serif', 'sans-serif'];
|
|
|
foreach ($allow as $item) {
|
|
|
if (strcasecmp($item, $name) === 0) {
|
|
|
- return $item;
|
|
|
+ return $item === 'Microsoft YaHei' ? '微软雅黑' : $item;
|
|
|
}
|
|
|
}
|
|
|
+ if (preg_match('/微软雅黑|微软黑|yahei/i', $name)) {
|
|
|
+ return '微软雅黑';
|
|
|
+ }
|
|
|
if (preg_match('/宋体|song/i', $name)) {
|
|
|
return 'SimSun';
|
|
|
}
|
|
|
- if (preg_match('/黑|hei/i', $name)) {
|
|
|
+ if (preg_match('/黑体|^hei$/i', $name)) {
|
|
|
return 'SimHei';
|
|
|
}
|
|
|
if (preg_match('/楷/i', $name)) {
|