textElementLayout.ts
3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/** 文本控件在元素框内的垂直对齐(config.verticalAlign) */
export type TextVerticalAlign = 'top' | 'center' | 'bottom'
export function readVerticalAlign(
config: Record<string, unknown> | undefined | null,
): TextVerticalAlign {
const raw = config?.verticalAlign ?? config?.VerticalAlign
const v = String(raw ?? 'top').trim().toLowerCase()
if (v === 'center' || v === 'middle') return 'center'
if (v === 'bottom') return 'bottom'
return 'top'
}
export function computeVerticalTextBlockOffset(
innerHeight: number,
blockHeight: number,
verticalAlign: TextVerticalAlign,
): number {
const extra = Math.max(0, innerHeight - blockHeight)
if (verticalAlign === 'center') return Math.floor(extra / 2)
if (verticalAlign === 'bottom') return extra
return 0
}
export function readFontWeight(
config: Record<string, unknown> | undefined | null,
): 'normal' | 'bold' {
const raw = config?.fontWeight ?? config?.FontWeight
const v = String(raw ?? 'normal').trim().toLowerCase()
return v === 'bold' || v === '700' || v === 'bolder' ? 'bold' : 'normal'
}
export function readFontStyle(
config: Record<string, unknown> | undefined | null,
): 'normal' | 'italic' {
const raw = config?.fontStyle ?? config?.FontStyle
const v = String(raw ?? 'normal').trim().toLowerCase()
return v === 'italic' || v === 'oblique' ? 'italic' : 'normal'
}
export function readTextDecoration(
config: Record<string, unknown> | undefined | null,
): 'none' | 'underline' {
const raw = config?.textDecoration ?? config?.TextDecoration
const v = String(raw ?? 'none').trim().toLowerCase()
return v.includes('underline') ? 'underline' : 'none'
}
export function readElementBorder(
el: {
border?: string | null
Border?: string | null
BorderType?: string | null
borderType?: string | null
},
): string {
const raw = el.border ?? el.Border ?? el.BorderType ?? el.borderType ?? 'none'
return String(raw).trim().toLowerCase() || 'none'
}
/** 元素 rotation(兼容大小写) */
export function readElementRotation(
el: { rotation?: string | null; Rotation?: string | null },
): 'horizontal' | 'vertical' | 'right' | 'rotate180' {
const v = String(el.rotation ?? el.Rotation ?? 'horizontal').trim().toLowerCase()
if (v === 'vertical' || v === 'left' || v === 'rotate-left' || v === 'left90') return 'vertical'
if (v === 'right' || v === 'rotate-right' || v === 'right90') return 'right'
if (v === 'rotate180' || v === '180' || v === 'down' || v === 'inverted') return 'rotate180'
return 'horizontal'
}
export function elementRotationDegrees(
el: { rotation?: string | null; Rotation?: string | null },
): 0 | -90 | 90 | 180 {
const rotation = readElementRotation(el)
if (rotation === 'vertical') return -90
if (rotation === 'right') return 90
if (rotation === 'rotate180') return 180
return 0
}
/** 与 renderLabelPreviewCanvas lineHeightForTextElement 一致 */
export function textElementLineHeightPx(fontSize: number, isDateTimeType = false): number {
return isDateTimeType
? Math.max(fontSize + 2, Math.round(fontSize * 1.2))
: Math.max(fontSize + 2, Math.round(fontSize * 1.25))
}
/** 矮框内固定文案:强制单行,避免折行后仅显示第一行 */
export function isShortSingleLineTextBox(
boxHeightPx: number,
fontSize: number,
text: string,
opts?: { breakAll?: boolean },
): boolean {
if (opts?.breakAll) return false
if (String(text ?? '').includes('\n')) return false
const lineH = textElementLineHeightPx(fontSize, false)
const descentPad = Math.max(2, Math.round(fontSize * 0.14))
const maxLines = Math.max(
1,
Math.floor((Math.max(0, boxHeightPx) + descentPad) / lineH),
)
return maxLines <= 1
}