%PDF-1.4 %Óëéá 1 0 obj <> endobj 3 0 obj <> endobj 4 0 obj <
| Server IP : 212.252.79.165 / Your IP : 216.73.217.172 [ Web Server : Apache System : Linux 212-252-79-165.cprapid.com 5.15.0-153-generic #163-Ubuntu SMP Thu Aug 7 16:37:18 UTC 2025 x86_64 User : cehaburo ( 1001) PHP Version : 8.1.33 Disable Function : exec,passthru,shell_exec,system Domains : 48 Domains MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /home/cehaburo/www/pdfjs/samples/advanced/text-compare/ |
Upload File : |
// @link WebViewerInstance: https://www.pdftron.com/api/web/WebViewerInstance.html
/* global Diff */
const compareViewer = [
{
initialDoc: '../../files/text-compare_1.pdf',
domElement: 'leftPanel',
diffPanel: 'compareLeftPanel',
instance: null,
displayingWebViewer: true,
filenameBtn: document.querySelector('#toggleLeftBtn'),
pageTextSections: null,
highlightColor: '#e74c3c',
backgroundColor: '#f9e6f0',
textColor: '#f9e79f',
searchTerm: '',
searchResult: [],
searchResultIndex: 0,
},
{
initialDoc: '../../files/text-compare_2.pdf',
domElement: 'rightPanel',
diffPanel: 'compareRightPanel',
instance: null,
displayingWebViewer: false,
pageTextSections: null,
filenameBtn: document.querySelector('#toggleRightBtn'),
highlightColor: '#45b39d',
backgroundColor: '#b3f9c6',
textColor: '#ebf5fb',
searchTerm: '',
searchResult: [],
searchResultIndex: 0,
},
];
const leftPanelIndex = 0;
const rightPanelIndex = 1;
let maxPageCount = 1;
let scrollTimeout;
let pageChangeTimeout;
Core.getDefaultBackendType().then(async pdfType => {
await Promise.all([initializeWebViewer(compareViewer[leftPanelIndex]), initializeWebViewer(compareViewer[rightPanelIndex])]);
maxPageCount = Math.min(compareViewer[leftPanelIndex].instance.Core.documentViewer.getPageCount(), compareViewer[rightPanelIndex].instance.Core.documentViewer.getPageCount());
compareViewer[leftPanelIndex].instance.Core.documentViewer.setCurrentPage(1);
compareViewer[rightPanelIndex].instance.Core.documentViewer.setCurrentPage(1);
document.querySelector('#totalPage').textContent = maxPageCount;
document.querySelector('#currentPage').setAttribute('max', maxPageCount);
document.querySelector('#currentPage').value = 1;
compareViewer[leftPanelIndex].filenameBtn.value = compareViewer[leftPanelIndex].instance.Core.documentViewer.getDocument().filename;
compareViewer[rightPanelIndex].filenameBtn.value = compareViewer[rightPanelIndex].instance.Core.documentViewer.getDocument().filename;
compareText(1);
});
const initializeWebViewer = viewer => {
return new Promise(resolve => {
WebViewer(
{
path: '../../../lib',
initialDoc: viewer.initialDoc,
disabledElements: ['toggleNotesButton', 'pageNavOverlay', 'searchButton'],
},
document.getElementById(`${viewer.domElement}`)
).then(instance => {
const { documentViewer } = instance.Core;
const Core = instance.Core;
const { Feature, FitMode } = instance.UI;
viewer.instance = instance;
// disable tools and editing annotations
instance.UI.disableTools();
instance.UI.disableFeatures(Feature.Annotations);
documentViewer.addEventListener('documentLoaded', async () => {
const displayModeManager = documentViewer.getDisplayModeManager();
const displayMode = displayModeManager.isVirtualDisplayEnabled()
? new Core.VirtualDisplayMode(documentViewer, Core.DisplayModes.Single)
: new Core.DisplayMode(documentViewer, Core.DisplayModes.Single);
displayModeManager.setDisplayMode(displayMode);
instance.UI.setFitMode(FitMode.FitWidth);
resolve(instance);
});
documentViewer.addEventListener('pageNumberUpdated', pageNumber => {
if (pageNumber > maxPageCount) {
documentViewer.setCurrentPage(maxPageCount);
return;
}
document.querySelector('#currentPage').value = pageNumber;
// update compare when page change, use debouncing to limit the amount of processing being done
clearTimeout(pageChangeTimeout);
pageChangeTimeout = setTimeout(() => {
const otherViewer = compareViewer[leftPanelIndex].instance === instance ? compareViewer[rightPanelIndex].instance : compareViewer[leftPanelIndex].instance;
if (otherViewer.Core.documentViewer.getCurrentPage() !== pageNumber) {
otherViewer.Core.documentViewer.setCurrentPage(pageNumber);
}
compareText(pageNumber);
}, 1000);
});
const documentContainer = document
.getElementById(`${viewer.domElement}`)
.querySelector('iframe')
.contentDocument.querySelector('.DocumentContainer');
documentContainer.onscroll = () => {
syncScrolls(documentContainer.scrollLeft, documentContainer.scrollTop);
clearTimeout(scrollTimeout);
};
documentViewer.addEventListener('zoomUpdated', zoom => {
syncZoom(zoom, viewer.domElement);
});
});
});
};
const getPageText = (instance, pageNumber) => {
const doc = instance.Core.documentViewer.getDocument();
return new Promise(resolve => {
doc.loadPageText(pageNumber, text => {
resolve(text);
});
});
};
const compareText = async pageNumber => {
const text0 = await getPageText(compareViewer[leftPanelIndex].instance, pageNumber);
const text1 = await getPageText(compareViewer[rightPanelIndex].instance, pageNumber);
compareViewer[leftPanelIndex].pageTextSections = [];
compareViewer[rightPanelIndex].pageTextSections = [];
const leftPanel = document.querySelector(`#${compareViewer[leftPanelIndex].diffPanel}`);
const rightPanel = document.querySelector(`#${compareViewer[rightPanelIndex].diffPanel}`);
leftPanel.innerHTML = '';
rightPanel.innerHTML = '';
let sectionIndex = 0;
const diffLines = Diff.diffLines(text0, text1);
for (let i = 0; i < diffLines.length; i++) {
const diffLine = diffLines[i];
const sectionLeft = document.createElement('div');
const sectionRight = document.createElement('div');
sectionLeft.className = 'section';
sectionRight.className = 'section';
if (!diffLine.removed && !diffLine.added) {
// handle case when the text are the same
// add a toggleable element that displayed the same text when double clicked
compareViewer[leftPanelIndex].pageTextSections.push(diffLine.value);
compareViewer[rightPanelIndex].pageTextSections.push(diffLine.value);
sectionLeft.setAttribute('section', sectionIndex);
sectionRight.setAttribute('section', sectionIndex);
sectionLeft.className = 'section identical';
sectionRight.className = 'section identical';
const btnLeft = document.createElement('span');
const btnRight = document.createElement('span');
btnLeft.innerHTML = '(...)';
btnRight.innerHTML = '(...)';
const textRight = document.createElement('p');
const textLeft = document.createElement('p');
textRight.innerHTML = diffLine.value.replace(/\r?\n/g, '<br />');
textRight.className = 'hidden';
textLeft.innerHTML = diffLine.value.replace(/\r?\n/g, '<br />');
textLeft.className = 'hidden';
sectionRight.appendChild(textRight);
sectionLeft.appendChild(textLeft);
sectionLeft.appendChild(btnLeft);
sectionRight.appendChild(btnRight);
const toggleText = () => {
if (window.getSelection().toString()) {
// return if highlighting text
return;
}
const displayingText = !(textLeft.className !== 'hidden');
textLeft.className = displayingText ? '' : 'hidden';
textRight.className = displayingText ? '' : 'hidden';
btnRight.className = displayingText ? 'hidden' : '';
btnLeft.className = displayingText ? 'hidden' : '';
};
sectionRight.addEventListener('mouseup', toggleText);
sectionLeft.addEventListener('mouseup', toggleText);
leftPanel.appendChild(sectionLeft);
rightPanel.appendChild(sectionRight);
} else {
let updatedLine = '';
if (i + 1 < diffLines.length && (diffLines[i + 1].removed || diffLines[i + 1].added)) {
updatedLine = diffLines[i + 1].value;
sectionLeft.setAttribute('section', sectionIndex);
sectionRight.setAttribute('section', sectionIndex);
compareViewer[leftPanelIndex].pageTextSections.push(diffLines[i].value);
compareViewer[rightPanelIndex].pageTextSections.push(diffLines[i + 1].value);
i++;
}
// get difference for individual characters so they can be highlighted
const diffChars = Diff.diffChars(diffLine.value, updatedLine);
let oldText = '';
let newText = '';
const addStyle = `background-color: ${compareViewer[rightPanelIndex].highlightColor}; color: ${compareViewer[rightPanelIndex].textColor};`;
const removeStyle = `background-color:${compareViewer[leftPanelIndex].highlightColor}; color: ${compareViewer[leftPanelIndex].textColor};`;
diffChars.forEach(char => {
const value = char.value.replace(/\r?\n/g, ' <br />');
if (!char.removed && !char.added) {
oldText += `<span>${value}</span>`;
newText += `<span>${value}</span>`;
} else if (char.added) {
newText += value.replace(/\s/g, '').length ? `<span style="${addStyle}">${value}</span>` : value;
} else if (char.removed) {
oldText += value.replace(/\s/g, '').length ? `<span style="${removeStyle}">${value}</span>` : value;
}
});
sectionRight.style.backgroundColor = compareViewer[rightPanelIndex].backgroundColor;
sectionLeft.style.backgroundColor = compareViewer[leftPanelIndex].backgroundColor;
const textLeft = document.createElement('p');
textLeft.innerHTML = oldText;
sectionLeft.appendChild(textLeft);
leftPanel.appendChild(sectionLeft);
const textRight = document.createElement('p');
textRight.innerHTML = newText;
sectionRight.appendChild(textRight);
rightPanel.appendChild(sectionRight);
const maxHeight = Math.max(sectionRight.scrollHeight, sectionLeft.scrollHeight);
sectionRight.style.height = `${maxHeight}px`;
sectionLeft.style.height = `${maxHeight}px`;
}
sectionIndex++;
}
};
const syncZoom = (zoom, domElement) => {
compareViewer.forEach(viewer => {
const instance = viewer.instance;
if (instance.UI.getZoomLevel() !== zoom && domElement !== viewer.domElement) {
instance.UI.setZoomLevel(zoom);
}
});
};
const syncScrolls = (scrollLeft, scrollTop) => {
compareViewer.forEach(viewer => {
const documentContainer = document
.getElementById(`${viewer.domElement}`)
.querySelector('iframe')
.contentDocument.querySelector('.DocumentContainer');
if (!documentContainer) {
return;
}
if (documentContainer.scrollLeft !== scrollLeft) {
documentContainer.scrollLeft = scrollLeft;
}
if (documentContainer.scrollTop !== scrollTop) {
documentContainer.scrollTop = scrollTop;
}
});
};
let scrollDebounce = 0;
const scrollDebounceTime = 10;
// re render the top display when window resize
window.onresize = () => {
if (compareViewer[leftPanelIndex].instance && compareViewer[leftPanelIndex].instance.Core.documentViewer) {
compareText(compareViewer[leftPanelIndex].instance.Core.documentViewer.getCurrentPage());
}
};
document.querySelector('#currentPage').onchange = e => {
const value = e.currentTarget.value;
if (value <= maxPageCount) {
compareViewer[leftPanelIndex].instance.Core.documentViewer.setCurrentPage(value);
compareViewer[rightPanelIndex].instance.Core.documentViewer.setCurrentPage(value);
}
};
// sync the top displays
document.getElementById('compareLeftPanel').onscroll = e => {
clearTimeout(scrollDebounce);
scrollDebounce = setTimeout(() => {
document.getElementById('compareRightPanel').scrollTop = e.target.scrollTop;
}, scrollDebounceTime);
};
document.getElementById('compareRightPanel').onscroll = e => {
clearTimeout(scrollDebounce);
scrollDebounce = setTimeout(() => {
document.getElementById('compareLeftPanel').scrollTop = e.target.scrollTop;
}, scrollDebounceTime);
};
const toggleWebViewer = () => {
if (compareViewer[leftPanelIndex].displayingWebViewer) {
document.getElementById('toggleRightBtn').disabled = true;
document.getElementById('toggleLeftBtn').disabled = false;
document.getElementById('rightPanel').classList.remove('hidden');
document.getElementById('leftPanel').classList.add('hidden');
compareViewer[leftPanelIndex].displayingWebViewer = false;
compareViewer[rightPanelIndex].displayingWebViewer = true;
} else {
document.getElementById('toggleRightBtn').disabled = false;
document.getElementById('toggleLeftBtn').disabled = true;
document.getElementById('rightPanel').classList.add('hidden');
document.getElementById('leftPanel').classList.remove('hidden');
compareViewer[leftPanelIndex].displayingWebViewer = true;
compareViewer[rightPanelIndex].displayingWebViewer = false;
}
};
document.getElementById('expandTextBtn').onclick = () => {
document.getElementById('expandTextBtn').hidden = true;
document.getElementById('shirkTextBtn').hidden = false;
Array.from(document.querySelectorAll('#compareLeftPanel>.identical')).forEach(identicalSection => {
identicalSection.querySelector('p').classList.remove('hidden');
identicalSection.querySelector('span').classList.add('hidden');
});
Array.from(document.querySelectorAll('#compareRightPanel>.identical')).forEach(identicalSection => {
identicalSection.querySelector('p').classList.remove('hidden');
identicalSection.querySelector('span').classList.add('hidden');
});
};
document.getElementById('shirkTextBtn').onclick = () => {
document.getElementById('expandTextBtn').hidden = false;
document.getElementById('shirkTextBtn').hidden = true;
Array.from(document.querySelectorAll('#compareLeftPanel>.identical')).forEach(identicalSection => {
identicalSection.querySelector('p').classList.add('hidden');
identicalSection.querySelector('span').classList.remove('hidden');
});
Array.from(document.querySelectorAll('#compareRightPanel>.identical')).forEach(identicalSection => {
identicalSection.querySelector('p').classList.add('hidden');
identicalSection.querySelector('span').classList.remove('hidden');
});
};
document.getElementById('colorPopup').onclick = e => {
e.stopPropagation();
const popup = document.querySelector('#colorFormPopup');
popup.hidden = !popup.hidden;
};
document.getElementById('colorFormPopup').onclick = e => {
e.stopPropagation();
};
document.getElementById('compareContainer').onclick = () => {
document.querySelector('#colorFormPopup').hidden = true;
};
const colorInputs = [
{
element: document.getElementById('rightHighlightColor'),
viewer: compareViewer[0],
color: 'highlightColor',
},
{
element: document.getElementById('rightBackgroundColor'),
viewer: compareViewer[0],
color: 'backgroundColor',
},
{
element: document.getElementById('rightTextColor'),
viewer: compareViewer[0],
color: 'textColor',
},
{
element: document.getElementById('leftHighlightColor'),
viewer: compareViewer[1],
color: 'highlightColor',
},
{
element: document.getElementById('leftBackgroundColor'),
viewer: compareViewer[1],
color: 'backgroundColor',
},
{
element: document.getElementById('leftTextColor'),
viewer: compareViewer[1],
color: 'textColor',
},
];
colorInputs.forEach(colorInput => {
colorInput.element.onchange = e => {
colorInput.viewer[colorInput.color] = e.srcElement.value;
compareText(compareViewer[0].instance.Core.documentViewer.getCurrentPage());
};
});
document.getElementById('toggleLeftBtn').onclick = () => {
toggleWebViewer();
};
document.getElementById('toggleRightBtn').onclick = () => {
toggleWebViewer();
};
const loadDocuments = (leftDocument, rightDocument) => {
let leftLoadDocumentPromise = Promise.resolve();
let rightLoadDocumentPromise = Promise.resolve();
if (leftDocument) {
document.querySelector(`#${compareViewer[leftPanelIndex].diffPanel}`).innerHTML = '';
leftLoadDocumentPromise = new Promise(resolve => {
compareViewer[leftPanelIndex].instance.Core.documentViewer.addEventListener(
'documentLoaded',
() => {
resolve();
},
{ once: true }
);
});
compareViewer[leftPanelIndex].instance.UI.loadDocument(leftDocument);
}
if (rightDocument) {
document.querySelector(`#${compareViewer[rightPanelIndex].diffPanel}`).innerHTML = '';
rightLoadDocumentPromise = new Promise(resolve => {
compareViewer[rightPanelIndex].instance.Core.documentViewer.addEventListener(
'documentLoaded',
() => {
resolve();
},
{ once: true }
);
});
compareViewer[rightPanelIndex].instance.UI.loadDocument(rightDocument);
}
Promise.all([leftLoadDocumentPromise, rightLoadDocumentPromise]).then(() => {
// wait for both documents to finish loading before getting file information and comparing them
maxPageCount = Math.min(compareViewer[leftPanelIndex].instance.Core.documentViewer.getPageCount(), compareViewer[rightPanelIndex].instance.Core.documentViewer.getPageCount());
compareViewer[leftPanelIndex].instance.Core.documentViewer.setCurrentPage(1);
compareViewer[rightPanelIndex].instance.Core.documentViewer.setCurrentPage(1);
document.querySelector('#totalPage').textContent = maxPageCount;
document.querySelector('#currentPage').setAttribute('max', maxPageCount);
document.querySelector('#currentPage').value = 1;
compareViewer[leftPanelIndex].filenameBtn.value = compareViewer[leftPanelIndex].instance.Core.documentViewer.getDocument().filename;
compareViewer[rightPanelIndex].filenameBtn.value = compareViewer[rightPanelIndex].instance.Core.documentViewer.getDocument().filename;
compareText(1);
});
};
document.getElementById('dropdown-form').onsubmit = e => {
e.preventDefault();
loadDocuments(document.querySelector('#leftPanel-select').value, document.querySelector('#rightPanel-select').value);
};
document.getElementById('url-form').onsubmit = e => {
e.preventDefault();
loadDocuments(document.querySelector('#leftPanel-url').value, document.querySelector('#rightPanel-url').value);
};
document.getElementById('file-picker-form').onsubmit = e => {
e.preventDefault();
loadDocuments(document.querySelector('#leftPanel-file-picker').files[0], document.querySelector('#rightPanel-file-picker').files[0]);
};
document.getElementById('findSelectedBtn').onclick = () => {
// get the currently selected text. We need to replace new lines with spaces for matching later.
const selection = window.getSelection();
// in "compareText", it set new lines to be " <br />", this undo it if needed
const selectedText = selection.toString().replace(/\s*\n/gm, ' ');
if (!selectedText) {
alert('No text selected to find');
return;
}
const selectedTextSection = selection.baseNode.parentElement.closest('.section');
const selectionNumber = parseInt(selectedTextSection.getAttribute('section'), 10);
let currentViewer = null;
if (selection.baseNode.parentElement.closest('.viewer').id === 'compareRightPanel') {
currentViewer = compareViewer[rightPanelIndex];
} else {
currentViewer = compareViewer[leftPanelIndex];
}
if (!currentViewer.displayingWebViewer) {
// if we aren't displaying the current viewer toggle to show it
toggleWebViewer();
}
// since we are searching all the text on a page, we need to find the number of previous occurrences of the search term
const sectionElements = Array.from(selection.baseNode.parentNode.parentElement.children);
let currentPreviousText = '';
for (let i = 0; sectionElements.length > i; i++) {
if (sectionElements[i] === selection.baseNode.parentElement) {
currentPreviousText += sectionElements[i].textContent.substring(0, selection.anchorOffset);
break;
}
currentPreviousText += sectionElements[i].textContent;
}
const previousSectionText = currentViewer.pageTextSections
.filter((t, i) => i < selectionNumber)
.map(t => t.replace(/\s*\n/gm, ' '))
.join(' ');
const previousText = `${previousSectionText}${currentPreviousText}`;
const matches = previousText.match(new RegExp(`${selectedText.replace(/[-[\]{}()*+?.,\\^$|#]/g, '\\$&').trim()}`, 'g'));
const previousOccurrencesInSection = matches ? matches.length : 0;
let count = 0;
const mode =
currentViewer.instance.Core.documentViewer.SearchMode.e_highlight |
currentViewer.instance.Core.documentViewer.SearchMode.e_ambient_string |
currentViewer.instance.Core.documentViewer.SearchMode.e_case_sensitive;
const searchOptions = {
onResult: result => {
if (count === previousOccurrencesInSection) {
// skip all the previous occurrences of the search term till we get to the currently selected text
currentViewer.instance.Core.documentViewer.displaySearchResult(result);
}
count++;
},
fullSearch: true,
startPage: currentViewer.instance.Core.documentViewer.getCurrentPage(),
endPage: currentViewer.instance.Core.documentViewer.getCurrentPage(),
};
currentViewer.instance.Core.documentViewer.textSearchInit(selectedText.trim(), mode, searchOptions);
};
document.getElementById('searchForm').onsubmit = e => {
e.preventDefault();
const currentSearchTerm = document.getElementById('textSearch').value;
const viewer = compareViewer[leftPanelIndex].displayingWebViewer ? compareViewer[leftPanelIndex] : compareViewer[rightPanelIndex];
if (viewer.searchTerm === currentSearchTerm && viewer.searchResult.length) {
// cycle through existing results
viewer.searchResultIndex = viewer.searchResultIndex + 1 < viewer.searchResult.length ? viewer.searchResultIndex + 1 : 0;
viewer.instance.Core.documentViewer.displaySearchResult(viewer.searchResult[viewer.searchResultIndex]);
} else {
viewer.searchTerm = currentSearchTerm;
viewer.searchResult = [];
const mode =
viewer.instance.Core.documentViewer.SearchMode.e_highlight | viewer.instance.Core.documentViewer.SearchMode.e_ambient_string | viewer.instance.Core.documentViewer.SearchMode.e_case_sensitive;
const isFullSearch = true;
viewer.instance.Core.documentViewer.textSearchInit(currentSearchTerm, mode, isFullSearch, result => {
// this callback get called as search term are found till the final "Done" result is returned
if (result.resultCode === Core.Search.ResultCode.FOUND) {
if (viewer.searchResult.length === 0) {
// display the first result received
viewer.instance.Core.documentViewer.displaySearchResult(result);
}
// store results for cycling through search, check "resultCode" to not add the "done" result
viewer.searchResult.push(result);
} else if (result.resultCode === Core.Search.ResultCode.DONE && !viewer.searchResult.length) {
alert(`No results found for ${currentSearchTerm}`);
}
});
}
};