Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 62 additions & 37 deletions server/src/main/resources/static/pdfjs/web/viewer.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
var kkhighlightAll;
var watermarkTxt;
var watermarkSettings = {
start_x: 80,
start_y: 80,
x_space: 80,
y_space: 80,
color: 'black',
alpha: 0.2,
fontsize: '18px',
font: '微软雅黑',
width: 200,
height: 80,
angle: 30
};
const queryString = document.location.search.substring(1);
const params = (0, parseQueryString)(queryString);

Expand All @@ -13,6 +26,30 @@ if (kkpdfAutoFetch == "true") {
function isNotEmpty(value) {
return value !== null && value !== undefined && value !== '' && value !== 'false' ;
}

function getWatermarkStringParam(params, name, fallback) {
const value = params.get(name);
return isNotEmpty(value) ? value : fallback;
}

function getWatermarkNumberParam(params, name, fallback, isValid) {
const rawValue = params.get(name);
if (!isNotEmpty(rawValue)) return fallback;
const value = Number(rawValue);
return Number.isFinite(value) && isValid(value) ? value : fallback;
}

function configureWatermark(params) {
watermarkSettings.x_space = getWatermarkNumberParam(params, "watermarkxspace", watermarkSettings.x_space, value => value >= 0);
watermarkSettings.y_space = getWatermarkNumberParam(params, "watermarkyspace", watermarkSettings.y_space, value => value >= 0);
watermarkSettings.font = getWatermarkStringParam(params, "watermarkfont", watermarkSettings.font);
watermarkSettings.fontsize = getWatermarkStringParam(params, "watermarkfontsize", watermarkSettings.fontsize);
watermarkSettings.color = getWatermarkStringParam(params, "watermarkcolor", watermarkSettings.color);
watermarkSettings.alpha = getWatermarkNumberParam(params, "watermarkalpha", watermarkSettings.alpha, value => value >= 0 && value <= 1);
watermarkSettings.width = getWatermarkNumberParam(params, "watermarkwidth", watermarkSettings.width, value => value > 0);
watermarkSettings.height = getWatermarkNumberParam(params, "watermarkheight", watermarkSettings.height, value => value > 0);
watermarkSettings.angle = getWatermarkNumberParam(params, "watermarkangle", watermarkSettings.angle, Number.isFinite);
}
/**
* 通用水印生成函数
* @param {HTMLElement} container - 水印容器(相对定位的父元素)
Expand All @@ -23,20 +60,7 @@ function isNotEmpty(value) {
function addWatermark(container, watermarkTxt, explicitWidth = null, explicitHeight = null) {
if (!isNotEmpty(watermarkTxt)) return;

// 公共配置
const settings = {
start_x: 80,
start_y: 80,
x_space: 80,
y_space: 80,
color: 'black',
alpha: 0.2,
fontsize: '18px',
font: '微软雅黑',
width: 200,
height: 80,
angle: 30
};
const settings = watermarkSettings;

// 确定实际使用的宽高
let pageWidth, pageHeight;
Expand All @@ -55,30 +79,30 @@ function addWatermark(container, watermarkTxt, explicitWidth = null, explicitHei
maxY = Math.max(maxY, 250);

const fragment = document.createDocumentFragment();
for (let x = settings.start_x; x < maxX; x += settings.x_space) {
for (let y = settings.start_y; y < maxY; y += settings.y_space) {
const xStep = settings.width + settings.x_space;
const yStep = settings.height + settings.y_space;
for (let x = settings.start_x; x < maxX; x += xStep) {
for (let y = settings.start_y; y < maxY; y += yStep) {
const div = document.createElement('div');
div.className = 'mask_div';
div.appendChild(document.createTextNode(watermarkTxt));
div.style.cssText = `
filter: progid:DXImageTransform.Microsoft.Alpha(opacity=${settings.alpha * 100});
transform: rotate(-${settings.angle}deg);
visibility: visible;
position: absolute;
left: ${x}px;
top: ${y}px;
overflow: hidden;
z-index: 100;
pointer-events: none;
opacity: ${settings.alpha};
font-size: ${settings.fontsize};
font-family: ${settings.font};
color: ${settings.color};
text-align: center;
width: ${settings.width}px;
height: ${settings.height}px;
display: block;
`;
div.style.filter = `progid:DXImageTransform.Microsoft.Alpha(opacity=${settings.alpha * 100})`;
div.style.transform = `rotate(-${settings.angle}deg)`;
div.style.visibility = 'visible';
div.style.position = 'absolute';
div.style.left = `${x}px`;
div.style.top = `${y}px`;
div.style.overflow = 'hidden';
div.style.zIndex = '100';
div.style.pointerEvents = 'none';
div.style.opacity = settings.alpha;
div.style.fontSize = settings.fontsize;
div.style.fontFamily = settings.font;
div.style.color = settings.color;
div.style.textAlign = 'center';
div.style.width = `${settings.width}px`;
div.style.height = `${settings.height}px`;
div.style.display = 'block';
fragment.appendChild(div);
}
}
Expand Down Expand Up @@ -22069,7 +22093,8 @@ const PDFViewerApplication = {
disableBookmark = params.get("disablebookmark") ?? 'false';
disableEditing = params.get("disableediting") ?? 'false';
kkhighlightAll = params.get("pdfhighlightall") ?? 'false';
watermarkTxt= params.get('watermarktxt') ?? 'false';
watermarkTxt = params.get('watermarktxt') ?? 'false';
configureWatermark(params);
try {
file = new URL(file).href;
} catch {
Expand Down Expand Up @@ -24017,4 +24042,4 @@ if (document.readyState === "interactive" || document.readyState === "complete")

export { PDFViewerApplication, AppConstants as PDFViewerApplicationConstants, AppOptions as PDFViewerApplicationOptions };

//# sourceMappingURL=viewer.mjs.map
//# sourceMappingURL=viewer.mjs.map
17 changes: 15 additions & 2 deletions server/src/main/resources/web/pdf.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,28 @@
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url)) + "&key=${kkkey}";
}
var viewerUrl = baseUrl + "pdfjs/web/viewer.html?file=" + encodeURIComponent(url);
var watermarkEncoded = encodeURIComponent('${watermarkTxt?js_string}');
var watermarkParams = {
watermarktxt: '${watermarkTxt?js_string}',
watermarkxspace: '${watermarkXSpace?js_string}',
watermarkyspace: '${watermarkYSpace?js_string}',
watermarkfont: '${watermarkFont?js_string}',
watermarkfontsize: '${watermarkFontsize?js_string}',
watermarkcolor: '${watermarkColor?js_string}',
watermarkalpha: '${watermarkAlpha?js_string}',
watermarkwidth: '${watermarkWidth?js_string}',
watermarkheight: '${watermarkHeight?js_string}',
watermarkangle: '${watermarkAngle?js_string}'
};
var highlightEncoded = encodeURIComponent('${highlightall?js_string}');
viewerUrl += "&disablepresentationmode=${pdfPresentationModeDisable}";
viewerUrl += "&disableopenfile=${pdfOpenFileDisable}";
viewerUrl += "&disableprint=${pdfPrintDisable}";
viewerUrl += "&disabledownload=${pdfDownloadDisable}";
viewerUrl += "&disablebookmark=${pdfBookmarkDisable}";
viewerUrl += "&disableediting=${pdfDisableEditing}";
viewerUrl += "&watermarktxt=" + watermarkEncoded;
Object.keys(watermarkParams).forEach(function (name) {
viewerUrl += "&" + name + "=" + encodeURIComponent(watermarkParams[name]);
});
viewerUrl += "&pdfhighlightall=" + highlightEncoded;
viewerUrl += "#page=${page}"; // ?c 确保数字不包含千位分隔符
<#if "true" == pdfSidebarOpen>
Expand Down
33 changes: 33 additions & 0 deletions server/src/test/java/cn/keking/PdfViewerCompatibilityTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -35,6 +36,38 @@ void shouldRenderPdfSidebarModeByDefaultBasedOnConfig() throws IOException {
assertTrue(pdfTemplate.contains("viewerUrl += \"&pagemode=none\";"));
}

@Test
void shouldForwardAndApplyAllPdfWatermarkSettings() throws IOException {
String pdfTemplate = readResource("/web/pdf.ftl");
String viewerScript = readResource("/static/pdfjs/web/viewer.mjs");
Map<String, String> watermarkParams = Map.ofEntries(
Map.entry("watermarktxt", "watermarkTxt"),
Map.entry("watermarkxspace", "watermarkXSpace"),
Map.entry("watermarkyspace", "watermarkYSpace"),
Map.entry("watermarkfont", "watermarkFont"),
Map.entry("watermarkfontsize", "watermarkFontsize"),
Map.entry("watermarkcolor", "watermarkColor"),
Map.entry("watermarkalpha", "watermarkAlpha"),
Map.entry("watermarkwidth", "watermarkWidth"),
Map.entry("watermarkheight", "watermarkHeight"),
Map.entry("watermarkangle", "watermarkAngle")
);

watermarkParams.forEach((queryParam, templateAttribute) -> {
assertTrue(pdfTemplate.contains(queryParam + ": '${" + templateAttribute + "?js_string}'"),
() -> "PDF template does not forward " + templateAttribute);
assertTrue(viewerScript.contains("\"" + queryParam + "\"")
|| viewerScript.contains("'" + queryParam + "'"),
() -> "PDF viewer does not consume " + queryParam);
});
assertTrue(viewerScript.contains("div.style.fontFamily = settings.font;"));
assertTrue(viewerScript.contains("div.style.fontSize = settings.fontsize;"));
assertTrue(viewerScript.contains("div.style.color = settings.color;"));
assertTrue(viewerScript.contains("div.style.opacity = settings.alpha;"));
assertTrue(viewerScript.contains("const xStep = settings.width + settings.x_space;"));
assertTrue(viewerScript.contains("const yStep = settings.height + settings.y_space;"));
}

@Test
void shouldPreferPdfForOfficePreviewByDefault() throws IOException {
String properties = readResource("/application.properties");
Expand Down
50 changes: 50 additions & 0 deletions tests/e2e/specs/preview-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,53 @@ test('21 security: block 10.x host in getCorsFile', async ({ request }) => {
const body = await resp.text();
expect(body).toContain('不受信任');
});

test('22 pdf preview applies custom watermark settings', async ({ page }) => {
const query = new URLSearchParams({
url: b64(`${fixtureBase}/sample.pdf`),
watermarkTxt: 'Custom watermark',
watermarkXSpace: '37',
watermarkYSpace: '43',
watermarkFont: 'Courier New',
watermarkFontsize: '31px',
watermarkColor: 'rgb(1, 2, 3)',
watermarkAlpha: '0.65',
watermarkWidth: '100',
watermarkHeight: '77',
watermarkAngle: '17',
});

await page.goto(`/onlinePreview?${query.toString()}`);
const watermark = page.frameLocator('#pdfFrame').locator('.mask_div').first();
await expect(watermark).toHaveText('Custom watermark');

const style = await watermark.evaluate(element => ({
fontFamily: element.style.fontFamily,
fontSize: element.style.fontSize,
color: element.style.color,
opacity: element.style.opacity,
width: element.style.width,
height: element.style.height,
transform: element.style.transform,
}));
expect(style).toEqual({
fontFamily: '"Courier New"',
fontSize: '31px',
color: 'rgb(1, 2, 3)',
opacity: '0.65',
width: '100px',
height: '77px',
transform: 'rotate(-17deg)',
});

const positions = await page.frameLocator('#pdfFrame').locator('.mask_div').evaluateAll(elements =>
elements.map(element => ({
left: Number.parseFloat(element.style.left),
top: Number.parseFloat(element.style.top),
})),
);
const nextColumn = positions.find(position => position.left !== positions[0].left);
expect(positions[1].top - positions[0].top).toBe(120);
expect(nextColumn).toBeDefined();
expect(nextColumn!.left - positions[0].left).toBe(137);
});
Loading