2023-07-22 13:17:24 +01:00
<!DOCTYPE html>
2024-10-29 15:56:45 +00:00
< html th:lang = "${#locale.language}" th:dir = "#{language.direction}" th:data-language = "${#locale.toString()}"
xmlns:th = "https://www.thymeleaf.org" >
< head >
2024-02-16 22:49:06 +01:00
< th:block th:insert = "~{fragments/common :: head(title=#{compare.title}, header=#{compare.header})}" ></ th:block >
2024-10-29 15:56:45 +00:00
< style >
. result-column {
border : 1 px solid #ccc ;
padding : 15 px ;
margin-bottom : 15 px ;
overflow-y : auto ;
height : calc ( 100 vh - 400 px );
white-space : pre-wrap ;
}
2024-02-11 11:47:00 -05:00
2024-10-29 15:56:45 +00:00
. flex-container {
display : flex ;
flex-direction : row ;
}
2024-07-04 17:11:41 -04:00
2024-10-29 15:56:45 +00:00
. color-selector {
display : flex ;
flex-direction : row ;
align-items : center ;
width : 50 % ;
max-height : 100 px ;
margin-bottom : 2 rem ;
}
2024-07-04 17:11:41 -04:00
2024-10-29 15:56:45 +00:00
# color-box1 ,
# color-box2 {
-webkit- appearance : none ;
-moz- appearance : none ;
appearance : none ;
border : none ;
background-color : transparent ;
}
2024-07-04 17:11:41 -04:00
2024-10-29 15:56:45 +00:00
. spacer1 {
padding-right : calc ( var ( -- bs - gutter - x ) * .5 );
}
2024-02-16 22:49:06 +01:00
2024-10-29 15:56:45 +00:00
. spacer2 {
padding-left : calc ( var ( -- bs - gutter - x ) * .5 );
}
</ style >
</ head >
2024-02-16 22:49:06 +01:00
2024-10-29 15:56:45 +00:00
< body >
< div id = "page-container" >
< div id = "content-wrap" >
< th:block th:insert = "~{fragments/navbar.html :: navbar}" ></ th:block >
< br >< br >
< div class = "container" >
< div class = "row justify-content-center" >
< div class = "col-md-9 bg-card" >
< div class = "tool-header" >
< span class = "material-symbols-rounded tool-header-icon other" > compare</ span >
< span class = "tool-header-text" th:text = "#{compare.header}" ></ span >
2023-07-22 13:17:24 +01:00
</ div >
2024-10-29 15:56:45 +00:00
< div
th:replace = "~{fragments/common :: fileSelector(name='fileInput', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf', remoteCall='false')}" >
</ div >
< div
th:replace = "~{fragments/common :: fileSelector(name='fileInput2', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf', remoteCall='false')}" >
</ div >
< div class = "row" >
< div class = "flex-container" >
< div class = "color-selector spacer1" >
< label th:text = "#{compare.highlightColor.1}" ></ label >
< label for = "color-box1" ></ label >< input type = "color" id = "color-box1" value = "#ff0000" >
</ div >
< div class = "color-selector spacer2" >
< label th:text = "#{compare.highlightColor.2}" ></ label >
< label for = "color-box2" ></ label >< input type = "color" id = "color-box2" value = "#008000" >
</ div >
</ div >
</ div >
< button class = "btn btn-primary" onclick = "comparePDFs()" th:text = "#{compare.submit}" ></ button >
< div class = "row" >
< div class = "col-md-6" >
< h3 th:text = "#{compare.document.1}" ></ h3 >
< div id = "result1" class = "result-column" ></ div >
</ div >
< div class = "col-md-6" >
< h3 th:text = "#{compare.document.2}" ></ h3 >
< div id = "result2" class = "result-column" ></ div >
</ div >
</ div >
< script type = "module" th:src = "@{'/pdfjs-legacy/pdf.mjs'}" ></ script >
< script th:inline = "javascript" >
// get the elements
var result1 = document . getElementById ( 'result1' );
var result2 = document . getElementById ( 'result2' );
// add event listeners
result1 . addEventListener ( 'scroll' , function () {
result2 . scrollTop = result1 . scrollTop ;
});
result2 . addEventListener ( 'scroll' , function () {
result1 . scrollTop = result2 . scrollTop ;
});
async function comparePDFs () {
const file1 = document . getElementById ( "fileInput-input" ). files [ 0 ];
const file2 = document . getElementById ( "fileInput2-input" ). files [ 0 ];
var color1 = document . getElementById ( 'color-box1' ). value ;
var color2 = document . getElementById ( 'color-box2' ). value ;
const complexMessage = /*[[#{compare.complex.message}]]*/ 'One or both of the provided documents are large files, accuracy of comparison may be reduced' ;
const largeFilesMessage = /*[[#{compare.large.file.message}]]*/ 'One or Both of the provided documents are too large to process' ;
const noTextMessage = /*[[#{compare.no.text.message}]]*/ 'One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison."' ;
if ( ! file1 || ! file2 ) {
console . error ( "Please select two PDF files to compare" );
return ;
}
pdfjsLib . GlobalWorkerOptions . workerSrc = './pdfjs-legacy/pdf.worker.mjs' ;
const [ pdf1 , pdf2 ] = await Promise . all ([
pdfjsLib . getDocument ( URL . createObjectURL ( file1 )). promise ,
pdfjsLib . getDocument ( URL . createObjectURL ( file2 )). promise
]);
const extractText = async ( pdf ) => {
const pages = [];
for ( let i = 1 ; i <= pdf . numPages ; i ++ ) {
const page = await pdf . getPage ( i );
const content = await page . getTextContent ();
const strings = content . items . map ( item => item . str );
pages . push ( strings . join ( " " ));
}
return pages . join ( " " );
};
const [ text1 , text2 ] = await Promise . all ([
extractText ( pdf1 ),
extractText ( pdf2 )
]);
if ( text1 . trim () === "" || text2 . trim () === "" ) {
alert ( noTextMessage );
return ;
}
const resultDiv1 = document . getElementById ( "result1" );
const resultDiv2 = document . getElementById ( "result2" );
const loading = /*[[#{loading}]]*/ 'Loading...' ;
resultDiv1 . innerHTML = loading ;
resultDiv2 . innerHTML = loading ;
// Create a new Worker
2024-11-29 15:11:59 +00:00
const worker = new Worker ( './js/compare/pdfWorker.js' );
2024-10-29 15:56:45 +00:00
// Post messages to the worker
worker . postMessage ({
type : 'SET_COMPLEX_MESSAGE' ,
message : complexMessage
});
worker . postMessage ({
type : 'SET_TOO_LARGE_MESSAGE' ,
message : largeFilesMessage
});
// Error handling for the worker
worker . onerror = function ( error ) {
console . error ( 'Worker error:' , error );
};
worker . onmessage = function ( e ) {
const { status , differences , message } = e . data ;
if ( status === 'error' ) {
resultDiv1 . innerHTML = '' ;
resultDiv2 . innerHTML = '' ;
alert ( message );
return ;
}
if ( status === 'success' && differences ) {
console . log ( 'Differences:' , differences );
displayDifferences ( differences );
}
if ( event . data . status === 'warning' ) {
console . warn ( event . data . message );
alert ( event . data . message );
}
};
worker . postMessage ({ text1 , text2 , color1 , color2 });
const displayDifferences = ( differences ) => {
const resultDiv1 = document . getElementById ( "result1" );
const resultDiv2 = document . getElementById ( "result2" );
resultDiv1 . innerHTML = "" ;
resultDiv2 . innerHTML = "" ;
differences . forEach (([ color , word ]) => {
const span1 = document . createElement ( "span" );
const span2 = document . createElement ( "span" );
if ( color === color2 ) {
span1 . style . color = "transparent" ;
span1 . style . userSelect = "none" ;
span2 . style . color = color ;
}
// If it's a deletion, show it in in the first document and transparent in the second
else if ( color === color1 ) {
span1 . style . color = color ;
span2 . style . color = "transparent" ;
span2 . style . userSelect = "none" ;
}
// If it's unchanged, show it in black in both
else {
span1 . style . color = color ;
span2 . style . color = color ;
}
span1 . textContent = word ;
span2 . textContent = word ;
resultDiv1 . appendChild ( span1 );
resultDiv2 . appendChild ( span2 );
// Add space after each word, or a new line if the word ends with a full stop
const spaceOrNewline1 = document . createElement ( "span" );
const spaceOrNewline2 = document . createElement ( "span" );
if ( word . endsWith ( "." )) {
spaceOrNewline1 . innerHTML = "<br>" ;
spaceOrNewline2 . innerHTML = "<br>" ;
} else {
spaceOrNewline1 . textContent = " " ;
spaceOrNewline2 . textContent = " " ;
}
resultDiv1 . appendChild ( spaceOrNewline1 );
resultDiv2 . appendChild ( spaceOrNewline2 );
});
};
}
</ script >
2024-02-16 22:49:06 +01:00
</ div >
2023-07-22 13:17:24 +01:00
</ div >
2024-02-16 22:49:06 +01:00
</ div >
2024-02-11 12:14:21 -05:00
</ div >
2024-10-29 15:56:45 +00:00
< th:block th:insert = "~{fragments/footer.html :: footer}" ></ th:block >
</ div >
</ body >
2024-03-21 21:58:01 +01:00
</ html >