@skrillex1224/playwright-toolkit 2.1.209 → 2.1.211

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/index.js CHANGED
@@ -408,6 +408,33 @@ function createInternalLogger(moduleName, explicitLogger) {
408
408
  };
409
409
  }
410
410
 
411
+ // src/internals/viewport.js
412
+ var toPositiveInt = (value) => {
413
+ const number = Math.round(Number(value) || 0);
414
+ return number > 0 ? number : 0;
415
+ };
416
+ var resolveCurrentViewportSize = async (page, fallback = {}) => {
417
+ const directViewport = page?.viewportSize?.();
418
+ const directWidth = toPositiveInt(directViewport?.width);
419
+ const directHeight = toPositiveInt(directViewport?.height);
420
+ if (directWidth && directHeight) {
421
+ return {
422
+ width: directWidth,
423
+ height: directHeight
424
+ };
425
+ }
426
+ const measuredViewport = await page?.evaluate?.(() => ({
427
+ width: window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0,
428
+ height: window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0
429
+ })).catch(() => null);
430
+ const fallbackWidth = toPositiveInt(fallback?.width);
431
+ const fallbackHeight = toPositiveInt(fallback?.height);
432
+ return {
433
+ width: toPositiveInt(measuredViewport?.width) || fallbackWidth || 1,
434
+ height: toPositiveInt(measuredViewport?.height) || fallbackHeight || 1
435
+ };
436
+ };
437
+
411
438
  // src/internals/screenshot.js
412
439
  var logger = createInternalLogger("Screenshot");
413
440
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -434,7 +461,7 @@ var normalizeQuality = (value, type) => {
434
461
  };
435
462
  var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
436
463
  const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
437
- const width = Math.max(1, Math.ceil(contentSize?.width || viewport.width || 1));
464
+ const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
438
465
  let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
