• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / 8a6ba229-c82f-4a21-a1ed-95461f2ad66c

04 Sep 2023 08:37AM UTC coverage: 90.196% (-0.004%) from 90.2%
8a6ba229-c82f-4a21-a1ed-95461f2ad66c

Pull #2829

circleci

cmm-va
fix: delete f
Pull Request #2829: fix: add tabIndex

5164 of 6386 branches covered (0.0%)

Branch coverage included in aggregate %.

78 of 78 new or added lines in 26 files covered. (100.0%)

13024 of 13779 relevant lines covered (94.52%)

971.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.17
/src/watermark/watermark.directive.ts
1
import { Directive, Input, ElementRef, OnDestroy, OnInit, SimpleChanges, OnChanges } from '@angular/core';
2
import { InputBoolean, UnsubscribeMixin } from 'ngx-tethys/core';
3
import { Subject, Observable } from 'rxjs';
4
import { takeUntil } from 'rxjs/operators';
5
import { DEFAULT_WATERMARK_CONFIG, DEFAULT_CANVAS_CONFIG } from './config';
6
import { MutationObserverFactory } from '@angular/cdk/observers';
7

8
export interface ThyCanvasConfigType {
9
    degree?: number;
10
    color?: string;
11
    fontSize?: number | string;
12
    textLineHeight?: number;
1✔
13
    gutter?: number[];
14
}
6✔
15

6!
16
/**
17
 * 水印指令
18
 * @name thyWatermark
4✔
19
 */
