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

atinc / ngx-tethys / 881c8997-29c3-4d01-9ef1-22092f16cec2

03 Apr 2024 03:31AM UTC coverage: 90.404% (-0.2%) from 90.585%
881c8997-29c3-4d01-9ef1-22092f16cec2

Pull #3062

circleci

minlovehua
refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()
Pull Request #3062: refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()

5411 of 6635 branches covered (81.55%)

Branch coverage included in aggregate %.

217 of 223 new or added lines in 82 files covered. (97.31%)

201 existing lines in 53 files now uncovered.

13176 of 13925 relevant lines covered (94.62%)

980.1 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, booleanAttribute } from '@angular/core';
2
import { Constructor, MixinBase, mixinUnsubscribe, ThyUnsubscribe } 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
const _MixinBase: Constructor<ThyUnsubscribe> & typeof MixinBase = mixinUnsubscribe(MixinBase);
1✔
9

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

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

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

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

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

7✔
50
    private observer: MutationObserver;
51

52
    private canvas: HTMLCanvasElement;
2✔
53

2✔
54
    private wmDiv: HTMLElement;
55

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

198
    ngOnDestroy(): void {
199
        super.ngOnDestroy();
200
    }
201
}
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