439
466
  if (maxClipHeight > 0) {
440
467
  height = Math.min(height, maxClipHeight);
@@ -471,7 +498,7 @@ var capturePageScreenshot = async (page, options = {}) => {
471
498
  const session = await context.newCDPSession(page);
472
499
  try {
473
500
  const metrics = await session.send("Page.getLayoutMetrics");
474
- const viewport = page.viewportSize() || { width: 1280, height: 720 };
501
+ const viewport = await resolveCurrentViewportSize(page);
475
502
  const captureParams = {
476
503
  format: type,
477
504
  fromSurface: true,
@@ -924,6 +951,9 @@ var ProxyMeterRuntime = {
924
951
  getProxyMeterSnapshot
925
952
  };
926
953
 
954
+ // src/internals/constants.js
955
+ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
956
+
927
957
  // src/runtime-env.js
928
958
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
929
959
  var rememberedRuntimeState = null;
@@ -1430,6 +1460,7 @@ var RuntimeEnv = {
1430
1460
  parseInput(input = {}, actor = "") {
1431
1461
  const runtime2 = tryParseJSON(input?.runtime) || {};
1432
1462
  const resolvedActor = String(actor || input?.actor || "").trim();
1463
+ const query = String(input?.query || "").trim();
1433
1464
  const cookies = normalizeCookies(runtime2?.cookies);
1434
1465
  const cookieMap = buildCookieMap(cookies);
1435
1466
  const localStorage = normalizeLocalStorage(runtime2?.local_storage);
@@ -1453,6 +1484,7 @@ var RuntimeEnv = {
1453
1484
  actor: resolvedActor,
1454
1485
  runtime: normalizedRuntime,
1455
1486
  envId,
1487
+ query,
1456
1488
  auth,
1457
1489
  cookies,
1458
1490
  cookieMap,
@@ -1520,6 +1552,12 @@ var RuntimeEnv = {
1520
1552
  async applyToPage(page, source = {}, options = {}) {
1521
1553
  if (!page) return;
1522
1554
  const state = normalizeRuntimeState(source, options?.actor || "");
1555
+ Object.defineProperty(page, PageRuntimeStateKey, {
1556
+ configurable: true,
1557
+ enumerable: false,
1558
+ writable: true,
1559
+ value: state
1560
+ });
1523
1561
  const localStorage = state.localStorage || {};
1524
1562
  const sessionStorage = state.sessionStorage || {};
1525
1563
  const cookies = (state.cookies || []).map((cookie) => {
@@ -4521,7 +4559,7 @@ var Logger = {
4521
4559
  import delay2 from "delay";
4522
4560
 
4523
4561
  // src/internals/watermarkify.js
4524
- var SCREENSHOT_WATERMARKIFY_HOST_ID = "__pk_screenshot_watermarkify__";
4562
+ import sharp from "sharp";
4525
4563
  var DEFAULT_TIMEZONE_OFFSET = 8;
4526
4564
  var DEFAULT_RESOLVER_TIMEOUT_MS = 180;
4527
4565
  var DEFAULT_IP_LOOKUP_TIMEOUT_MS = 1e4;
@@ -4532,6 +4570,26 @@ var DEFAULT_WATERMARK_CELL_HEIGHT = 330;
4532
4570
  var DEFAULT_STRIP_LOGO_URL = "https://static.heartbitai.com/geo/icon/favicon.png";
4533
4571
  var DEFAULT_IP_LOOKUP_URL = "http://myip.ipip.net";
4534
4572
  var DEFAULT_LOGO_FETCH_TIMEOUT_MS = 2500;
4573
+ var DEFAULT_STRIP_ONE_LINE_HEIGHT = 78;
4574
+ var DEFAULT_STRIP_WRAPPED_MIN_HEIGHT = 108;
4575
+ var DEFAULT_STRIP_PADDING_LEFT = 22;
4576
+ var DEFAULT_STRIP_PADDING_RIGHT = 28;
4577
+ var DEFAULT_STRIP_GAP = 18;
4578
+ var DEFAULT_STRIP_LABEL_WIDTH = 56;
4579
+ var DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS = 180;
4580
+ var DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH = 320;
4581
+ var DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO = 0.58;
4582
+ var DEFAULT_STRIP_ZONE_GAP = 18;
4583
+ var DEFAULT_STRIP_PROMPT_TRAILING_PADDING = 16;
4584
+ var DEFAULT_STRIP_LABEL_FONT_SIZE = 14.5;
4585
+ var DEFAULT_STRIP_VALUE_FONT_SIZE = 15.5;
4586
+ var DEFAULT_STRIP_VALUE_PADDING = 10;
4587
+ var DEFAULT_STRIP_LABEL_PADDING = 14;
4588
+ var DEFAULT_STRIP_SECTION_GAP = DEFAULT_STRIP_GAP + 16;
4589
+ var DEFAULT_STRIP_LINE_HEIGHT_RATIO = 1.28;
4590
+ var DEFAULT_STRIP_PROMPT_MAX_LINES = 3;
4591
+ var DEFAULT_STRIP_CONTENT_SAFE_PADDING_X = 12;
4592
+ var DEFAULT_STRIP_TEXT_WIDTH_SAFETY = 10;
4535
4593
  var WEAK_LOCATION_VALUES = /* @__PURE__ */ new Set(["cn", "\u4E2D\u56FD"]);
4536
4594
  var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4537
4595
  /(?:中国)?移动$/i,
@@ -4556,6 +4614,7 @@ var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4556
4614
  /digitalocean$/i
4557
4615
  ];
4558
4616
  var cachedStripLogoSrcPromise = null;
4617
+ var cachedEnrichmentByContext = /* @__PURE__ */ new WeakMap();
4559
4618
  var normalizeText = (value) => String(value || "").trim();
4560
4619
  var toInline = (value, maxLen = 200) => {
4561
4620
  const text = normalizeText(value);
@@ -4567,12 +4626,14 @@ var shortenTail = (value, maxLen = 80) => {
4567
4626
  if (!text || text.length <= maxLen) return text;
4568
4627
  return `${text.slice(0, Math.max(0, maxLen - 1)).trimEnd()}\u2026`;
4569
4628
  };
4570
- var shortenMiddle = (value, maxLen = 56, headLen = 32, tailLen = 14) => {
4571
- const text = toInline(value, Math.max(maxLen * 2, maxLen + headLen + tailLen));
4572
- if (!text || text.length <= maxLen) return text;
4573
- const safeHeadLen = Math.max(4, Math.min(headLen, maxLen - 5));
4574
- const safeTailLen = Math.max(4, Math.min(tailLen, maxLen - safeHeadLen - 1));
4575
- return `${text.slice(0, safeHeadLen).trimEnd()}\u2026${text.slice(-safeTailLen).trimStart()}`;
4629
+ var shortenByCharacters = (value, maxChars = 80) => {
4630
+ const text = normalizeWhitespace(value);
4631
+ if (!text) return "";
4632
+ const chars = Array.from(text);
4633
+ if (chars.length <= maxChars) {
4634
+ return text;
4635
+ }
4636
+ return `${chars.slice(0, Math.max(0, maxChars - 1)).join("").trimEnd()}\u2026`;
4576
4637
  };
4577
4638
  var padDatePart = (value) => String(value).padStart(2, "0");
4578
4639
  var formatUtcOffsetLabel = (offsetHours = DEFAULT_TIMEZONE_OFFSET) => {
@@ -4646,6 +4707,29 @@ var fillEnrichment = (target, source) => {
4646
4707
  }
4647
4708
  return target;
4648
4709
  };
4710
+ var readCachedEnrichment = (page) => {
4711
+ const context = page && typeof page.context === "function" ? page.context() : null;
4712
+ if (!context) {
4713
+ return null;
4714
+ }
4715
+ const cached = cachedEnrichmentByContext.get(context);
4716
+ return cached && typeof cached === "object" ? cached : null;
4717
+ };
4718
+ var writeCachedEnrichment = (page, source) => {
4719
+ const context = page && typeof page.context === "function" ? page.context() : null;
4720
+ if (!context || !source || typeof source !== "object") {
4721
+ return;
4722
+ }
4723
+ const current = cachedEnrichmentByContext.get(context) || {};
4724
+ const next = {
4725
+ ip: toInline(source.ip || current.ip, 80),
4726
+ location: toInline(source.location || current.location, 80)
4727
+ };
4728
+ if (!next.ip && !next.location) {
4729
+ return;
4730
+ }
4731
+ cachedEnrichmentByContext.set(context, next);
4732
+ };
4649
4733
  var getHostname = (url) => {
4650
4734
  try {
4651
4735
  const parsed = new URL(url);
@@ -4749,8 +4833,8 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
4749
4833
  url: baseMeta.url,
4750
4834
  hostname: baseMeta.hostname,
4751
4835
  title: baseMeta.title,
4836
+ prompt: baseMeta.prompt,
4752
4837
  query: baseMeta.query,
4753
- taskId: baseMeta.taskId,
4754
4838
  serverAddr,
4755
4839
  signal: controller?.signal
4756
4840
  })).catch(() => null),
@@ -4775,44 +4859,43 @@ var openProbePage = async (page) => {
4775
4859
  if (!context) {
4776
4860
  return null;
4777
4861
  }
4862
+ const browser = typeof context.browser === "function" ? context.browser() : null;
4863
+ if (browser && typeof browser.newContext === "function") {
4864
+ const probeContext = await browser.newContext().catch(() => null);
4865
+ if (probeContext && typeof probeContext.newPage === "function") {
4866
+ const probePage = await probeContext.newPage().catch(async () => {
4867
+ await probeContext.close().catch(() => {
4868
+ });
4869
+ return null;
4870
+ });
4871
+ if (probePage) {
4872
+ return {
4873
+ page: probePage,
4874
+ close: async () => {
4875
+ await probeContext.close().catch(() => {
4876
+ });
4877
+ }
4878
+ };
4879
+ }
4880
+ } else {
4881
+ await probeContext?.close?.().catch(() => {
4882
+ });
4883
+ }
4884
+ }
4778
4885
  if (typeof context.newPage === "function") {
4779
4886
  try {
4780
- const probePage2 = await context.newPage();
4887
+ const probePage = await context.newPage();
4781
4888
  return {
4782
- page: probePage2,
4889
+ page: probePage,
4783
4890
  close: async () => {
4784
- await probePage2.close().catch(() => {
4891
+ await probePage.close().catch(() => {
4785
4892
  });
4786
4893
  }
4787
4894
  };
4788
4895
  } catch {
4789
4896
  }
4790
4897
  }
4791
- const browser = typeof context.browser === "function" ? context.browser() : null;
4792
- if (!browser || typeof browser.newContext !== "function") {
4793
- return null;
4794
- }
4795
- const probeContext = await browser.newContext().catch(() => null);
4796
- if (!probeContext || typeof probeContext.newPage !== "function") {
4797
- await probeContext?.close?.().catch(() => {
4798
- });
4799
- return null;
4800
- }
4801
- const probePage = await probeContext.newPage().catch(async () => {
4802
- await probeContext.close().catch(() => {
4803
- });
4804
- return null;
4805
- });
4806
- if (!probePage) {
4807
- return null;
4808
- }
4809
- return {
4810
- page: probePage,
4811
- close: async () => {
4812
- await probeContext.close().catch(() => {
4813
- });
4814
- }
4815
- };
4898
+ return null;
4816
4899
  };
4817
4900
  var resolveWithIpLookup = async (page, options = {}) => {
4818
4901
  if (!page || typeof page.context !== "function" || options.ipLookup === false) {
@@ -4857,10 +4940,14 @@ var resolveWithIpLookup = async (page, options = {}) => {
4857
4940
  };
4858
4941
  var resolveEnrichment = async (page, baseMeta, options) => {
4859
4942
  const response = options.response && typeof options.response === "object" ? options.response : null;
4943
+ const cached = readCachedEnrichment(page);
4860
4944
  const merged = {
4861
4945
  ip: toInline(options.ip, 80),
4862
4946
  location: toInline(options.location, 80)
4863
4947
  };
4948
+ if (!merged.ip || !merged.location) {
4949
+ fillEnrichment(merged, cached);
4950
+ }
4864
4951
  if (!merged.ip || !merged.location) {
4865
4952
  fillEnrichment(
4866
4953
  merged,
@@ -4884,24 +4971,31 @@ var resolveEnrichment = async (page, baseMeta, options) => {
4884
4971
  merged.location = headerLocation || merged.location;
4885
4972
  }
4886
4973
  }
4974
+ writeCachedEnrichment(page, merged);
4887
4975
  return merged;
4888
4976
  };
4889
- var buildWatermarkStamp = ({ taskId, captureTime, ip, location }) => {
4977
+ var buildWatermarkStamp = ({ prompt, captureTime, ip, location }) => {
4890
4978
  const parts = [
4891
- `TaskID ${shortenMiddle(taskId, 56, 24, 16) || "-"}`,
4979
+ `Prompt ${shortenTail(prompt, 40) || "-"}`,
4892
4980
  `Time ${captureTime}`,
4893
4981
  `Loc ${shortenTail(location, 20) || "-"}`,
4894
4982
  `IP ${toInline(ip, 24) || "-"}`
4895
4983
  ];
4896
4984
  return parts.join(" | ");
4897
4985
  };
4898
- var buildStripSegments = ({ taskId, captureTime, ip, location }) => {
4986
+ var buildStripSegments = ({ prompt, captureTime, ip, location }) => {
4987
+ const promptValue = shortenByCharacters(
4988
+ normalizeWhitespace(prompt) || "-",
4989
+ DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS
4990
+ ) || "-";
4991
+ const ipValue = normalizeWhitespace(ip) || "-";
4992
+ const locationValue = normalizeWhitespace(location) || "-";
4899
4993
  return [
4900
4994
  {
4901
- kind: "taskId",
4902
- label: "TaskID",
4903
- value: toInline(taskId, 240) || "-",
4904
- rawValue: toInline(taskId, 240) || "-"
4995
+ kind: "prompt",
4996
+ label: "Prompt",
4997
+ value: promptValue,
4998
+ rawValue: promptValue
4905
4999
  },
4906
5000
  {
4907
5001
  kind: "time",
@@ -4912,25 +5006,50 @@ var buildStripSegments = ({ taskId, captureTime, ip, location }) => {
4912
5006
  {
4913
5007
  kind: "location",
4914
5008
  label: "Loc",
4915
- value: shortenTail(location, 24) || "-",
4916
- rawValue: toInline(location, 80) || "-"
5009
+ value: locationValue,
5010
+ rawValue: locationValue
4917
5011
  },
4918
5012
  {
4919
5013
  kind: "ip",
4920
5014
  label: "IP",
4921
- value: toInline(ip, 28) || "-",
4922
- rawValue: toInline(ip, 80) || "-"
5015
+ value: ipValue,
5016
+ rawValue: ipValue
4923
5017
  }
4924
5018
  ];
4925
5019
  };
5020
+ var escapeXml = (value) => String(value || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
5021
+ var estimateTextWidth = (value, fontSize = 16) => {
5022
+ const text = String(value || "");
5023
+ let width = 0;
5024
+ for (const char of text) {
5025
+ if (/\s/.test(char)) {
5026
+ width += fontSize * 0.34;
5027
+ } else if (/[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u30ff\uac00-\ud7af]/.test(char)) {
5028
+ width += fontSize * 0.92;
5029
+ } else if (/[A-Z0-9]/.test(char)) {
5030
+ width += fontSize * 0.62;
5031
+ } else {
5032
+ width += fontSize * 0.56;
5033
+ }
5034
+ }
5035
+ return Math.ceil(width);
5036
+ };
5037
+ var resolvePromptFields = (options = {}, fallbackTitle = "") => {
5038
+ const query = normalizeText(options.query);
5039
+ const prompt = query || normalizeText(options.prompt) || normalizeText(fallbackTitle) || "\u672A\u63D0\u4F9B Prompt";
5040
+ return {
5041
+ prompt,
5042
+ query: query || prompt
5043
+ };
5044
+ };
4926
5045
  var createBaseWatermarkifyOptions = () => ({
4927
5046
  enabled: true,
4928
5047
  timezoneOffsetHours: DEFAULT_TIMEZONE_OFFSET,
4929
5048
  response: null,
4930
5049
  ip: "",
4931
5050
  location: "",
5051
+ prompt: "",
4932
5052
  query: "",
4933
- taskId: "",
4934
5053
  ipLookup: true,
4935
5054
  ipLookupTimeoutMs: DEFAULT_IP_LOOKUP_TIMEOUT_MS,
4936
5055
  resolver: null,
@@ -4973,8 +5092,8 @@ var normalizeScreenshotWatermarkify = (value) => {
4973
5092
  response: source.response ?? null,
4974
5093
  ip: normalizeText(source.ip),
4975
5094
  location: normalizeText(source.location),
4976
- query: toInline(source.query, 140),
4977
- taskId: toInline(source.taskId, 120),
5095
+ prompt: normalizeWhitespace(source.prompt),
5096
+ query: normalizeWhitespace(source.query),
4978
5097
  ipLookup: source.ipLookup !== false,
4979
5098
  ipLookupTimeoutMs: Number.isFinite(ipLookupTimeoutMsRaw) ? ipLookupTimeoutMsRaw : DEFAULT_IP_LOOKUP_TIMEOUT_MS,
4980
5099
  resolver: typeof source.resolver === "function" ? source.resolver : null,
@@ -4993,16 +5112,16 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
4993
5112
  const url = normalizeText(page?.url?.()) || "about:blank";
4994
5113
  const title = normalizeText(await page.title().catch(() => "")) || "\u672A\u547D\u540D\u9875\u9762";
4995
5114
  const hostname = getHostname(url);
4996
- const query = normalizeText(options.query) || title || "\u672A\u63D0\u4F9B Query";
4997
- const taskId = normalizeText(options.taskId) || "-";
4998
- const captureTime = formatTimestampForUtcOffset(/* @__PURE__ */ new Date(), timezoneOffsetHours);
5115
+ const { prompt, query } = resolvePromptFields(options, title);
5116
+ const capturedAt = options.capturedAt instanceof Date ? options.capturedAt : new Date(options.capturedAt || Date.now());
5117
+ const captureTime = formatTimestampForUtcOffset(capturedAt, timezoneOffsetHours);
4999
5118
  const [enrichment, stripLogoSrc] = await Promise.all([
5000
5119
  resolveEnrichment(page, {
5001
5120
  url,
5002
5121
  hostname,
5003
5122
  title,
5004
- query,
5005
- taskId
5123
+ prompt,
5124
+ query
5006
5125
  }, options),
5007
5126
  resolveStripLogoSrc()
5008
5127
  ]);
@@ -5010,13 +5129,13 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5010
5129
  const location = enrichment.location || "-";
5011
5130
  return {
5012
5131
  watermarkText: buildWatermarkStamp({
5013
- taskId,
5132
+ prompt,
5014
5133
  captureTime,
5015
5134
  ip,
5016
5135
  location
5017
5136
  }),
5018
5137
  stripSegments: buildStripSegments({
5019
- taskId,
5138
+ prompt,
5020
5139
  captureTime,
5021
5140
  ip,
5022
5141
  location
@@ -5026,396 +5145,509 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5026
5145
  stripLogoSrc
5027
5146
  };
5028
5147
  };
5029
- var installScreenshotWatermarkify = async (page, meta) => {
5030
- const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
5031
- const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
5032
- if (!page || !meta || !hasWatermark && !hasStrip) {
5033
- return async () => {
5034
- };
5148
+ var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
5149
+ var buildStripSegmentLayout = (segment, options = {}) => {
5150
+ const label = normalizeWhitespace(segment?.label || "");
5151
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5152
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5153
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5154
+ const labelWidth = Math.max(
5155
+ DEFAULT_STRIP_LABEL_WIDTH,
5156
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5157
+ );
5158
+ const rawValueWidth = Math.max(
5159
+ 24,
5160
+ estimateTextWidth(rawValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING
5161
+ );
5162
+ const maxValueWidth = Math.max(0, Number(options.valueMaxWidth) || 0);
5163
+ const renderValue = maxValueWidth > 0 ? fitTextWithEllipsis(rawValue, Math.max(18, maxValueWidth - DEFAULT_STRIP_VALUE_PADDING), valueFontSize) : rawValue;
5164
+ const valueWidth = maxValueWidth > 0 ? Math.max(24, Math.min(maxValueWidth, estimateTextWidth(renderValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING)) : rawValueWidth;
5165
+ return {
5166
+ ...segment,
5167
+ label,
5168
+ rawValue,
5169
+ renderValue,
5170
+ labelFontSize,
5171
+ valueFontSize,
5172
+ labelWidth,
5173
+ valueWidth,
5174
+ segmentWidth: labelWidth + valueWidth,
5175
+ rowHeight: Math.round(Math.max(labelFontSize, valueFontSize) * 1.42)
5176
+ };
5177
+ };
5178
+ var buildStripRowLayout = (segments, overrides = {}) => {
5179
+ const safeSegments = Array.isArray(segments) ? segments : [];
5180
+ const sectionGap = DEFAULT_STRIP_SECTION_GAP;
5181
+ const layouts = safeSegments.map((segment, index) => buildStripSegmentLayout(segment, {
5182
+ valueMaxWidth: overrides?.valueMaxWidths?.[index]
5183
+ }));
5184
+ const totalWidth = layouts.reduce((sum, segment) => sum + segment.segmentWidth, 0) + Math.max(0, layouts.length - 1) * sectionGap;
5185
+ const rowHeight = layouts.reduce((max, segment) => Math.max(max, segment.rowHeight || 0), 0);
5186
+ return {
5187
+ sectionGap,
5188
+ layouts,
5189
+ totalWidth,
5190
+ rowHeight
5191
+ };
5192
+ };
5193
+ var fitTextWithEllipsis = (chars, maxWidth, fontSize) => {
5194
+ const safeChars = Array.isArray(chars) ? chars.slice() : Array.from(String(chars || ""));
5195
+ if (!safeChars.length) {
5196
+ return "-";
5197
+ }
5198
+ if (estimateTextWidth(safeChars.join(""), fontSize) <= maxWidth) {
5199
+ return safeChars.join("").trim() || "-";
5035
5200
  }
5036
- await page.evaluate(({ hostId, watermarkifyMeta, defaults }) => {
5037
- const previousHost = document.getElementById(hostId);
5038
- if (previousHost && typeof previousHost.__pkCleanup === "function") {
5039
- previousHost.__pkCleanup();
5201
+ while (safeChars.length > 1 && estimateTextWidth(`${safeChars.join("").trimEnd()}\u2026`, fontSize) > maxWidth) {
5202
+ safeChars.pop();
5203
+ }
5204
+ return `${safeChars.join("").trimEnd()}\u2026` || "\u2026";
5205
+ };
5206
+ var splitTextToLines = (value, maxWidth, fontSize, maxLines = 2) => {
5207
+ const chars = Array.from(normalizeWhitespace(value) || "-");
5208
+ const safeMaxWidth = Math.max(18, maxWidth - DEFAULT_STRIP_TEXT_WIDTH_SAFETY);
5209
+ if (!chars.length) {
5210
+ return { lines: ["-"], truncated: false };
5211
+ }
5212
+ const lines = [];
5213
+ let cursor = 0;
5214
+ let truncated = false;
5215
+ while (cursor < chars.length && lines.length < maxLines) {
5216
+ while (cursor < chars.length && chars[cursor] === " ") {
5217
+ cursor += 1;
5040
5218
  }
5041
- previousHost?.remove();
5042
- const mountPoint = document.body || document.documentElement;
5043
- if (!mountPoint) return;
5044
- const createNode = (tagName, className) => {
5045
- const node = document.createElement(tagName);
5046
- if (className) node.className = className;
5047
- return node;
5048
- };
5049
- const safeText = (value) => String(value || "").trim();
5050
- const host = document.createElement("div");
5051
- host.id = hostId;
5052
- host.style.position = "fixed";
5053
- host.style.inset = "0";
5054
- host.style.width = "100vw";
5055
- host.style.height = "100vh";
5056
- host.style.pointerEvents = "none";
5057
- host.style.zIndex = "2147483646";
5058
- const shadow = host.attachShadow({ mode: "open" });
5059
- const style = document.createElement("style");
5060
- style.textContent = `
5061
- :host {
5062
- all: initial;
5063
- position: fixed;
5064
- inset: 0;
5065
- width: 100vw;
5066
- height: 100vh;
5067
- pointer-events: none;
5068
- z-index: 2147483646;
5069
- }
5070
-
5071
- .root {
5072
- position: absolute;
5073
- inset: 0;
5074
- width: 100%;
5075
- height: 100%;
5076
- overflow: hidden;
5077
- pointer-events: none;
5078
- font-family: MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
5079
- }
5080
-
5081
- .watermark {
5082
- position: absolute;
5083
- inset: 0;
5084
- overflow: hidden;
5085
- pointer-events: none;
5086
- }
5087
-
5088
- .wmStamp {
5089
- position: absolute;
5090
- left: 0;
5091
- top: 0;
5092
- transform-origin: left center;
5093
- pointer-events: none;
5094
- white-space: nowrap;
5095
- }
5096
-
5097
- .wmStampText {
5098
- color: rgba(17, 17, 17, var(--wm-opacity, 0.15));
5099
- font-size: 19px;
5100
- font-weight: 720;
5101
- line-height: 1;
5102
- letter-spacing: 0.032em;
5103
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.44);
5104
- -webkit-text-stroke: 0.22px rgba(255, 255, 255, 0.12);
5105
- font-variant-numeric: tabular-nums;
5106
- filter: saturate(0.92);
5107
- }
5108
-
5109
- .strip {
5110
- position: absolute;
5111
- left: 0;
5112
- right: 0;
5113
- bottom: 0;
5114
- min-height: 78px;
5115
- padding: 0 22px 0 20px;
5116
- display: flex;
5117
- align-items: center;
5118
- overflow: hidden;
5119
- border-top: 1px solid rgba(17, 17, 17, 0.1);
5120
- background:
5121
- linear-gradient(180deg, rgba(255, 255, 255, 0.995) 0%, rgba(248, 248, 247, 0.985) 100%);
5122
- box-shadow:
5123
- 0 -22px 48px rgba(15, 23, 42, 0.1),
5124
- inset 0 1px 0 rgba(255, 255, 255, 0.92);
5125
- backdrop-filter: blur(16px);
5126
- -webkit-backdrop-filter: blur(16px);
5127
- }
5128
-
5129
- .strip::before {
5130
- content: "";
5131
- position: absolute;
5132
- left: 20px;
5133
- right: 52%;
5134
- top: 0;
5135
- height: 2px;
5136
- border-radius: 999px;
5137
- background: linear-gradient(
5138
- 90deg,
5139
- rgba(17, 17, 17, 0.98) 0%,
5140
- rgba(17, 17, 17, 0.68) 24%,
5141
- rgba(17, 17, 17, 0.08) 58%,
5142
- rgba(17, 17, 17, 0) 100%
5143
- );
5144
- }
5145
-
5146
- .stripInner {
5147
- position: relative;
5148
- z-index: 1;
5149
- display: flex;
5150
- align-items: center;
5151
- gap: 16px;
5152
- width: 100%;
5153
- min-width: 0;
5154
- }
5155
-
5156
- .stripLogo {
5157
- position: relative;
5158
- width: 28px;
5159
- height: 28px;
5160
- flex: none;
5161
- display: flex;
5162
- align-items: center;
5163
- justify-content: center;
5164
- }
5165
-
5166
- .stripLogoImg {
5167
- width: 28px;
5168
- height: 28px;
5169
- object-fit: contain;
5170
- display: block;
5171
- }
5172
-
5173
- .segments {
5174
- display: flex;
5175
- align-items: center;
5176
- gap: 0;
5177
- flex: 1 1 auto;
5178
- min-width: 0;
5179
- white-space: nowrap;
5180
- }
5181
-
5182
- .segment {
5183
- position: relative;
5184
- display: inline-flex;
5185
- align-items: center;
5186
- gap: 10px;
5187
- min-width: 0;
5188
- flex: none;
5189
- padding-right: 16px;
5190
- margin-right: 16px;
5191
- }
5192
-
5193
- .segment:not(:last-child)::after {
5194
- content: "";
5195
- position: absolute;
5196
- top: 50%;
5197
- right: 0;
5198
- width: 1px;
5199
- height: 25px;
5200
- transform: translateY(-50%);
5201
- background: linear-gradient(
5202
- 180deg,
5203
- rgba(148, 163, 184, 0) 0%,
5204
- rgba(148, 163, 184, 0.34) 24%,
5205
- rgba(148, 163, 184, 0.34) 76%,
5206
- rgba(148, 163, 184, 0) 100%
5207
- );
5208
- }
5209
-
5210
- .segmentTaskId {
5211
- flex: 1 1 auto;
5212
- max-width: none;
5213
- min-width: 0;
5214
- }
5215
-
5216
- .label {
5217
- width: 52px;
5218
- color: rgba(17, 17, 17, 0.42);
5219
- font-size: 12px;
5220
- font-weight: 600;
5221
- line-height: 1;
5222
- letter-spacing: 0.01em;
5223
- flex: none;
5224
- display: inline-flex;
5225
- align-items: center;
5226
- justify-content: flex-start;
5227
- }
5228
-
5229
- .value {
5230
- color: #111111;
5231
- font-size: 18px;
5232
- font-weight: 710;
5233
- line-height: 1;
5234
- min-width: 0;
5235
- overflow: hidden;
5236
- text-overflow: ellipsis;
5237
- font-variant-numeric: tabular-nums;
5238
- display: inline-flex;
5239
- align-items: center;
5240
- }
5241
-
5242
- .segmentTaskId .value {
5243
- color: #101010;
5244
- font-size: 15px;
5245
- font-weight: 760;
5246
- letter-spacing: -0.01em;
5247
- }
5248
-
5249
- .segmentTime .value {
5250
- color: #202020;
5251
- font-size: 17px;
5252
- font-weight: 700;
5253
- }
5254
-
5255
- .segmentLocation .value,
5256
- .segmentIp .value {
5257
- color: #2a2a2a;
5258
- font-size: 17px;
5259
- font-weight: 700;
5260
- }
5261
-
5262
- `;
5263
- const root = createNode("div", "root");
5264
- const watermarkNode = createNode("div", "watermark");
5265
- const stripNode = createNode("div", "strip");
5266
- const stripInner = createNode("div", "stripInner");
5267
- const stripLogo = createNode("div", "stripLogo");
5268
- const stripLogoImg = createNode("img", "stripLogoImg");
5269
- const stripSegmentsNode = createNode("div", "segments");
5270
- stripLogoImg.alt = "";
5271
- stripLogoImg.decoding = "async";
5272
- stripLogoImg.referrerPolicy = "no-referrer";
5273
- stripLogoImg.src = safeText(watermarkifyMeta?.stripLogoSrc) || defaults.logoUrl;
5274
- stripLogo.appendChild(stripLogoImg);
5275
- stripInner.appendChild(stripLogo);
5276
- stripInner.appendChild(stripSegmentsNode);
5277
- stripNode.appendChild(stripInner);
5278
- root.appendChild(watermarkNode);
5279
- root.appendChild(stripNode);
5280
- shadow.appendChild(style);
5281
- shadow.appendChild(root);
5282
- mountPoint.appendChild(host);
5283
- const buildSegmentNode = (segment) => {
5284
- const kind = String(segment?.kind || "").trim();
5285
- const label = String(segment?.label || "").trim();
5286
- const value = String(segment?.value || "").trim();
5287
- const rawValue = String(segment?.rawValue || value).trim();
5288
- const classSuffix = kind ? ` segment${kind.slice(0, 1).toUpperCase()}${kind.slice(1)}` : "";
5289
- const segmentNode = createNode("span", `segment${classSuffix}`);
5290
- const labelNode = createNode("span", "label");
5291
- const valueNode = createNode("span", "value");
5292
- labelNode.textContent = label;
5293
- valueNode.textContent = value;
5294
- segmentNode.title = rawValue ? `${label} ${rawValue}` : label;
5295
- segmentNode.appendChild(labelNode);
5296
- segmentNode.appendChild(valueNode);
5297
- return segmentNode;
5298
- };
5299
- const renderStrip = () => {
5300
- const stripSegments = Array.isArray(watermarkifyMeta?.stripSegments) ? watermarkifyMeta.stripSegments : [];
5301
- if (!watermarkifyMeta?.strip?.enabled || stripSegments.length === 0) {
5302
- stripNode.remove();
5303
- return;
5304
- }
5305
- const fragment = document.createDocumentFragment();
5306
- for (const segment of stripSegments) {
5307
- fragment.appendChild(buildSegmentNode(segment));
5308
- }
5309
- stripSegmentsNode.replaceChildren(fragment);
5310
- };
5311
- const waitForLogo = async () => {
5312
- if (!stripLogoImg.getAttribute("src")) {
5313
- return;
5314
- }
5315
- await new Promise((resolve) => {
5316
- let settled = false;
5317
- const done = () => {
5318
- if (settled) return;
5319
- settled = true;
5320
- resolve();
5321
- };
5322
- const fail = () => {
5323
- if (settled) return;
5324
- stripLogo.remove();
5325
- done();
5326
- };
5327
- const timeoutDone = () => {
5328
- if (settled) return;
5329
- done();
5330
- };
5331
- if (stripLogoImg.complete && stripLogoImg.naturalWidth > 0) {
5332
- done();
5333
- return;
5334
- }
5335
- if (stripLogoImg.complete && stripLogoImg.naturalWidth === 0) {
5336
- fail();
5337
- return;
5338
- }
5339
- stripLogoImg.addEventListener("load", done, { once: true });
5340
- stripLogoImg.addEventListener("error", fail, { once: true });
5341
- setTimeout(timeoutDone, 2500);
5342
- });
5343
- };
5344
- const renderWatermark = () => {
5345
- const stampText = safeText(watermarkifyMeta?.watermarkText);
5346
- if (!watermarkifyMeta?.watermark?.enabled || !stampText) {
5347
- watermarkNode.remove();
5348
- return;
5349
- }
5350
- const cellWidth = Math.max(680, Number(watermarkifyMeta.watermark.cellWidth) || defaults.cellWidth);
5351
- const cellHeight = Math.max(260, Number(watermarkifyMeta.watermark.cellHeight) || defaults.cellHeight);
5352
- const rotateDeg = Number(watermarkifyMeta.watermark.rotateDeg) || defaults.rotateDeg;
5353
- const opacity = Math.min(0.22, Math.max(0.05, Number(watermarkifyMeta.watermark.opacity) || defaults.opacity));
5354
- const width = watermarkNode.clientWidth || window.innerWidth || document.documentElement.clientWidth || cellWidth;
5355
- const height = watermarkNode.clientHeight || window.innerHeight || document.documentElement.clientHeight || cellHeight;
5356
- const rowOffset = Math.round(cellWidth * 0.24);
5357
- const startX = -Math.round(cellWidth * 0.16);
5358
- const startY = -Math.round(cellHeight * 0.12);
5359
- const cols = Math.ceil((width + cellWidth * 1.1) / cellWidth) + 1;
5360
- const rows = Math.ceil((height + cellHeight * 0.9) / cellHeight) + 1;
5361
- const fragment = document.createDocumentFragment();
5362
- for (let row = 0; row < rows; row += 1) {
5363
- for (let col = 0; col < cols; col += 1) {
5364
- const stamp = createNode("div", "wmStamp");
5365
- const stampTextNode = createNode("div", "wmStampText");
5366
- const x = startX + col * cellWidth + (row % 2 ? rowOffset : 0);
5367
- const y = startY + row * cellHeight + (row % 2 ? 14 : -8);
5368
- stamp.style.transform = `translate(${x}px, ${y}px) rotate(${rotateDeg}deg)`;
5369
- stampTextNode.style.setProperty("--wm-opacity", String(opacity));
5370
- stampTextNode.textContent = stampText;
5371
- stamp.appendChild(stampTextNode);
5372
- fragment.appendChild(stamp);
5373
- }
5219
+ if (cursor >= chars.length) {
5220
+ break;
5221
+ }
5222
+ const isLastLine = lines.length === maxLines - 1;
5223
+ const lineChars = [];
5224
+ while (cursor < chars.length) {
5225
+ const nextChars = [...lineChars, chars[cursor]];
5226
+ const remaining = cursor < chars.length - 1;
5227
+ const previewText = nextChars.join("") + (isLastLine && remaining ? "\u2026" : "");
5228
+ if (estimateTextWidth(previewText, fontSize) <= safeMaxWidth || lineChars.length === 0) {
5229
+ lineChars.push(chars[cursor]);
5230
+ cursor += 1;
5231
+ continue;
5374
5232
  }
5375
- watermarkNode.replaceChildren(fragment);
5376
- };
5377
- let resizeFrame = 0;
5378
- const queueWatermarkRender = () => {
5379
- if (!watermarkNode.isConnected) return;
5380
- cancelAnimationFrame(resizeFrame);
5381
- resizeFrame = requestAnimationFrame(() => {
5382
- renderWatermark();
5233
+ break;
5234
+ }
5235
+ while (cursor < chars.length && chars[cursor] === " ") {
5236
+ cursor += 1;
5237
+ }
5238
+ if (isLastLine && cursor < chars.length) {
5239
+ lines.push(fitTextWithEllipsis([...lineChars, ...chars.slice(cursor)], safeMaxWidth, fontSize));
5240
+ truncated = true;
5241
+ cursor = chars.length;
5242
+ continue;
5243
+ }
5244
+ lines.push(lineChars.join("").trim() || "-");
5245
+ }
5246
+ return {
5247
+ lines: lines.length > 0 ? lines : ["-"],
5248
+ truncated
5249
+ };
5250
+ };
5251
+ var resolveMetadataMinValueWidth = (segment) => {
5252
+ switch (segment?.kind) {
5253
+ case "time":
5254
+ return Math.max(148, estimateTextWidth("2026-04-23 21:27:16 UTC+8", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5255
+ case "ip":
5256
+ return Math.max(108, estimateTextWidth("255.255.255.255", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5257
+ case "location":
5258
+ return 76;
5259
+ default:
5260
+ return 56;
5261
+ }
5262
+ };
5263
+ var buildMetadataRowLayout = (segments, contentWidth) => {
5264
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5265
+ const naturalRow = buildStripRowLayout(safeSegments);
5266
+ if (!safeSegments.length || naturalRow.totalWidth <= contentWidth) {
5267
+ return naturalRow;
5268
+ }
5269
+ const sectionGap = naturalRow.sectionGap;
5270
+ const totalLabelWidth = naturalRow.layouts.reduce((sum, segment) => sum + segment.labelWidth, 0);
5271
+ const availableValueWidth = Math.max(
5272
+ 72,
5273
+ Math.floor(contentWidth - totalLabelWidth - Math.max(0, safeSegments.length - 1) * sectionGap)
5274
+ );
5275
+ const minValueWidths = naturalRow.layouts.map((segment) => resolveMetadataMinValueWidth(segment));
5276
+ const naturalValueWidths = naturalRow.layouts.map((segment) => segment.valueWidth);
5277
+ const valueWidths = naturalValueWidths.slice();
5278
+ let overflow = naturalRow.totalWidth - contentWidth;
5279
+ const shrinkOrder = ["location", "ip", "time"];
5280
+ shrinkOrder.forEach((kind) => {
5281
+ if (overflow <= 0) return;
5282
+ naturalRow.layouts.forEach((segment, index) => {
5283
+ if (overflow <= 0 || segment.kind !== kind) return;
5284
+ const minWidth = minValueWidths[index];
5285
+ const reducible = Math.max(0, valueWidths[index] - minWidth);
5286
+ if (!reducible) return;
5287
+ const reduction = Math.min(reducible, overflow);
5288
+ valueWidths[index] -= reduction;
5289
+ overflow -= reduction;
5290
+ });
5291
+ });
5292
+ if (overflow > 0) {
5293
+ const minTotalWidth = minValueWidths.reduce((sum, width) => sum + width, 0);
5294
+ if (minTotalWidth > availableValueWidth) {
5295
+ const flexibleIndexes = naturalRow.layouts.map((segment, index) => ({ segment, index })).filter(({ segment }) => segment.kind !== "time");
5296
+ let extraOverflow = minTotalWidth - availableValueWidth;
5297
+ flexibleIndexes.forEach(({ index }) => {
5298
+ if (extraOverflow <= 0) return;
5299
+ const minFloor = 40;
5300
+ const reducible = Math.max(0, minValueWidths[index] - minFloor);
5301
+ if (!reducible) return;
5302
+ const reduction = Math.min(reducible, extraOverflow);
5303
+ valueWidths[index] -= reduction;
5304
+ extraOverflow -= reduction;
5383
5305
  });
5384
- };
5385
- const handleResize = () => {
5386
- queueWatermarkRender();
5387
- };
5388
- host.__pkCleanup = () => {
5389
- cancelAnimationFrame(resizeFrame);
5390
- window.removeEventListener("resize", handleResize);
5391
- };
5392
- renderStrip();
5393
- renderWatermark();
5394
- queueWatermarkRender();
5395
- window.addEventListener("resize", handleResize, { passive: true });
5396
- return waitForLogo();
5397
- }, {
5398
- hostId: SCREENSHOT_WATERMARKIFY_HOST_ID,
5399
- watermarkifyMeta: meta,
5400
- defaults: {
5401
- opacity: DEFAULT_WATERMARK_OPACITY,
5402
- rotateDeg: DEFAULT_WATERMARK_ROTATE_DEG,
5403
- cellWidth: DEFAULT_WATERMARK_CELL_WIDTH,
5404
- cellHeight: DEFAULT_WATERMARK_CELL_HEIGHT,
5405
- logoUrl: DEFAULT_STRIP_LOGO_URL
5406
5306
  }
5307
+ }
5308
+ return buildStripRowLayout(safeSegments, {
5309
+ valueMaxWidths: valueWidths
5407
5310
  });
5408
- return async () => {
5409
- await page.evaluate((hostId) => {
5410
- const host = document.getElementById(hostId);
5411
- if (host && typeof host.__pkCleanup === "function") {
5412
- host.__pkCleanup();
5413
- }
5414
- host?.remove();
5415
- }, SCREENSHOT_WATERMARKIFY_HOST_ID).catch(() => {
5416
- });
5311
+ };
5312
+ var buildPromptWrapLayout = (segment, contentWidth) => {
5313
+ const label = normalizeWhitespace(segment?.label || "Prompt") || "Prompt";
5314
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5315
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5316
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5317
+ const labelWidth = Math.max(
5318
+ DEFAULT_STRIP_LABEL_WIDTH,
5319
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5320
+ );
5321
+ const safeTotalWidth = Math.max(labelWidth + 56, Number(contentWidth) || 0);
5322
+ const valueWidth = Math.max(
5323
+ 56,
5324
+ safeTotalWidth - labelWidth - DEFAULT_STRIP_CONTENT_SAFE_PADDING_X
5325
+ );
5326
+ const result = splitTextToLines(rawValue, valueWidth, valueFontSize, DEFAULT_STRIP_PROMPT_MAX_LINES);
5327
+ const lineHeight = Math.max(18, Math.round(valueFontSize * DEFAULT_STRIP_LINE_HEIGHT_RATIO));
5328
+ const lineWidths = result.lines.map((line) => estimateTextWidth(line, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING);
5329
+ const measuredValueWidth = Math.min(
5330
+ valueWidth,
5331
+ Math.max(56, ...lineWidths)
5332
+ );
5333
+ const rowHeight = Math.round(Math.max(labelFontSize, valueFontSize) * 1.42);
5334
+ return {
5335
+ mode: result.lines.length === 1 ? "single" : "wrapped",
5336
+ label,
5337
+ rawValue,
5338
+ labelFontSize,
5339
+ labelWidth,
5340
+ valueFontSize,
5341
+ valueWidth,
5342
+ lines: result.lines,
5343
+ truncated: result.truncated,
5344
+ lineHeight,
5345
+ blockHeight: result.lines.length === 1 ? rowHeight : Math.max(lineHeight, result.lines.length * lineHeight),
5346
+ totalWidth: labelWidth + measuredValueWidth
5347
+ };
5348
+ };
5349
+ var buildStripLayout = (segments, contentWidth) => {
5350
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5351
+ const promptSegment = safeSegments.find((segment) => segment.kind === "prompt") || safeSegments[0] || null;
5352
+ const metadataSegments = safeSegments.filter((segment) => segment !== promptSegment);
5353
+ const zoneGap = DEFAULT_STRIP_ZONE_GAP;
5354
+ let metadataRow = buildMetadataRowLayout(
5355
+ metadataSegments,
5356
+ Math.max(220, contentWidth)
5357
+ );
5358
+ let metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5359
+ let promptAvailableWidth = Math.max(
5360
+ 220,
5361
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5362
+ );
5363
+ if (promptAvailableWidth < DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH && metadataSegments.length > 0) {
5364
+ const tighterMetadataWidth = Math.max(180, contentWidth - DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH - zoneGap);
5365
+ metadataRow = buildMetadataRowLayout(metadataSegments, tighterMetadataWidth);
5366
+ metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5367
+ promptAvailableWidth = Math.max(
5368
+ 220,
5369
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5370
+ );
5371
+ }
5372
+ const preferredPromptWidth = Math.min(
5373
+ promptAvailableWidth,
5374
+ Math.max(280, Math.round(contentWidth * DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO))
5375
+ );
5376
+ let promptRow = buildPromptWrapLayout(promptSegment, preferredPromptWidth);
5377
+ if (promptRow?.truncated && promptAvailableWidth > preferredPromptWidth + 24) {
5378
+ promptRow = buildPromptWrapLayout(promptSegment, promptAvailableWidth);
5379
+ }
5380
+ const contentHeight = Math.max(
5381
+ promptRow?.blockHeight || 0,
5382
+ metadataRow?.rowHeight || 0,
5383
+ 24
5384
+ );
5385
+ const verticalPadding = contentHeight > (metadataRow?.rowHeight || 0) ? 18 : 16;
5386
+ const stripHeight = Math.max(
5387
+ contentHeight > (metadataRow?.rowHeight || 0) ? DEFAULT_STRIP_WRAPPED_MIN_HEIGHT : DEFAULT_STRIP_ONE_LINE_HEIGHT,
5388
+ Math.ceil(contentHeight + verticalPadding * 2)
5389
+ );
5390
+ return {
5391
+ mode: "zoned",
5392
+ height: stripHeight,
5393
+ contentHeight,
5394
+ promptRow,
5395
+ promptStartX: 0,
5396
+ metadataRow,
5397
+ metadataStartX
5417
5398
  };
5418
5399
  };
5400
+ var renderStripRow = (row, options = {}) => {
5401
+ const startX = Number(options.startX) || 0;
5402
+ const centerY = Number(options.centerY) || 0;
5403
+ const fontFamily = String(options.fontFamily || "");
5404
+ const showDividers = options.showDividers !== false;
5405
+ const layouts = Array.isArray(row?.layouts) ? row.layouts : [];
5406
+ if (!layouts.length) {
5407
+ return "";
5408
+ }
5409
+ const rowHeight = Math.max(18, Number(row?.rowHeight) || 18);
5410
+ const dividerHalfHeight = Math.max(12, Math.round(rowHeight * 0.56));
5411
+ const sectionGap = Math.max(0, Number(row?.sectionGap) || 0);
5412
+ const svgSegments = [];
5413
+ let currentX = startX;
5414
+ layouts.forEach((segment, index) => {
5415
+ const label = escapeXml(segment.label || "");
5416
+ const value = escapeXml(segment.renderValue || "-");
5417
+ const labelX = currentX;
5418
+ const valueX = currentX + segment.labelWidth;
5419
+ svgSegments.push(`
5420
+ <text
5421
+ x="${labelX}"
5422
+ y="${centerY}"
5423
+ fill="#111111"
5424
+ fill-opacity="0.54"
5425
+ font-family="${fontFamily}"
5426
+ font-size="${segment.labelFontSize}"
5427
+ font-weight="650"
5428
+ dominant-baseline="middle"
5429
+ >${label}</text>
5430
+ <text
5431
+ x="${valueX}"
5432
+ y="${centerY}"
5433
+ fill="#111111"
5434
+ font-family="${fontFamily}"
5435
+ font-size="${segment.valueFontSize}"
5436
+ font-weight="${segment.kind === "prompt" ? 760 : 710}"
5437
+ dominant-baseline="middle"
5438
+ >${value}</text>
5439
+ `);
5440
+ currentX += segment.segmentWidth;
5441
+ if (showDividers && index < layouts.length - 1) {
5442
+ const lineX = currentX + Math.round(sectionGap / 2);
5443
+ svgSegments.push(`
5444
+ <line
5445
+ x1="${lineX}"
5446
+ y1="${centerY - dividerHalfHeight}"
5447
+ x2="${lineX}"
5448
+ y2="${centerY + dividerHalfHeight}"
5449
+ stroke="#94a3b8"
5450
+ stroke-opacity="0.34"
5451
+ stroke-width="1"
5452
+ />
5453
+ `);
5454
+ currentX += sectionGap;
5455
+ }
5456
+ });
5457
+ return svgSegments.join("");
5458
+ };
5459
+ var renderStripDivider = (x, centerY, height) => {
5460
+ const safeX = Number(x) || 0;
5461
+ const safeCenterY = Number(centerY) || 0;
5462
+ const safeHeight = Math.max(18, Number(height) || 18);
5463
+ const halfHeight = Math.max(12, Math.round(safeHeight * 0.56));
5464
+ return `
5465
+ <line
5466
+ x1="${safeX}"
5467
+ y1="${safeCenterY - halfHeight}"
5468
+ x2="${safeX}"
5469
+ y2="${safeCenterY + halfHeight}"
5470
+ stroke="#94a3b8"
5471
+ stroke-opacity="0.34"
5472
+ stroke-width="1"
5473
+ />
5474
+ `;
5475
+ };
5476
+ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
5477
+ const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
5478
+ const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
5479
+ if (!hasWatermark && !hasStrip) {
5480
+ return "";
5481
+ }
5482
+ const width = Math.max(1, Number(imageWidth) || 1);
5483
+ const height = Math.max(1, Number(imageHeight) || 1);
5484
+ const fontFamily = escapeXml(buildFontFamily());
5485
+ const parts = [];
5486
+ if (hasWatermark) {
5487
+ const stampText = escapeXml(normalizeText(meta.watermarkText));
5488
+ const cellWidth = Math.max(680, Number(meta.watermark?.cellWidth) || DEFAULT_WATERMARK_CELL_WIDTH);
5489
+ const cellHeight = Math.max(260, Number(meta.watermark?.cellHeight) || DEFAULT_WATERMARK_CELL_HEIGHT);
5490
+ const rotateDeg = Number(meta.watermark?.rotateDeg) || DEFAULT_WATERMARK_ROTATE_DEG;
5491
+ const opacity = Math.min(0.22, Math.max(0.05, Number(meta.watermark?.opacity) || DEFAULT_WATERMARK_OPACITY));
5492
+ const rowOffset = Math.round(cellWidth * 0.24);
5493
+ const startX = -Math.round(cellWidth * 0.16);
5494
+ const startY = -Math.round(cellHeight * 0.12);
5495
+ const cols = Math.ceil((width + cellWidth * 1.1) / cellWidth) + 1;
5496
+ const rows = Math.ceil((height + cellHeight * 0.9) / cellHeight) + 1;
5497
+ const stamps = [];
5498
+ for (let row = 0; row < rows; row += 1) {
5499
+ for (let col = 0; col < cols; col += 1) {
5500
+ const x = startX + col * cellWidth + (row % 2 ? rowOffset : 0);
5501
+ const y = startY + row * cellHeight + (row % 2 ? 14 : -8);
5502
+ stamps.push(`
5503
+ <g transform="translate(${x} ${y}) rotate(${rotateDeg})">
5504
+ <text
5505
+ x="0"
5506
+ y="0"
5507
+ fill="#111111"
5508
+ fill-opacity="${opacity}"
5509
+ stroke="#ffffff"
5510
+ stroke-opacity="0.12"
5511
+ stroke-width="0.22"
5512
+ font-family="${fontFamily}"
5513
+ font-size="19"
5514
+ font-weight="720"
5515
+ letter-spacing="0.6"
5516
+ >${stampText}</text>
5517
+ </g>
5518
+ `);
5519
+ }
5520
+ }
5521
+ parts.push(`<g id="watermarks">${stamps.join("")}</g>`);
5522
+ }
5523
+ if (hasStrip) {
5524
+ const logoSize = 28;
5525
+ const logoX = DEFAULT_STRIP_PADDING_LEFT;
5526
+ const contentStartX = logoX + logoSize + DEFAULT_STRIP_GAP;
5527
+ const contentWidth = width - contentStartX - DEFAULT_STRIP_PADDING_RIGHT;
5528
+ const stripLayout = buildStripLayout(meta.stripSegments, contentWidth);
5529
+ const stripHeight = stripLayout.height;
5530
+ const stripY = height - stripHeight;
5531
+ const logoY = Number((stripY + (stripHeight - logoSize) / 2).toFixed(2));
5532
+ let stripContentSvg = "";
5533
+ const centerY = stripY + stripHeight / 2 + 1;
5534
+ const promptRow = stripLayout.promptRow;
5535
+ const metadataRow = stripLayout.metadataRow;
5536
+ const promptParts = [];
5537
+ const hasPrompt = Boolean(promptRow?.label);
5538
+ const hasMetadata = Array.isArray(metadataRow?.layouts) && metadataRow.layouts.length > 0;
5539
+ const zoneDividerX = hasPrompt && hasMetadata ? contentStartX + stripLayout.metadataStartX - DEFAULT_STRIP_ZONE_GAP / 2 : null;
5540
+ if (promptRow?.mode === "single") {
5541
+ promptParts.push(renderStripRow({
5542
+ layouts: [
5543
+ {
5544
+ kind: "prompt",
5545
+ label: promptRow.label,
5546
+ renderValue: promptRow.lines[0] || "-",
5547
+ labelFontSize: promptRow.labelFontSize,
5548
+ valueFontSize: promptRow.valueFontSize,
5549
+ labelWidth: promptRow.labelWidth,
5550
+ segmentWidth: promptRow.labelWidth + promptRow.valueWidth
5551
+ }
5552
+ ],
5553
+ sectionGap: 0,
5554
+ rowHeight: promptRow.blockHeight
5555
+ }, {
5556
+ startX: contentStartX + stripLayout.promptStartX,
5557
+ centerY,
5558
+ fontFamily,
5559
+ showDividers: false
5560
+ }));
5561
+ } else if (promptRow) {
5562
+ const promptStartX = contentStartX + stripLayout.promptStartX;
5563
+ const labelX = promptStartX;
5564
+ const valueX = promptStartX + promptRow.labelWidth;
5565
+ const blockTop = centerY - promptRow.blockHeight / 2;
5566
+ const labelY = centerY;
5567
+ promptParts.push(`
5568
+ <text
5569
+ x="${labelX}"
5570
+ y="${labelY}"
5571
+ fill="#111111"
5572
+ fill-opacity="0.54"
5573
+ font-family="${fontFamily}"
5574
+ font-size="${promptRow.labelFontSize}"
5575
+ font-weight="650"
5576
+ dominant-baseline="middle"
5577
+ >${escapeXml(promptRow.label)}</text>
5578
+ `);
5579
+ promptRow.lines.forEach((line, index) => {
5580
+ const lineY = blockTop + index * promptRow.lineHeight + promptRow.lineHeight / 2;
5581
+ promptParts.push(`
5582
+ <text
5583
+ x="${valueX}"
5584
+ y="${lineY}"
5585
+ fill="#111111"
5586
+ font-family="${fontFamily}"
5587
+ font-size="${promptRow.valueFontSize}"
5588
+ font-weight="760"
5589
+ dominant-baseline="middle"
5590
+ >${escapeXml(line)}</text>
5591
+ `);
5592
+ });
5593
+ }
5594
+ stripContentSvg = [
5595
+ promptParts.join(""),
5596
+ zoneDividerX == null ? "" : renderStripDivider(
5597
+ zoneDividerX,
5598
+ centerY,
5599
+ Math.max(promptRow?.blockHeight || 0, metadataRow?.rowHeight || 0)
5600
+ ),
5601
+ renderStripRow(metadataRow, {
5602
+ startX: contentStartX + stripLayout.metadataStartX,
5603
+ centerY,
5604
+ fontFamily,
5605
+ showDividers: true
5606
+ })
5607
+ ].join("");
5608
+ parts.push(`
5609
+ <g id="strip">
5610
+ <rect x="0" y="${stripY}" width="${width}" height="${stripHeight}" fill="#ffffff" fill-opacity="0.985" />
5611
+ <rect x="0" y="${stripY}" width="${width}" height="1" fill="#111111" fill-opacity="0.10" />
5612
+ <rect x="${DEFAULT_STRIP_PADDING_LEFT}" y="${stripY}" width="${Math.max(120, Math.round(width * 0.42))}" height="2" fill="url(#stripAccent)" />
5613
+ ${meta.stripLogoSrc ? `<image href="${escapeXml(meta.stripLogoSrc)}" x="${logoX}" y="${logoY}" width="${logoSize}" height="${logoSize}" />` : ""}
5614
+ ${stripContentSvg}
5615
+ </g>
5616
+ `);
5617
+ }
5618
+ return `
5619
+ <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
5620
+ <defs>
5621
+ <linearGradient id="stripAccent" x1="0%" y1="0%" x2="100%" y2="0%">
5622
+ <stop offset="0%" stop-color="#111111" stop-opacity="0.98" />
5623
+ <stop offset="24%" stop-color="#111111" stop-opacity="0.68" />
5624
+ <stop offset="58%" stop-color="#111111" stop-opacity="0.08" />
5625
+ <stop offset="100%" stop-color="#111111" stop-opacity="0" />
5626
+ </linearGradient>
5627
+ </defs>
5628
+ ${parts.join("")}
5629
+ </svg>
5630
+ `;
5631
+ };
5632
+ var watermarkifyScreenshotBuffer = async (buffer, meta) => {
5633
+ const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
5634
+ const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
5635
+ if (!Buffer.isBuffer(buffer) || !meta || !hasWatermark && !hasStrip) {
5636
+ return buffer;
5637
+ }
5638
+ const image = sharp(buffer, { failOn: "none" });
5639
+ const metadata = await image.metadata().catch(() => null);
5640
+ const width = Math.max(1, Number(metadata?.width) || 0);
5641
+ const height = Math.max(1, Number(metadata?.height) || 0);
5642
+ if (!width || !height) {
5643
+ return buffer;
5644
+ }
5645
+ const overlaySvg = buildWatermarkifySvg(meta, width, height);
5646
+ if (!overlaySvg) {
5647
+ return buffer;
5648
+ }
5649
+ return await image.composite([{ input: Buffer.from(overlaySvg), top: 0, left: 0 }]).png().toBuffer();
5650
+ };
5419
5651
 
5420
5652
  // src/share.js
5421
5653
  var logger11 = createInternalLogger("Share");
@@ -5495,6 +5727,20 @@ var normalizeShare = (share) => {
5495
5727
  xurl: normalizeXurl(source.xurl)
5496
5728
  };
5497
5729
  };
5730
+ var resolveCaptureScreenWatermarkify = (page, optionValue) => {
5731
+ if (optionValue === false) {
5732
+ return normalizeScreenshotWatermarkify(false);
5733
+ }
5734
+ const runtimeQuery = String(page?.[PageRuntimeStateKey]?.query || "").trim();
5735
+ if (optionValue == null || optionValue === true) {
5736
+ return normalizeScreenshotWatermarkify(runtimeQuery ? { query: runtimeQuery } : true);
5737
+ }
5738
+ const optionSource = optionValue && typeof optionValue === "object" ? optionValue : {};
5739
+ return normalizeScreenshotWatermarkify({
5740
+ ...runtimeQuery ? { query: runtimeQuery } : {},
5741
+ ...optionSource
5742
+ });
5743
+ };
5498
5744
  var getByPathSegments = (source, pathSegments) => {
5499
5745
  if (!Array.isArray(pathSegments) || pathSegments.length === 0) return void 0;
5500
5746
  let current = source;
@@ -5818,14 +6064,12 @@ var Share = {
5818
6064
  * @returns {Promise<string>} base64 png
5819
6065
  */
5820
6066
  async captureScreen(page, options = {}) {
5821
- const originalViewport = page.viewportSize();
5822
- const defaultBuffer = Math.round((originalViewport?.height || 1080) / 2);
6067
+ const originalViewport = await resolveCurrentViewportSize(page);
6068
+ const defaultBuffer = Math.round((originalViewport.height || 1080) / 2);
5823
6069
  const buffer = options.buffer ?? defaultBuffer;
5824
6070
  const restore = options.restore ?? false;
5825
6071
  const maxHeight = options.maxHeight ?? 8e3;
5826
- const screenshotWatermarkify = normalizeScreenshotWatermarkify(options.watermarkify ?? true);
5827
- let cleanupScreenshotWatermarkify = async () => {
5828
- };
6072
+ const screenshotWatermarkify = resolveCaptureScreenWatermarkify(page, options.watermarkify);
5829
6073
  try {
5830
6074
  const maxScrollHeight = await page.evaluate(() => {
5831
6075
  let maxHeight2 = document.body.scrollHeight;
@@ -5849,23 +6093,26 @@ var Share = {
5849
6093
  });
5850
6094
  const targetHeight = Math.min(maxScrollHeight + buffer, maxHeight);
5851
6095
  await page.setViewportSize({
5852
- width: originalViewport?.width || 1280,
6096
+ width: originalViewport.width,
5853
6097
  height: targetHeight
5854
6098
  });
5855
6099
  await delay2(1e3);
5856
- if (screenshotWatermarkify.enabled) {
5857
- const watermarkifyMeta = await resolveScreenshotWatermarkifyMeta(page, screenshotWatermarkify);
5858
- cleanupScreenshotWatermarkify = await installScreenshotWatermarkify(page, watermarkifyMeta);
5859
- await delay2(120);
5860
- }
5861
- const buffer_ = await capturePageScreenshot(page, {
6100
+ const capturedAt = /* @__PURE__ */ new Date();
6101
+ const rawBuffer = await capturePageScreenshot(page, {
5862
6102
  fullPage: true,
5863
6103
  type: "png",
5864
6104
  maxClipHeight: targetHeight
5865
6105
  });
6106
+ if (!screenshotWatermarkify.enabled) {
6107
+ return rawBuffer.toString("base64");
6108
+ }
6109
+ const watermarkifyMeta = await resolveScreenshotWatermarkifyMeta(page, {
6110
+ ...screenshotWatermarkify,
6111
+ capturedAt
6112
+ });
6113
+ const buffer_ = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta);
5866
6114
  return buffer_.toString("base64");
5867
6115
  } finally {
5868
- await cleanupScreenshotWatermarkify();
5869
6116
  if (restore) {
5870
6117
  await page.evaluate(() => {
5871
6118
  document.querySelectorAll(".__pk_expanded__").forEach((el) => {
@@ -5878,9 +6125,7 @@ var Share = {
5878
6125
  el.classList.remove("__pk_expanded__");
5879
6126
  });
5880
6127
  });
5881
- if (originalViewport) {
5882
- await page.setViewportSize(originalViewport);
5883
- }
6128
+ await page.setViewportSize(originalViewport);
5884
6129
  }
5885
6130
  }
5886
6131
  }