4✔
20
@Directive({
4✔
21
    selector: '[thyWatermark]',
4✔
22
    standalone: true
23
})
24
export class ThyWatermarkDirective extends UnsubscribeMixin implements OnInit, OnDestroy, OnChanges {
4!
25
    /**
4✔
26
     * 是否禁用,默认为 false
6✔
27
     */
28
    @Input()
29
    @InputBoolean()
30
    thyDisabled: boolean = false;
4✔
31

32
    content: string;
33
    /**
34
     * 水印内容
7✔
35
     */
7✔
36
    @Input()
6✔
37
    set thyWatermark(value: string) {
4✔
38
        value = value?.replace(/^\"|\"$/g, '');
2!
39
        this.content = !!value ? value : '';
2✔
40
    }
41

42
    /**
7✔
43
     * canvas样式配置
5✔
44
     */
4✔
45
    @Input() thyCanvasConfig: ThyCanvasConfigType;
1!
46

47
    private createWatermark$ = new Subject<string>();
7✔
48

7✔
49
    private observer: MutationObserver;
50

51
    private canvas: HTMLCanvasElement;
2✔
52

2✔
53
    private wmDiv: HTMLElement;
54

55
    constructor(private el: ElementRef) {
3!
56
        super();
3✔
57
    }
3✔
58

59
    ngOnInit() {
60
        if (!this.thyDisabled) {
61
            this.createWatermark$.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(() => {
7✔
62
                this.observeAttributes()
63
                    .pipe(takeUntil(this.ngUnsubscribe$))
14✔
64
                    .subscribe(() => {});
65
            });
7✔
66
            this.createWatermark();
7✔
67
        }
7✔
68
    }
7✔
69

7✔
70
    ngOnChanges(changes: SimpleChanges): void {
7✔
71
        const { thyWatermark, thyDisabled } = changes;
72
        const thyWatermarkChange = () => {
73
            if (thyWatermark.firstChange) return;
74
            if (thyWatermark.currentValue) {
75
                this.refreshWatermark();
76
            }
77
        };
78
        const thyDisabledChange = () => {
79
            if (thyDisabled.firstChange) return;
80
            thyDisabled?.currentValue ? this.removeWatermark() : this.refreshWatermark();
56✔
81
        };
7✔
82
        thyWatermark && thyWatermarkChange();
7✔
83
        thyDisabled && thyDisabledChange();
7✔
84
    }
7✔
85

7✔
86
    private refreshWatermark() {
7✔
87
        this.removeWatermark();
88
        this.createWatermark();
7✔
89
    }
7✔
90

7✔
91
    private removeWatermark() {
7✔
92
        if (this.wmDiv) {
7✔
93
            this.wmDiv.remove();
7✔
94
            this.wmDiv = null;
7✔
95
        }
7✔
96
    }
7✔
97

7✔
98
    createCanvas() {
7✔
99
        let { gutter, fontSize, color, degree, textLineHeight } = {
7✔
100
            ...DEFAULT_CANVAS_CONFIG,
7✔
101
            ...(this.thyCanvasConfig || {})
7✔
102
        };
7✔
103

7✔
104
        const [xGutter, yGutter] = gutter;
105
        const canvas = document.createElement('canvas');
7✔
106
        const ctx = canvas.getContext('2d');
7✔
107

108
        const getFakeSize = () => {
6✔
109
            const fakeBox = document.createElement('div');
6✔
110
            const fakeBoxStyle = {
6!
111
                position: 'absolute',
6✔
112
                top: 0,
113
                left: 0,
114
                display: 'inline-block',
115
                'font-size': `${parseFloat('' + fontSize)}px`,
60✔
116
                'word-wrap': 'break-word',
6✔
117
                'font-family': 'inherit',
6!
118
                'white-space': 'pre-line'
6✔
119
            };
6✔
120
            const styleStr = Object.keys(fakeBoxStyle).reduce((pre, next) => ((pre += `${next}:${fakeBoxStyle[next]};`), pre), '');
6✔
121
            fakeBox.setAttribute('style', styleStr);
6✔
122

123
            fakeBox.innerHTML = this.content.replace(/(\\n)/gm, '</br>');
6✔
124
            document.querySelector('body').insertBefore(fakeBox, document.querySelector('body').firstChild);
125
            const { width, height } = fakeBox.getBoundingClientRect();
126
            fakeBox.remove();
6✔
127
            return { width, height };
6✔
128
        };
6✔
129
        const { width: fakeBoxWidth, height: fakeBoxHeight } = getFakeSize();
6✔
130

6!
131
        const angle = (degree * Math.PI) / 180;
6✔
132
        const contentArr = this.content.split('\\n');
133
        const canvasHeight = Math.sin(angle) * fakeBoxWidth + fakeBoxHeight;
134

135
        let start = Math.ceil(Math.sin(angle) * fakeBoxWidth * Math.sin(angle));
6✔
136
        const canvasWidth = start + fakeBoxWidth;
×
137
        canvas.setAttribute('width', '' + (canvasWidth + xGutter));
×
138
        canvas.setAttribute('height', '' + (canvasHeight + yGutter));
×
139

140
        ctx.font = `${parseFloat('' + fontSize)}px microsoft yahei`;
141
        ctx.textAlign = 'center';
6✔
142
        ctx.textBaseline = 'top';
6✔
143
        ctx.fillStyle = color;
6✔
144
        ctx.rotate(0 - (degree * Math.PI) / 180);
145
        contentArr.map((k, i) => {
146
            ctx.fillText(k, -start + Math.ceil(canvasWidth / 2), Math.sin(angle) * canvasWidth + textLineHeight * i);
147
            start += Math.sin(angle) * textLineHeight;
148
        });
4✔
149
        this.canvas = canvas;
150
        return canvas;
1✔
151
    }
152

153
    private createWatermark(isRefresh = true) {
1✔
154
        const watermarkDiv = this.wmDiv || document.createElement('div');
155

156
        const background = !isRefresh ? this.canvas.toDataURL() : this.createCanvas().toDataURL();
157
        const watermarkStyle = {
158
            ...DEFAULT_WATERMARK_CONFIG,
159
            'background-image': `url(${background})`
1✔
160
        };
161

162
        const styleStr = Object.keys(watermarkStyle).reduce((pre, next) => ((pre += `${next}:${watermarkStyle[next]};`), pre), '');
163
        watermarkDiv.setAttribute('style', styleStr);
1✔
164

165
        if (!this.wmDiv) {
166
            const parentNode = this.el.nativeElement;
167
            watermarkDiv.classList.add(`_vm`);
168
            this.wmDiv = watermarkDiv;
169
            parentNode.insertBefore(watermarkDiv, parentNode.firstChild);
170
        }
171
        this.createWatermark$.next('');
172
    }
173

174
    private observeAttributes() {
175
        this.observer?.disconnect();
176
        return new Observable(observe => {
177
            const stream = new Subject<MutationRecord[]>();
178
            this.observer = new MutationObserverFactory().create(mutations => stream.next(mutations));
179
            if (this.observer) {
180
                this.observer.observe(this.wmDiv, {
181
                    attributes: true
182
                });
183
            }
184
            stream.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(() => {
185
                if (this.wmDiv) {
186
                    this?.observer?.disconnect();
187
                    this.createWatermark(false);
188
                }
189
            });
190
            observe.next(stream);
191
            return () => {
192
                this.observer?.disconnect();
193
            };
194
        });
195
    }
196

197
    ngOnDestroy(): void {
198
        super.ngOnDestroy();
199
    }
200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc