@onco-foundry/mask-port 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/textin_masker.d.ts +1 -1
- package/dist/textin_masker.js +45 -14
- package/package.json +2 -2
package/dist/textin_masker.d.ts
CHANGED
|
@@ -37,6 +37,6 @@ export type TextInMaskerOptions = {
|
|
|
37
37
|
export declare const findSensitiveWordsByRules: (text: string) => readonly SensitiveWord[];
|
|
38
38
|
/**
|
|
39
39
|
* 创建 TextIn 识别脱敏器。凭证在 create 时一次绑定,之后 mask 只传业务参数。
|
|
40
|
-
*
|
|
40
|
+
* 同一敏感词出现多次时逐处遮盖、逐处登记 mapping;跨行命中拆成多个框,仍登记为一个实体。
|
|
41
41
|
*/
|
|
42
42
|
export declare const createTextInMasker: (options: TextInMaskerOptions) => Masker;
|
package/dist/textin_masker.js
CHANGED
|
@@ -82,9 +82,39 @@ const locateWordQuad = (line, beginIndex, wordLength) => {
|
|
|
82
82
|
return line.position === undefined ? undefined : toQuad(line.position);
|
|
83
83
|
};
|
|
84
84
|
const scaleQuadToImage = (quad, scaleX, scaleY) => quad.map(([x, y]) => [x * scaleX, y * scaleY]);
|
|
85
|
+
/**
|
|
86
|
+
* Find exact occurrences against the same newline-joined text seen by the finder,
|
|
87
|
+
* then split each occurrence into line-local spans for geometry lookup.
|
|
88
|
+
*/
|
|
89
|
+
const locateWordSpans = (lines, recognizedText, word) => {
|
|
90
|
+
const lineRanges = [];
|
|
91
|
+
let offset = 0;
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
lineRanges.push({ line, start: offset, end: offset + line.text.length });
|
|
94
|
+
offset += line.text.length + 1;
|
|
95
|
+
}
|
|
96
|
+
const occurrences = [];
|
|
97
|
+
let fromIndex = 0;
|
|
98
|
+
while (word.length > 0) {
|
|
99
|
+
const hit = recognizedText.indexOf(word, fromIndex);
|
|
100
|
+
if (hit < 0)
|
|
101
|
+
break;
|
|
102
|
+
fromIndex = hit + word.length;
|
|
103
|
+
const end = hit + word.length;
|
|
104
|
+
const spans = lineRanges.flatMap(range => {
|
|
105
|
+
const spanStart = Math.max(hit, range.start);
|
|
106
|
+
const spanEnd = Math.min(end, range.end);
|
|
107
|
+
return spanStart < spanEnd
|
|
108
|
+
? [{ line: range.line, beginIndex: spanStart - range.start, length: spanEnd - spanStart }]
|
|
109
|
+
: [];
|
|
110
|
+
});
|
|
111
|
+
occurrences.push(spans);
|
|
112
|
+
}
|
|
113
|
+
return occurrences;
|
|
114
|
+
};
|
|
85
115
|
/**
|
|
86
116
|
* 创建 TextIn 识别脱敏器。凭证在 create 时一次绑定,之后 mask 只传业务参数。
|
|
87
|
-
*
|
|
117
|
+
* 同一敏感词出现多次时逐处遮盖、逐处登记 mapping;跨行命中拆成多个框,仍登记为一个实体。
|
|
88
118
|
*/
|
|
89
119
|
export const createTextInMasker = (options) => {
|
|
90
120
|
const credentials = z.object({
|
|
@@ -178,20 +208,18 @@ export const createTextInMasker = (options) => {
|
|
|
178
208
|
const counters = new Map();
|
|
179
209
|
for (const word of requested) {
|
|
180
210
|
let located = 0;
|
|
181
|
-
for (const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const locatedLine = options.requireAllMatches && line.char_positions?.length !== line.text.length
|
|
189
|
-
? { ...line, char_positions: undefined } : line;
|
|
190
|
-
const quad = locateWordQuad(locatedLine, hit, word.text.length);
|
|
211
|
+
for (const spans of locateWordSpans(lines, recognizedText, word.text)) {
|
|
212
|
+
const occurrenceQuads = [];
|
|
213
|
+
let complete = spans.length > 0;
|
|
214
|
+
for (const span of spans) {
|
|
215
|
+
const locatedLine = options.requireAllMatches && span.line.char_positions?.length !== span.line.text.length
|
|
216
|
+
? { ...span.line, char_positions: undefined } : span.line;
|
|
217
|
+
const quad = locateWordQuad(locatedLine, span.beginIndex, span.length);
|
|
191
218
|
if (quad === undefined) {
|
|
192
219
|
if (options.requireAllMatches)
|
|
193
220
|
throw new AppError('TextIn 脱敏敏感词缺少坐标', 502);
|
|
194
|
-
|
|
221
|
+
complete = false;
|
|
222
|
+
break;
|
|
195
223
|
}
|
|
196
224
|
if (options.requireAllMatches) {
|
|
197
225
|
const scaled = scaleQuadToImage(quad, scaleX, scaleY);
|
|
@@ -208,8 +236,11 @@ export const createTextInMasker = (options) => {
|
|
|
208
236
|
throw new AppError('TextIn 脱敏坐标无效', 502);
|
|
209
237
|
}
|
|
210
238
|
}
|
|
239
|
+
occurrenceQuads.push(clampQuad(scaleQuad(scaleQuadToImage(quad, scaleX, scaleY), SAFETY_SCALE), imageWidth, imageHeight));
|
|
240
|
+
}
|
|
241
|
+
if (complete && occurrenceQuads.length > 0) {
|
|
211
242
|
located++;
|
|
212
|
-
finalQuads.push(
|
|
243
|
+
finalQuads.push(...occurrenceQuads);
|
|
213
244
|
const count = (counters.get(word.target) ?? 0) + 1;
|
|
214
245
|
counters.set(word.target, count);
|
|
215
246
|
mappings.push({
|
|
@@ -220,7 +251,7 @@ export const createTextInMasker = (options) => {
|
|
|
220
251
|
}
|
|
221
252
|
}
|
|
222
253
|
if (options.requireAllMatches && located === 0) {
|
|
223
|
-
throw new AppError('TextIn
|
|
254
|
+
throw new AppError('TextIn 脱敏敏感词无法定位', 502);
|
|
224
255
|
}
|
|
225
256
|
}
|
|
226
257
|
const overlay = polygonSvg(imageWidth, imageHeight, finalQuads);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onco-foundry/mask-port",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"agent-lattice": "0.25.0",
|
|
17
17
|
"sharp": "^0.35.3",
|
|
18
18
|
"zod": "^4.4.3",
|
|
19
|
-
"@onco-foundry/capability-registry": "0.1.1",
|
|
20
19
|
"@onco-foundry/errors": "0.1.1",
|
|
20
|
+
"@onco-foundry/capability-registry": "0.1.1",
|
|
21
21
|
"@onco-foundry/trace-port": "0.3.2",
|
|
22
22
|
"@onco-foundry/resource-versioning": "0.3.0"
|
|
23
23
|
},
|