Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Satini_pvduc
/
ocrpdf
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
tien_nemo
2025-08-12 11:32:04 +0700
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
37bc9da9d249b91ad9fcff704509307cfaf98cd4
37bc9da9
1 parent
a8852132
demo
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
367 additions
and
357 deletions
select.html
select.html
View file @
37bc9da
<html
lang=
"en"
><head>
<meta
charset=
"UTF-8"
>
<title>
OCR Mapping with Manual Select Tool
</title>
<script
src=
"https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"
></script>
<style>
body
{
font-family
:
sans-serif
;
background
:
#f5f5f5
;
}
#app
{
display
:
flex
;
gap
:
20px
;
padding
:
20px
;
}
.left-panel
{
width
:
500px
;
background
:
#fff
;
padding
:
15px
;
border-radius
:
8px
;
box-shadow
:
0
0
5px
rgba
(
0
,
0
,
0
,
0.1
);
}
.form-group
{
margin-bottom
:
15px
;
}
.form-group
label
{
font-weight
:
bold
;
display
:
block
;
margin-bottom
:
5px
;
}
.form-group
input
{
width
:
100%
;
padding
:
6px
;
border
:
1px
solid
#ccc
;
border-radius
:
4px
;
}
.right-panel
{
flex
:
1
;
position
:
relative
;
background
:
#eee
;
border-radius
:
8px
;
overflow
:
hidden
;
user-select
:
none
;
}
.pdf-container
{
position
:
relative
;
display
:
inline-block
;
}
.bbox
{
position
:
absolute
;
border
:
2px
solid
#ff5252
;
/*background-color: rgba(255, 82, 82, 0.2);*/
cursor
:
pointer
;
}
.bbox.active
{
/*border-color: #2196F3;*/
background-color
:
rgb
(
33
243
132
/
30%
);
}
select
{
position
:
absolute
;
z-index
:
10
;
background
:
#fff
;
border
:
1px
solid
#ccc
;
}
.select-box
{
position
:
absolute
;
/*border: 2px dashed #2196F3;*/
background-color
:
rgba
(
33
,
150
,
243
,
0.2
);
pointer-events
:
none
;
z-index
:
5
;
}
.delete-btn
{
position
:
absolute
;
bottom
:
-10px
;
right
:
-10px
;
background
:
#ff4d4d
;
color
:
#fff
;
border
:
none
;
border-radius
:
50%
;
cursor
:
pointer
;
font-size
:
14px
;
padding
:
3px
6px
;
z-index
:
20
;
}
</style>
<meta
charset=
"UTF-8"
>
<title>
OCR Mapping with Manual Select Tool
</title>
<script
src=
"https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"
></script>
<style>
body
{
font-family
:
sans-serif
;
background
:
#f5f5f5
;
}
#app
{
display
:
flex
;
gap
:
20px
;
padding
:
20px
;
}
.left-panel
{
width
:
500px
;
background
:
#fff
;
padding
:
15px
;
border-radius
:
8px
;
box-shadow
:
0
0
5px
rgba
(
0
,
0
,
0
,
0.1
);
}
.form-group
{
margin-bottom
:
15px
;
}
.form-group
label
{
font-weight
:
bold
;
display
:
block
;
margin-bottom
:
5px
;
}
.form-group
input
{
width
:
100%
;
padding
:
6px
;
border
:
1px
solid
#ccc
;
border-radius
:
4px
;
}
.right-panel
{
flex
:
1
;
position
:
relative
;
background
:
#eee
;
border-radius
:
8px
;
overflow
:
hidden
;
user-select
:
none
;
}
.pdf-container
{
position
:
relative
;
display
:
inline-block
;
}
.bbox
{
position
:
absolute
;
border
:
2px
solid
#ff5252
;
/*background-color: rgba(255, 82, 82, 0.2);*/
cursor
:
pointer
;
}
.bbox.active
{
/*border-color: #2196F3;*/
background-color
:
rgb
(
33
243
132
/
30%
);
}
select
{
position
:
absolute
;
z-index
:
10
;
background
:
#fff
;
border
:
1px
solid
#ccc
;
}
.select-box
{
position
:
absolute
;
/*border: 2px dashed #2196F3;*/
background-color
:
rgba
(
33
,
150
,
243
,
0.2
);
pointer-events
:
none
;
z-index
:
5
;
}
.delete-btn
{
position
:
absolute
;
bottom
:
-10px
;
right
:
-10px
;
background
:
#ff4d4d
;
color
:
#fff
;
border
:
none
;
border-radius
:
50%
;
cursor
:
pointer
;
font-size
:
14px
;
padding
:
3px
6px
;
z-index
:
20
;
}
</style>
</head>
<body>
<div
id=
"app"
>
<!-- Right: PDF viewer + select tool -->
<!-- Right: PDF viewer + select tool -->
<div
class=
"right-panel"
>
<div
class=
"pdf-container"
ref=
"pdfContainer"
@
mousedown=
"startSelect"
...
...
@@ -70,49 +70,49 @@
/>
<!-- Vùng kéo chọn -->
<div
v-if=
"selectBox.show"
class=
"select-box"
:style=
"{ left: selectBox.x + 'px', top: selectBox.y + 'px', width: selectBox.width + 'px', height: selectBox.height + 'px' }"
></div>
<!-- Vẽ bbox OCR -->
<div
v-for=
"(item, index) in ocrData"
:key=
"index"
v-if=
"!item.isDeleted"
class=
"bbox"
:class=
"{ active: index === activeIndex }"
:data-field=
"item.field"
:style=
"getBoxStyle(item, index)"
@
click=
"selectingIndex = index"
>
<button
v-if=
"item.isManual && item.showDelete"
class=
"delete-btn"
@
click
.
stop=
"deleteBox(index)"
>
🗑
</button>
</div>
<!-- Dropdown OCR -->
<select
v-if=
"selectingIndex !== null"
:style=
"getSelectStyle(ocrData[selectingIndex])"
v-model=
"ocrData[selectingIndex].field"
@
change=
"applyMapping"
>
<option
disabled
value=
""
>
-- Chọn trường dữ liệu --
</option>
<option
v-for=
"field in fieldOptions"
:value=
"field.value"
>
{{ field.label }}
</option>
</select>
<!-- Dropdown thủ công -->
<select
v-if=
"selectBox.showDropdown"
:style=
"{ left: selectBox.x + 'px', top: (selectBox.y + selectBox.height) + 'px' }"
v-model=
"manualField"
@
change=
"applyManualMapping"
@
click
.
stop
>
<option
disabled
value=
""
>
-- Chọn trường dữ liệu --
</option>
<option
v-for=
"field in fieldOptions"
:value=
"field.value"
>
{{ field.label }}
</option>
</select>
<!-- Vùng kéo chọn -->
<div
v-if=
"selectBox.show"
class=
"select-box"
:style=
"{ left: selectBox.x + 'px', top: selectBox.y + 'px', width: selectBox.width + 'px', height: selectBox.height + 'px' }"
></div>
<!-- Vẽ bbox OCR -->
<div
v-for=
"(item, index) in ocrData"
:key=
"index"
v-if=
"!item.isDeleted"
class=
"bbox"
:class=
"{ active: index === activeIndex }"
:data-field=
"item.field"
:style=
"getBoxStyle(item, index)"
@
click=
"selectingIndex = index"
>
<button
v-if=
"item.isManual && item.showDelete"
class=
"delete-btn"
@
click
.
stop=
"deleteBox(index)"
>
🗑
</button>
</div>
<!-- Dropdown OCR -->
<select
v-if=
"selectingIndex !== null"
:style=
"getSelectStyle(ocrData[selectingIndex])"
v-model=
"ocrData[selectingIndex].field"
@
change=
"applyMapping"
>
<option
disabled
value=
""
>
-- Chọn trường dữ liệu --
</option>
<option
v-for=
"field in fieldOptions"
:value=
"field.value"
>
{{ field.label }}
</option>
</select>
<!-- Dropdown thủ công -->
<select
v-if=
"selectBox.showDropdown"
:style=
"{ left: selectBox.x + 'px', top: (selectBox.y + selectBox.height) + 'px' }"
v-model=
"manualField"
@
change=
"applyManualMapping"
@
click
.
stop
>
<option
disabled
value=
""
>
-- Chọn trường dữ liệu --
</option>
<option
v-for=
"field in fieldOptions"
:value=
"field.value"
>
{{ field.label }}
</option>
</select>
</div>
</div>
</div>
<!-- Left: Form inputs -->
<div
class=
"left-panel"
>
...
...
@@ -126,272 +126,282 @@
<script>
new
Vue
({
el
:
'#app'
,
data
()
{
return
{
pdfImageUrl
:
""
,
selectingIndex
:
null
,
isMappingManually
:
false
,
isSelecting
:
false
,
activeField
:
null
,
manualField
:
""
,
formData
:
{
export_date
:
""
,
order_code
:
""
,
customer
:
""
,
address
:
""
,
staff
:
""
},
fieldOptions
:
[
{
value
:
"export_date"
,
label
:
"Ngày xuất"
},
{
value
:
"order_code"
,
label
:
"Mã đơn hàng"
},
{
value
:
"customer"
,
label
:
"Khách hàng"
},
{
value
:
"address"
,
label
:
"Địa chỉ"
},
{
value
:
"staff"
,
label
:
"Nhân viên"
}
],
ocrData
:
[],
selectBox
:
{
show
:
false
,
showDropdown
:
false
,
x
:
0
,
y
:
0
,
width
:
0
,
height
:
0
,
startX
:
0
,
startY
:
0
},
manualIndex
:
null
}
},
mounted
()
{
this
.
pdfImageUrl
=
"/public/image/data_picking_detail_1754967679.jpg"
;
// ảnh xuất từ Python
this
.
initData
();
},
methods
:
{
deleteBox
(
index
)
{
const
item
=
this
.
ocrData
[
index
];
if
(
item
.
isManual
)
{
const
manualBbox
=
item
.
bbox
;
// Hiện lại border các box OCR gốc nằm trong vùng thủ công
this
.
ocrData
.
forEach
(
o
=>
{
if
(
!
o
.
isManual
&&
this
.
isBoxInside
(
o
.
bbox
,
manualBbox
))
{
o
.
hideBorder
=
false
;
new
Vue
({
el
:
'#app'
,
data
()
{
return
{
pdfImageUrl
:
""
,
selectingIndex
:
null
,
isMappingManually
:
false
,
isSelecting
:
false
,
activeIndex
:
null
,
manualField
:
""
,
formData
:
{
export_date
:
""
,
order_code
:
""
,
customer
:
""
,
address
:
""
,
staff
:
""
},
fieldOptions
:
[
{
value
:
"export_date"
,
label
:
"Ngày xuất"
},
{
value
:
"order_code"
,
label
:
"Mã đơn hàng"
},
{
value
:
"customer"
,
label
:
"Khách hàng"
},
{
value
:
"address"
,
label
:
"Địa chỉ"
},
{
value
:
"staff"
,
label
:
"Nhân viên"
}
],
ocrData
:
[],
selectBox
:
{
show
:
false
,
showDropdown
:
false
,
x
:
0
,
y
:
0
,
width
:
0
,
height
:
0
,
startX
:
0
,
startY
:
0
},
manualIndex
:
null
}
},
mounted
()
{
this
.
pdfImageUrl
=
"/public/image/data_picking_detail_1754967679.jpg"
;
// ảnh xuất từ Python
this
.
initData
();
},
methods
:
{
deleteBox
(
index
)
{
const
item
=
this
.
ocrData
[
index
];
if
(
item
.
isManual
)
{
const
manualBbox
=
item
.
bbox
;
// Hiện lại border các box OCR gốc nằm trong vùng thủ công
this
.
ocrData
.
forEach
(
o
=>
{
if
(
!
o
.
isManual
&&
this
.
isBoxInside
(
o
.
bbox
,
manualBbox
))
{
o
.
hideBorder
=
false
;
}
});
// Đánh dấu xoá vùng thủ công
this
.
ocrData
[
index
].
isDeleted
=
true
;
this
.
ocrData
[
index
].
showDelete
=
false
;
// Reset trạng thái nếu đây là vùng đang chọn
if
(
this
.
manualIndex
===
index
)
{
this
.
isMappingManually
=
false
;
this
.
selectBox
.
show
=
false
;
this
.
selectBox
.
showDropdown
=
false
;
this
.
manualField
=
""
;
this
.
manualIndex
=
null
;
}
}
},
async
initData
()
{
await
this
.
loadOCRData
();
},
async
loadOCRData
()
{
const
res
=
await
fetch
(
"/public/image/data_picking_detail_1754967679.json"
);
this
.
ocrData
=
await
res
.
json
();
},
onImageLoad
()
{
const
img
=
this
.
$refs
.
pdfImage
;
this
.
imageWidth
=
img
.
naturalWidth
;
this
.
imageHeight
=
img
.
naturalHeight
;
},
getBoxStyle
(
item
,
index
)
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
||
!
this
.
$refs
.
pdfImage
)
return
{};
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
displayedWidth
/
this
.
imageWidth
;
const
scaleY
=
displayedHeight
/
this
.
imageHeight
;
return
{
position
:
'absolute'
,
left
:
`
${
Math
.
round
(
x1
*
scaleX
)}
px`
,
top
:
`
${
Math
.
round
(
y1
*
scaleY
)}
px`
,
width
:
`
${
Math
.
round
((
x2
-
x1
)
*
scaleX
)}
px`
,
height
:
`
${
Math
.
round
((
y2
-
y1
)
*
scaleY
)}
px`
,
border
:
item
.
hideBorder
?
'none'
:
'2px solid '
+
(
index
===
this
.
activeIndex
?
'#199601'
:
'#ff5252'
),
//backgroundColor: item.hideBorder ? 'transparent' : (this.activeIndex === item.field ? 'rgba(33,150,243,0.3)' : 'rgba(255,82,82,0.2)'),
boxSizing
:
'border-box'
,
cursor
:
'pointer'
,
zIndex
:
item
.
isManual
?
30
:
10
};
},
highlightField
(
field
)
{
// tìm box gần nhất match field này
let
idx
=
-
1
;
for
(
let
i
=
this
.
ocrData
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
it
=
this
.
ocrData
[
i
];
if
(
!
it
.
isDeleted
&&
it
.
field
===
field
)
{
idx
=
i
;
break
;
}
}
this
.
activeIndex
=
idx
;
// nếu không tìm thấy thì = -1
},
startSelect
(
e
)
{
if
(
this
.
isMappingManually
||
e
.
button
!==
0
)
return
;
this
.
isSelecting
=
true
;
const
rect
=
this
.
$refs
.
pdfContainer
.
getBoundingClientRect
();
this
.
selectBox
.
startX
=
e
.
clientX
-
rect
.
left
;
this
.
selectBox
.
startY
=
e
.
clientY
-
rect
.
top
;
this
.
selectBox
.
x
=
this
.
selectBox
.
startX
;
this
.
selectBox
.
y
=
this
.
selectBox
.
startY
;
this
.
selectBox
.
width
=
0
;
this
.
selectBox
.
height
=
0
;
this
.
selectBox
.
show
=
true
;
this
.
selectBox
.
showDropdown
=
false
;
this
.
manualField
=
""
;
},
onSelect
(
e
)
{
if
(
!
this
.
isSelecting
)
return
;
const
rect
=
this
.
$refs
.
pdfContainer
.
getBoundingClientRect
();
const
currentX
=
e
.
clientX
-
rect
.
left
;
const
currentY
=
e
.
clientY
-
rect
.
top
;
this
.
selectBox
.
x
=
Math
.
min
(
currentX
,
this
.
selectBox
.
startX
);
this
.
selectBox
.
y
=
Math
.
min
(
currentY
,
this
.
selectBox
.
startY
);
this
.
selectBox
.
width
=
Math
.
abs
(
currentX
-
this
.
selectBox
.
startX
);
this
.
selectBox
.
height
=
Math
.
abs
(
currentY
-
this
.
selectBox
.
startY
);
},
endSelect
(
e
)
{
if
(
!
this
.
isSelecting
)
return
;
this
.
isSelecting
=
false
;
if
(
this
.
selectBox
.
width
<
10
||
this
.
selectBox
.
height
<
10
)
{
this
.
selectBox
.
show
=
false
;
return
;
}
// displayed coords (như hiện tại, dùng để hiển thị select overlay)
const
dispX1
=
this
.
selectBox
.
x
;
const
dispY1
=
this
.
selectBox
.
y
;
const
dispX2
=
this
.
selectBox
.
x
+
this
.
selectBox
.
width
;
const
dispY2
=
this
.
selectBox
.
y
+
this
.
selectBox
.
height
;
// scale: displayed -> original
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
this
.
imageWidth
/
displayedWidth
;
const
scaleY
=
this
.
imageHeight
/
displayedHeight
;
// bbox ở hệ gốc (original image pixels) — dùng để so sánh với ocrData và lưu vào ocrData
const
origBbox
=
[
Math
.
round
(
dispX1
*
scaleX
),
Math
.
round
(
dispY1
*
scaleY
),
Math
.
round
(
dispX2
*
scaleX
),
Math
.
round
(
dispY2
*
scaleY
)
];
// Ẩn border các box OCR gốc nằm giao nhau với vùng thủ công (dùng coords gốc)
this
.
ocrData
.
forEach
(
item
=>
{
if
(
!
item
.
isManual
&&
this
.
isBoxInside
(
item
.
bbox
,
origBbox
))
{
item
.
hideBorder
=
true
;
}
});
// Thêm box thủ công (lưu theo coords gốc)
this
.
ocrData
.
push
({
text
:
""
,
bbox
:
origBbox
,
field
:
""
,
isManual
:
true
,
showDelete
:
true
,
isDeleted
:
false
,
hideBorder
:
false
});
this
.
manualIndex
=
this
.
ocrData
.
length
-
1
;
this
.
isMappingManually
=
true
;
this
.
selectBox
.
showDropdown
=
true
;
e
.
stopPropagation
();
e
.
preventDefault
();
}
,
applyMapping
()
{
const
item
=
this
.
ocrData
[
this
.
selectingIndex
];
if
(
item
&&
item
.
isManual
)
{
this
.
manualIndex
=
this
.
selectingIndex
;
this
.
manualField
=
item
.
field
||
""
;
this
.
applyManualMapping
();
return
;
}
if
(
item
.
field
)
{
this
.
formData
[
item
.
field
]
=
item
.
text
;
this
.
activeIndex
=
this
.
selectingIndex
;
}
this
.
selectingIndex
=
null
;
},
applyManualMapping
()
{
if
(
!
this
.
manualField
)
return
;
const
manualIndex
=
this
.
manualIndex
;
const
newBbox
=
this
.
ocrData
[
manualIndex
].
bbox
;
let
combinedText
=
[];
this
.
ocrData
.
forEach
(
item
=>
{
if
(
!
item
.
isManual
&&
this
.
isBoxInside
(
item
.
bbox
,
newBbox
)
&&
item
.
text
.
trim
())
{
const
partial
=
this
.
getPartialText
(
item
.
text
,
item
.
bbox
,
newBbox
);
if
(
partial
)
combinedText
.
push
(
partial
);
// combinedText.push(item.text.trim());
}
});
const
finalText
=
combinedText
.
join
(
" "
);
this
.
ocrData
[
manualIndex
].
field
=
this
.
manualField
;
this
.
formData
[
this
.
manualField
]
=
finalText
;
this
.
activeIndex
=
manualIndex
;
console
.
log
(
'manualField'
,
this
.
manualField
,
this
.
manualIndex
)
// Reset trạng thái chọn
this
.
isMappingManually
=
false
;
this
.
selectBox
.
show
=
false
;
this
.
selectBox
.
showDropdown
=
false
;
// this.manualField = "";
// this.manualIndex = null;
},
isBoxInside
(
inner
,
outer
)
{
return
!
(
inner
[
2
]
<
outer
[
0
]
||
// box bên trái vùng chọn
inner
[
0
]
>
outer
[
2
]
||
// box bên phải vùng chọn
inner
[
3
]
<
outer
[
1
]
||
// box phía trên vùng chọn
inner
[
1
]
>
outer
[
3
]
// box phía dưới vùng chọn
);
},
getPartialText
(
text
,
bbox
,
selectBbox
)
{
const
[
x1
,
y1
,
x2
,
y2
]
=
bbox
;
const
[
sx1
,
sy1
,
sx2
,
sy2
]
=
selectBbox
;
// Chiều rộng box OCR
const
boxWidth
=
x2
-
x1
;
// Vị trí start và end tương đối trong text
let
startRatio
=
Math
.
max
(
0
,
(
sx1
-
x1
)
/
boxWidth
);
let
endRatio
=
Math
.
min
(
1
,
(
sx2
-
x1
)
/
boxWidth
);
const
startIndex
=
Math
.
floor
(
startRatio
*
text
.
length
);
const
endIndex
=
Math
.
ceil
(
endRatio
*
text
.
length
);
return
text
.
substring
(
startIndex
,
endIndex
).
trim
();
},
getSelectStyle
(
item
)
{
if
(
!
this
.
imageWidth
)
return
{
position
:
'absolute'
};
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
displayedWidth
/
this
.
imageWidth
;
const
scaleY
=
displayedHeight
/
this
.
imageHeight
;
return
{
position
:
'absolute'
,
left
:
`
${
Math
.
round
(
x1
*
scaleX
)}
px`
,
top
:
`
${
Math
.
round
(
y2
*
scaleY
)}
px`
,
zIndex
:
9999
};
}
});
// Đánh dấu xoá vùng thủ công
this
.
ocrData
[
index
].
isDeleted
=
true
;
this
.
ocrData
[
index
].
showDelete
=
false
;
// Reset trạng thái nếu đây là vùng đang chọn
if
(
this
.
manualIndex
===
index
)
{
this
.
isMappingManually
=
false
;
this
.
selectBox
.
show
=
false
;
this
.
selectBox
.
showDropdown
=
false
;
this
.
manualField
=
""
;
this
.
manualIndex
=
null
;
}
}
},
async
initData
()
{
await
this
.
loadOCRData
();
},
async
loadOCRData
()
{
const
res
=
await
fetch
(
"/public/image/data_picking_detail_1754967679.json"
);
this
.
ocrData
=
await
res
.
json
();
},
onImageLoad
()
{
const
img
=
this
.
$refs
.
pdfImage
;
this
.
imageWidth
=
img
.
naturalWidth
;
this
.
imageHeight
=
img
.
naturalHeight
;
},
getBoxStyle
(
item
)
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
||
!
this
.
$refs
.
pdfImage
)
return
{};
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
displayedWidth
/
this
.
imageWidth
;
const
scaleY
=
displayedHeight
/
this
.
imageHeight
;
return
{
position
:
'absolute'
,
left
:
`
${
Math
.
round
(
x1
*
scaleX
)}
px`
,
top
:
`
${
Math
.
round
(
y1
*
scaleY
)}
px`
,
width
:
`
${
Math
.
round
((
x2
-
x1
)
*
scaleX
)}
px`
,
height
:
`
${
Math
.
round
((
y2
-
y1
)
*
scaleY
)}
px`
,
border
:
item
.
hideBorder
?
'none'
:
'2px solid '
+
(
this
.
activeField
===
item
.
field
?
'#199601'
:
'#ff5252'
),
//backgroundColor: item.hideBorder ? 'transparent' : (this.activeField === item.field ? 'rgba(33,150,243,0.3)' : 'rgba(255,82,82,0.2)'),
boxSizing
:
'border-box'
,
cursor
:
'pointer'
,
zIndex
:
item
.
isManual
?
30
:
10
};
},
highlightField
(
field
)
{
this
.
activeField
=
field
;
},
startSelect
(
e
)
{
if
(
this
.
isMappingManually
||
e
.
button
!==
0
)
return
;
this
.
isSelecting
=
true
;
const
rect
=
this
.
$refs
.
pdfContainer
.
getBoundingClientRect
();
this
.
selectBox
.
startX
=
e
.
clientX
-
rect
.
left
;
this
.
selectBox
.
startY
=
e
.
clientY
-
rect
.
top
;
this
.
selectBox
.
x
=
this
.
selectBox
.
startX
;
this
.
selectBox
.
y
=
this
.
selectBox
.
startY
;
this
.
selectBox
.
width
=
0
;
this
.
selectBox
.
height
=
0
;
this
.
selectBox
.
show
=
true
;
this
.
selectBox
.
showDropdown
=
false
;
this
.
manualField
=
""
;
},
onSelect
(
e
)
{
if
(
!
this
.
isSelecting
)
return
;
const
rect
=
this
.
$refs
.
pdfContainer
.
getBoundingClientRect
();
const
currentX
=
e
.
clientX
-
rect
.
left
;
const
currentY
=
e
.
clientY
-
rect
.
top
;
this
.
selectBox
.
x
=
Math
.
min
(
currentX
,
this
.
selectBox
.
startX
);
this
.
selectBox
.
y
=
Math
.
min
(
currentY
,
this
.
selectBox
.
startY
);
this
.
selectBox
.
width
=
Math
.
abs
(
currentX
-
this
.
selectBox
.
startX
);
this
.
selectBox
.
height
=
Math
.
abs
(
currentY
-
this
.
selectBox
.
startY
);
},
endSelect
(
e
)
{
if
(
!
this
.
isSelecting
)
return
;
this
.
isSelecting
=
false
;
if
(
this
.
selectBox
.
width
<
10
||
this
.
selectBox
.
height
<
10
)
{
this
.
selectBox
.
show
=
false
;
return
;
}
// displayed coords (như hiện tại, dùng để hiển thị select overlay)
const
dispX1
=
this
.
selectBox
.
x
;
const
dispY1
=
this
.
selectBox
.
y
;
const
dispX2
=
this
.
selectBox
.
x
+
this
.
selectBox
.
width
;
const
dispY2
=
this
.
selectBox
.
y
+
this
.
selectBox
.
height
;
// scale: displayed -> original
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
this
.
imageWidth
/
displayedWidth
;
const
scaleY
=
this
.
imageHeight
/
displayedHeight
;
// bbox ở hệ gốc (original image pixels) — dùng để so sánh với ocrData và lưu vào ocrData
const
origBbox
=
[
Math
.
round
(
dispX1
*
scaleX
),
Math
.
round
(
dispY1
*
scaleY
),
Math
.
round
(
dispX2
*
scaleX
),
Math
.
round
(
dispY2
*
scaleY
)
];
// Ẩn border các box OCR gốc nằm giao nhau với vùng thủ công (dùng coords gốc)
this
.
ocrData
.
forEach
(
item
=>
{
if
(
!
item
.
isManual
&&
this
.
isBoxInside
(
item
.
bbox
,
origBbox
))
{
item
.
hideBorder
=
true
;
}
});
// Thêm box thủ công (lưu theo coords gốc)
this
.
ocrData
.
push
({
text
:
""
,
bbox
:
origBbox
,
field
:
""
,
isManual
:
true
,
showDelete
:
true
,
isDeleted
:
false
,
hideBorder
:
false
});
this
.
manualIndex
=
this
.
ocrData
.
length
-
1
;
this
.
isMappingManually
=
true
;
this
.
selectBox
.
showDropdown
=
true
;
e
.
stopPropagation
();
e
.
preventDefault
();
}
,
applyMapping
()
{
const
item
=
this
.
ocrData
[
this
.
selectingIndex
];
if
(
item
&&
item
.
isManual
)
{
this
.
manualIndex
=
this
.
selectingIndex
;
this
.
manualField
=
item
.
field
;
// đảm bảo sync field hiện tại
this
.
applyManualMapping
();
return
;
}
if
(
item
.
field
)
{
this
.
formData
[
item
.
field
]
=
item
.
text
;
this
.
activeField
=
item
.
field
;
}
this
.
selectingIndex
=
null
;
},
applyManualMapping
()
{
if
(
!
this
.
manualField
)
return
;
const
manualIndex
=
this
.
manualIndex
;
const
newBbox
=
this
.
ocrData
[
manualIndex
].
bbox
;
let
combinedText
=
[];
this
.
ocrData
.
forEach
(
item
=>
{
if
(
!
item
.
isManual
&&
this
.
isBoxInside
(
item
.
bbox
,
newBbox
)
&&
item
.
text
.
trim
())
{
const
partial
=
this
.
getPartialText
(
item
.
text
,
item
.
bbox
,
newBbox
);
if
(
partial
)
combinedText
.
push
(
partial
);
// combinedText.push(item.text.trim());
}
});
const
finalText
=
combinedText
.
join
(
" "
);
this
.
ocrData
[
manualIndex
].
field
=
this
.
manualField
;
this
.
formData
[
this
.
manualField
]
=
finalText
;
this
.
activeField
=
this
.
manualField
;
console
.
log
(
'manualField'
,
this
.
manualField
,
this
.
manualIndex
)
// Reset trạng thái chọn
this
.
isMappingManually
=
false
;
this
.
selectBox
.
show
=
false
;
this
.
selectBox
.
showDropdown
=
false
;
// this.manualField = "";
// this.manualIndex = null;
},
isBoxInside
(
inner
,
outer
)
{
return
!
(
inner
[
2
]
<
outer
[
0
]
||
// box bên trái vùng chọn
inner
[
0
]
>
outer
[
2
]
||
// box bên phải vùng chọn
inner
[
3
]
<
outer
[
1
]
||
// box phía trên vùng chọn
inner
[
1
]
>
outer
[
3
]
// box phía dưới vùng chọn
);
},
getPartialText
(
text
,
bbox
,
selectBbox
)
{
const
[
x1
,
y1
,
x2
,
y2
]
=
bbox
;
const
[
sx1
,
sy1
,
sx2
,
sy2
]
=
selectBbox
;
// Chiều rộng box OCR
const
boxWidth
=
x2
-
x1
;
// Vị trí start và end tương đối trong text
let
startRatio
=
Math
.
max
(
0
,
(
sx1
-
x1
)
/
boxWidth
);
let
endRatio
=
Math
.
min
(
1
,
(
sx2
-
x1
)
/
boxWidth
);
const
startIndex
=
Math
.
floor
(
startRatio
*
text
.
length
);
const
endIndex
=
Math
.
ceil
(
endRatio
*
text
.
length
);
return
text
.
substring
(
startIndex
,
endIndex
).
trim
();
},
getSelectStyle
(
item
)
{
if
(
!
this
.
imageWidth
)
return
{
position
:
'absolute'
};
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
displayedWidth
/
this
.
imageWidth
;
const
scaleY
=
displayedHeight
/
this
.
imageHeight
;
return
{
position
:
'absolute'
,
left
:
`
${
Math
.
round
(
x1
*
scaleX
)}
px`
,
top
:
`
${
Math
.
round
(
y2
*
scaleY
)}
px`
,
zIndex
:
9999
};
}
}
});
});
</script>
</body></html>
</body>
</html>
...
...
Please
register
or
sign in
to post a comment