@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.cjs CHANGED
@@ -435,6 +435,33 @@ function createInternalLogger(moduleName, explicitLogger) {
435
435
  };
436
436
  }
437
437
 
438
+ // src/internals/viewport.js
439
+ var toPositiveInt = (value) => {
440
+ const number = Math.round(Number(value) || 0);
441
+ return number > 0 ? number : 0;
442
+ };
443
+ var resolveCurrentViewportSize = async (page, fallback = {}) => {
444
+ const directViewport = page?.viewportSize?.();
445
+ const directWidth = toPositiveInt(directViewport?.width);
446
+ const directHeight = toPositiveInt(directViewport?.height);
447
+ if (directWidth && directHeight) {
448
+ return {
449
+ width: directWidth,
450
+ height: directHeight
451
+ };
452
+ }
453
+ const measuredViewport = await page?.evaluate?.(() => ({
454
+ width: window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0,
455
+ height: window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0
456
+ })).catch(() => null);
457
+ const fallbackWidth = toPositiveInt(fallback?.width);
458
+ const fallbackHeight = toPositiveInt(fallback?.height);
459
+ return {
460
+ width: toPositiveInt(measuredViewport?.width) || fallbackWidth || 1,
461
+ height: toPositiveInt(measuredViewport?.height) || fallbackHeight || 1
462
+ };
463
+ };
464
+
438
465
  // src/internals/screenshot.js
439
466
  var logger = createInternalLogger("Screenshot");
440
467
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -461,7 +488,7 @@ var normalizeQuality = (value, type) => {
461
488
  };
462
489
  var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
463
490
  const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
464
- const width = Math.max(1, Math.ceil(contentSize?.width || viewport.width || 1));
491
+ const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
465
492
  let height = Math.max(1, Math.ceil(contentSize?.height || viewport.height || 1));
466
493
  if (maxClipHeight > 0) {
467
494
  height = Math.min(height, maxClipHeight);
@@ -498,7 +525,7 @@ var capturePageScreenshot = async (page, options = {}) => {
498
525
  const session = await context.newCDPSession(page);
499
526
  try {
500
527
  const metrics = await session.send("Page.getLayoutMetrics");
501
- const viewport = page.viewportSize() || { width: 1280, height: 720 };
528
+ const viewport = await resolveCurrentViewportSize(page);
502
529
  const captureParams = {
503
530
  format: type,
504
531
  fromSurface: true,
@@ -952,6 +979,9 @@ var ProxyMeterRuntime = {
952
979
  getProxyMeterSnapshot
953
980
  };
954
981
 
982
+ // src/internals/constants.js
983
+ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
984
+
955
985
  // src/runtime-env.js
956
986
  var BROWSER_PROFILE_SCHEMA_VERSION = 1;
957
987
  var rememberedRuntimeState = null;
@@ -1458,6 +1488,7 @@ var RuntimeEnv = {
1458
1488
  parseInput(input = {}, actor = "") {
1459
1489
  const runtime2 = tryParseJSON(input?.runtime) || {};
1460
1490
  const resolvedActor = String(actor || input?.actor || "").trim();
1491
+ const query = String(input?.query || "").trim();
1461
1492
  const cookies = normalizeCookies(runtime2?.cookies);
1462
1493
  const cookieMap = buildCookieMap(cookies);
1463
1494
  const localStorage = normalizeLocalStorage(runtime2?.local_storage);
@@ -1481,6 +1512,7 @@ var RuntimeEnv = {
1481
1512
  actor: resolvedActor,
1482
1513
  runtime: normalizedRuntime,
1483
1514
  envId,
1515
+ query,
1484
1516
  auth,
1485
1517
  cookies,
1486
1518
  cookieMap,
@@ -1548,6 +1580,12 @@ var RuntimeEnv = {
1548
1580
  async applyToPage(page, source = {}, options = {}) {
1549
1581
  if (!page) return;
1550
1582
  const state = normalizeRuntimeState(source, options?.actor || "");
1583
+ Object.defineProperty(page, PageRuntimeStateKey, {
1584
+ configurable: true,
1585
+ enumerable: false,
1586
+ writable: true,
1587
+ value: state
1588
+ });
1551
1589
  const localStorage = state.localStorage || {};
1552
1590
  const sessionStorage = state.sessionStorage || {};
1553
1591
  const cookies = (state.cookies || []).map((cookie) => {
@@ -4549,7 +4587,7 @@ var Logger = {
4549
4587
  var import_delay2 = __toESM(require("delay"), 1);
4550
4588
 
4551
4589
  // src/internals/watermarkify.js
4552
- var SCREENSHOT_WATERMARKIFY_HOST_ID = "__pk_screenshot_watermarkify__";
4590
+ var import_sharp = __toESM(require("sharp"), 1);
4553
4591
  var DEFAULT_TIMEZONE_OFFSET = 8;
4554
4592
  var DEFAULT_RESOLVER_TIMEOUT_MS = 180;
4555
4593
  var DEFAULT_IP_LOOKUP_TIMEOUT_MS = 1e4;
@@ -4560,6 +4598,26 @@ var DEFAULT_WATERMARK_CELL_HEIGHT = 330;
4560
4598
  var DEFAULT_STRIP_LOGO_URL = "https://static.heartbitai.com/geo/icon/favicon.png";
4561
4599
  var DEFAULT_IP_LOOKUP_URL = "http://myip.ipip.net";
4562
4600
  var DEFAULT_LOGO_FETCH_TIMEOUT_MS = 2500;
4601
+ var DEFAULT_STRIP_ONE_LINE_HEIGHT = 78;
4602
+ var DEFAULT_STRIP_WRAPPED_MIN_HEIGHT = 108;
4603
+ var DEFAULT_STRIP_PADDING_LEFT = 22;
4604
+ var DEFAULT_STRIP_PADDING_RIGHT = 28;
4605
+ var DEFAULT_STRIP_GAP = 18;
4606
+ var DEFAULT_STRIP_LABEL_WIDTH = 56;
4607
+ var DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS = 180;
4608
+ var DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH = 320;
4609
+ var DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO = 0.58;
4610
+ var DEFAULT_STRIP_ZONE_GAP = 18;
4611
+ var DEFAULT_STRIP_PROMPT_TRAILING_PADDING = 16;
4612
+ var DEFAULT_STRIP_LABEL_FONT_SIZE = 14.5;
4613
+ var DEFAULT_STRIP_VALUE_FONT_SIZE = 15.5;
4614
+ var DEFAULT_STRIP_VALUE_PADDING = 10;
4615
+ var DEFAULT_STRIP_LABEL_PADDING = 14;
4616
+ var DEFAULT_STRIP_SECTION_GAP = DEFAULT_STRIP_GAP + 16;
4617
+ var DEFAULT_STRIP_LINE_HEIGHT_RATIO = 1.28;
4618
+ var DEFAULT_STRIP_PROMPT_MAX_LINES = 3;
4619
+ var DEFAULT_STRIP_CONTENT_SAFE_PADDING_X = 12;
4620
+ var DEFAULT_STRIP_TEXT_WIDTH_SAFETY = 10;
4563
4621
  var WEAK_LOCATION_VALUES = /* @__PURE__ */ new Set(["cn", "\u4E2D\u56FD"]);
4564
4622
  var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4565
4623
  /(?:中国)?移动$/i,
@@ -4584,6 +4642,7 @@ var LOCATION_NETWORK_SUFFIX_PATTERNS = [
4584
4642
  /digitalocean$/i
4585
4643
  ];
4586
4644
  var cachedStripLogoSrcPromise = null;
4645
+ var cachedEnrichmentByContext = /* @__PURE__ */ new WeakMap();
4587
4646
  var normalizeText = (value) => String(value || "").trim();
4588
4647
  var toInline = (value, maxLen = 200) => {
4589
4648
  const text = normalizeText(value);
@@ -4595,12 +4654,14 @@ var shortenTail = (value, maxLen = 80) => {
4595
4654
  if (!text || text.length <= maxLen) return text;
4596
4655
  return `${text.slice(0, Math.max(0, maxLen - 1)).trimEnd()}\u2026`;
4597
4656
  };
4598
- var shortenMiddle = (value, maxLen = 56, headLen = 32, tailLen = 14) => {
4599
- const text = toInline(value, Math.max(maxLen * 2, maxLen + headLen + tailLen));
4600
- if (!text || text.length <= maxLen) return text;
4601
- const safeHeadLen = Math.max(4, Math.min(headLen, maxLen - 5));
4602
- const safeTailLen = Math.max(4, Math.min(tailLen, maxLen - safeHeadLen - 1));
4603
- return `${text.slice(0, safeHeadLen).trimEnd()}\u2026${text.slice(-safeTailLen).trimStart()}`;
4657
+ var shortenByCharacters = (value, maxChars = 80) => {
4658
+ const text = normalizeWhitespace(value);
4659
+ if (!text) return "";
4660
+ const chars = Array.from(text);
4661
+ if (chars.length <= maxChars) {
4662
+ return text;
4663
+ }
4664
+ return `${chars.slice(0, Math.max(0, maxChars - 1)).join("").trimEnd()}\u2026`;
4604
4665
  };
4605
4666
  var padDatePart = (value) => String(value).padStart(2, "0");
4606
4667
  var formatUtcOffsetLabel = (offsetHours = DEFAULT_TIMEZONE_OFFSET) => {
@@ -4674,6 +4735,29 @@ var fillEnrichment = (target, source) => {
4674
4735
  }
4675
4736
  return target;
4676
4737
  };
4738
+ var readCachedEnrichment = (page) => {
4739
+ const context = page && typeof page.context === "function" ? page.context() : null;
4740
+ if (!context) {
4741
+ return null;
4742
+ }
4743
+ const cached = cachedEnrichmentByContext.get(context);
4744
+ return cached && typeof cached === "object" ? cached : null;
4745
+ };
4746
+ var writeCachedEnrichment = (page, source) => {
4747
+ const context = page && typeof page.context === "function" ? page.context() : null;
4748
+ if (!context || !source || typeof source !== "object") {
4749
+ return;
4750
+ }
4751
+ const current = cachedEnrichmentByContext.get(context) || {};
4752
+ const next = {
4753
+ ip: toInline(source.ip || current.ip, 80),
4754
+ location: toInline(source.location || current.location, 80)
4755
+ };
4756
+ if (!next.ip && !next.location) {
4757
+ return;
4758
+ }
4759
+ cachedEnrichmentByContext.set(context, next);
4760
+ };
4677
4761
  var getHostname = (url) => {
4678
4762
  try {
4679
4763
  const parsed = new URL(url);
@@ -4777,8 +4861,8 @@ var resolveWithCustomResolver = async (page, baseMeta, options = {}) => {
4777
4861
  url: baseMeta.url,
4778
4862
  hostname: baseMeta.hostname,
4779
4863
  title: baseMeta.title,
4864
+ prompt: baseMeta.prompt,
4780
4865
  query: baseMeta.query,
4781
- taskId: baseMeta.taskId,
4782
4866
  serverAddr,
4783
4867
  signal: controller?.signal
4784
4868
  })).catch(() => null),
@@ -4803,44 +4887,43 @@ var openProbePage = async (page) => {
4803
4887
  if (!context) {
4804
4888
  return null;
4805
4889
  }
4890
+ const browser = typeof context.browser === "function" ? context.browser() : null;
4891
+ if (browser && typeof browser.newContext === "function") {
4892
+ const probeContext = await browser.newContext().catch(() => null);
4893
+ if (probeContext && typeof probeContext.newPage === "function") {
4894
+ const probePage = await probeContext.newPage().catch(async () => {
4895
+ await probeContext.close().catch(() => {
4896
+ });
4897
+ return null;
4898
+ });
4899
+ if (probePage) {
4900
+ return {
4901
+ page: probePage,
4902
+ close: async () => {
4903
+ await probeContext.close().catch(() => {
4904
+ });
4905
+ }
4906
+ };
4907
+ }
4908
+ } else {
4909
+ await probeContext?.close?.().catch(() => {
4910
+ });
4911
+ }
4912
+ }
4806
4913
  if (typeof context.newPage === "function") {
4807
4914
  try {
4808
- const probePage2 = await context.newPage();
4915
+ const probePage = await context.newPage();
4809
4916
  return {
4810
- page: probePage2,
4917
+ page: probePage,
4811
4918
  close: async () => {
4812
- await probePage2.close().catch(() => {
4919
+ await probePage.close().catch(() => {
4813
4920
  });
4814
4921
  }
4815
4922
  };
4816
4923
  } catch {
4817
4924
  }
4818
4925
  }
4819
- const browser = typeof context.browser === "function" ? context.browser() : null;
4820
- if (!browser || typeof browser.newContext !== "function") {
4821
- return null;
4822
- }
4823
- const probeContext = await browser.newContext().catch(() => null);
4824
- if (!probeContext || typeof probeContext.newPage !== "function") {
4825
- await probeContext?.close?.().catch(() => {
4826
- });
4827
- return null;
4828
- }
4829
- const probePage = await probeContext.newPage().catch(async () => {
4830
- await probeContext.close().catch(() => {
4831
- });
4832
- return null;
4833
- });
4834
- if (!probePage) {
4835
- return null;
4836
- }
4837
- return {
4838
- page: probePage,
4839
- close: async () => {
4840
- await probeContext.close().catch(() => {
4841
- });
4842
- }
4843
- };
4926
+ return null;
4844
4927
  };
4845
4928
  var resolveWithIpLookup = async (page, options = {}) => {
4846
4929
  if (!page || typeof page.context !== "function" || options.ipLookup === false) {
@@ -4885,10 +4968,14 @@ var resolveWithIpLookup = async (page, options = {}) => {
4885
4968
  };
4886
4969
  var resolveEnrichment = async (page, baseMeta, options) => {
4887
4970
  const response = options.response && typeof options.response === "object" ? options.response : null;
4971
+ const cached = readCachedEnrichment(page);
4888
4972
  const merged = {
4889
4973
  ip: toInline(options.ip, 80),
4890
4974
  location: toInline(options.location, 80)
4891
4975
  };
4976
+ if (!merged.ip || !merged.location) {
4977
+ fillEnrichment(merged, cached);
4978
+ }
4892
4979
  if (!merged.ip || !merged.location) {
4893
4980
  fillEnrichment(
4894
4981
  merged,
@@ -4912,24 +4999,31 @@ var resolveEnrichment = async (page, baseMeta, options) => {
4912
4999
  merged.location = headerLocation || merged.location;
4913
5000
  }
4914
5001
  }
5002
+ writeCachedEnrichment(page, merged);
4915
5003
  return merged;
4916
5004
  };
4917
- var buildWatermarkStamp = ({ taskId, captureTime, ip, location }) => {
5005
+ var buildWatermarkStamp = ({ prompt, captureTime, ip, location }) => {
4918
5006
  const parts = [
4919
- `TaskID ${shortenMiddle(taskId, 56, 24, 16) || "-"}`,
5007
+ `Prompt ${shortenTail(prompt, 40) || "-"}`,
4920
5008
  `Time ${captureTime}`,
4921
5009
  `Loc ${shortenTail(location, 20) || "-"}`,
4922
5010
  `IP ${toInline(ip, 24) || "-"}`
4923
5011
  ];
4924
5012
  return parts.join(" | ");
4925
5013
  };
4926
- var buildStripSegments = ({ taskId, captureTime, ip, location }) => {
5014
+ var buildStripSegments = ({ prompt, captureTime, ip, location }) => {
5015
+ const promptValue = shortenByCharacters(
5016
+ normalizeWhitespace(prompt) || "-",
5017
+ DEFAULT_STRIP_PROMPT_MAX_SAFE_CHARS
5018
+ ) || "-";
5019
+ const ipValue = normalizeWhitespace(ip) || "-";
5020
+ const locationValue = normalizeWhitespace(location) || "-";
4927
5021
  return [
4928
5022
  {
4929
- kind: "taskId",
4930
- label: "TaskID",
4931
- value: toInline(taskId, 240) || "-",
4932
- rawValue: toInline(taskId, 240) || "-"
5023
+ kind: "prompt",
5024
+ label: "Prompt",
5025
+ value: promptValue,
5026
+ rawValue: promptValue
4933
5027
  },
4934
5028
  {
4935
5029
  kind: "time",
@@ -4940,25 +5034,50 @@ var buildStripSegments = ({ taskId, captureTime, ip, location }) => {
4940
5034
  {
4941
5035
  kind: "location",
4942
5036
  label: "Loc",
4943
- value: shortenTail(location, 24) || "-",
4944
- rawValue: toInline(location, 80) || "-"
5037
+ value: locationValue,
5038
+ rawValue: locationValue
4945
5039
  },
4946
5040
  {
4947
5041
  kind: "ip",
4948
5042
  label: "IP",
4949
- value: toInline(ip, 28) || "-",
4950
- rawValue: toInline(ip, 80) || "-"
5043
+ value: ipValue,
5044
+ rawValue: ipValue
4951
5045
  }
4952
5046
  ];
4953
5047
  };
5048
+ var escapeXml = (value) => String(value || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
5049
+ var estimateTextWidth = (value, fontSize = 16) => {
5050
+ const text = String(value || "");
5051
+ let width = 0;
5052
+ for (const char of text) {
5053
+ if (/\s/.test(char)) {
5054
+ width += fontSize * 0.34;
5055
+ } else if (/[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u30ff\uac00-\ud7af]/.test(char)) {
5056
+ width += fontSize * 0.92;
5057
+ } else if (/[A-Z0-9]/.test(char)) {
5058
+ width += fontSize * 0.62;
5059
+ } else {
5060
+ width += fontSize * 0.56;
5061
+ }
5062
+ }
5063
+ return Math.ceil(width);
5064
+ };
5065
+ var resolvePromptFields = (options = {}, fallbackTitle = "") => {
5066
+ const query = normalizeText(options.query);
5067
+ const prompt = query || normalizeText(options.prompt) || normalizeText(fallbackTitle) || "\u672A\u63D0\u4F9B Prompt";
5068
+ return {
5069
+ prompt,
5070
+ query: query || prompt
5071
+ };
5072
+ };
4954
5073
  var createBaseWatermarkifyOptions = () => ({
4955
5074
  enabled: true,
4956
5075
  timezoneOffsetHours: DEFAULT_TIMEZONE_OFFSET,
4957
5076
  response: null,
4958
5077
  ip: "",
4959
5078
  location: "",
5079
+ prompt: "",
4960
5080
  query: "",
4961
- taskId: "",
4962
5081
  ipLookup: true,
4963
5082
  ipLookupTimeoutMs: DEFAULT_IP_LOOKUP_TIMEOUT_MS,
4964
5083
  resolver: null,
@@ -5001,8 +5120,8 @@ var normalizeScreenshotWatermarkify = (value) => {
5001
5120
  response: source.response ?? null,
5002
5121
  ip: normalizeText(source.ip),
5003
5122
  location: normalizeText(source.location),
5004
- query: toInline(source.query, 140),
5005
- taskId: toInline(source.taskId, 120),
5123
+ prompt: normalizeWhitespace(source.prompt),
5124
+ query: normalizeWhitespace(source.query),
5006
5125
  ipLookup: source.ipLookup !== false,
5007
5126
  ipLookupTimeoutMs: Number.isFinite(ipLookupTimeoutMsRaw) ? ipLookupTimeoutMsRaw : DEFAULT_IP_LOOKUP_TIMEOUT_MS,
5008
5127
  resolver: typeof source.resolver === "function" ? source.resolver : null,
@@ -5021,16 +5140,16 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5021
5140
  const url = normalizeText(page?.url?.()) || "about:blank";
5022
5141
  const title = normalizeText(await page.title().catch(() => "")) || "\u672A\u547D\u540D\u9875\u9762";
5023
5142
  const hostname = getHostname(url);
5024
- const query = normalizeText(options.query) || title || "\u672A\u63D0\u4F9B Query";
5025
- const taskId = normalizeText(options.taskId) || "-";
5026
- const captureTime = formatTimestampForUtcOffset(/* @__PURE__ */ new Date(), timezoneOffsetHours);
5143
+ const { prompt, query } = resolvePromptFields(options, title);
5144
+ const capturedAt = options.capturedAt instanceof Date ? options.capturedAt : new Date(options.capturedAt || Date.now());
5145
+ const captureTime = formatTimestampForUtcOffset(capturedAt, timezoneOffsetHours);
5027
5146
  const [enrichment, stripLogoSrc] = await Promise.all([
5028
5147
  resolveEnrichment(page, {
5029
5148
  url,
5030
5149
  hostname,
5031
5150
  title,
5032
- query,
5033
- taskId
5151
+ prompt,
5152
+ query
5034
5153
  }, options),
5035
5154
  resolveStripLogoSrc()
5036
5155
  ]);
@@ -5038,13 +5157,13 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5038
5157
  const location = enrichment.location || "-";
5039
5158
  return {
5040
5159
  watermarkText: buildWatermarkStamp({
5041
- taskId,
5160
+ prompt,
5042
5161
  captureTime,
5043
5162
  ip,
5044
5163
  location
5045
5164
  }),
5046
5165
  stripSegments: buildStripSegments({
5047
- taskId,
5166
+ prompt,
5048
5167
  captureTime,
5049
5168
  ip,
5050
5169
  location
@@ -5054,396 +5173,509 @@ var resolveScreenshotWatermarkifyMeta = async (page, options = {}) => {
5054
5173
  stripLogoSrc
5055
5174
  };
5056
5175
  };
5057
- var installScreenshotWatermarkify = async (page, meta) => {
5058
- const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
5059
- const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
5060
- if (!page || !meta || !hasWatermark && !hasStrip) {
5061
- return async () => {
5062
- };
5176
+ var buildFontFamily = () => 'MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif';
5177
+ var buildStripSegmentLayout = (segment, options = {}) => {
5178
+ const label = normalizeWhitespace(segment?.label || "");
5179
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5180
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5181
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5182
+ const labelWidth = Math.max(
5183
+ DEFAULT_STRIP_LABEL_WIDTH,
5184
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5185
+ );
5186
+ const rawValueWidth = Math.max(
5187
+ 24,
5188
+ estimateTextWidth(rawValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING
5189
+ );
5190
+ const maxValueWidth = Math.max(0, Number(options.valueMaxWidth) || 0);
5191
+ const renderValue = maxValueWidth > 0 ? fitTextWithEllipsis(rawValue, Math.max(18, maxValueWidth - DEFAULT_STRIP_VALUE_PADDING), valueFontSize) : rawValue;
5192
+ const valueWidth = maxValueWidth > 0 ? Math.max(24, Math.min(maxValueWidth, estimateTextWidth(renderValue, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING)) : rawValueWidth;
5193
+ return {
5194
+ ...segment,
5195
+ label,
5196
+ rawValue,
5197
+ renderValue,
5198
+ labelFontSize,
5199
+ valueFontSize,
5200
+ labelWidth,
5201
+ valueWidth,
5202
+ segmentWidth: labelWidth + valueWidth,
5203
+ rowHeight: Math.round(Math.max(labelFontSize, valueFontSize) * 1.42)
5204
+ };
5205
+ };
5206
+ var buildStripRowLayout = (segments, overrides = {}) => {
5207
+ const safeSegments = Array.isArray(segments) ? segments : [];
5208
+ const sectionGap = DEFAULT_STRIP_SECTION_GAP;
5209
+ const layouts = safeSegments.map((segment, index) => buildStripSegmentLayout(segment, {
5210
+ valueMaxWidth: overrides?.valueMaxWidths?.[index]
5211
+ }));
5212
+ const totalWidth = layouts.reduce((sum, segment) => sum + segment.segmentWidth, 0) + Math.max(0, layouts.length - 1) * sectionGap;
5213
+ const rowHeight = layouts.reduce((max, segment) => Math.max(max, segment.rowHeight || 0), 0);
5214
+ return {
5215
+ sectionGap,
5216
+ layouts,
5217
+ totalWidth,
5218
+ rowHeight
5219
+ };
5220
+ };
5221
+ var fitTextWithEllipsis = (chars, maxWidth, fontSize) => {
5222
+ const safeChars = Array.isArray(chars) ? chars.slice() : Array.from(String(chars || ""));
5223
+ if (!safeChars.length) {
5224
+ return "-";
5225
+ }
5226
+ if (estimateTextWidth(safeChars.join(""), fontSize) <= maxWidth) {
5227
+ return safeChars.join("").trim() || "-";
5063
5228
  }
5064
- await page.evaluate(({ hostId, watermarkifyMeta, defaults }) => {
5065
- const previousHost = document.getElementById(hostId);
5066
- if (previousHost && typeof previousHost.__pkCleanup === "function") {
5067
- previousHost.__pkCleanup();
5229
+ while (safeChars.length > 1 && estimateTextWidth(`${safeChars.join("").trimEnd()}\u2026`, fontSize) > maxWidth) {
5230
+ safeChars.pop();
5231
+ }
5232
+ return `${safeChars.join("").trimEnd()}\u2026` || "\u2026";
5233
+ };
5234
+ var splitTextToLines = (value, maxWidth, fontSize, maxLines = 2) => {
5235
+ const chars = Array.from(normalizeWhitespace(value) || "-");
5236
+ const safeMaxWidth = Math.max(18, maxWidth - DEFAULT_STRIP_TEXT_WIDTH_SAFETY);
5237
+ if (!chars.length) {
5238
+ return { lines: ["-"], truncated: false };
5239
+ }
5240
+ const lines = [];
5241
+ let cursor = 0;
5242
+ let truncated = false;
5243
+ while (cursor < chars.length && lines.length < maxLines) {
5244
+ while (cursor < chars.length && chars[cursor] === " ") {
5245
+ cursor += 1;
5068
5246
  }
5069
- previousHost?.remove();
5070
- const mountPoint = document.body || document.documentElement;
5071
- if (!mountPoint) return;
5072
- const createNode = (tagName, className) => {
5073
- const node = document.createElement(tagName);
5074
- if (className) node.className = className;
5075
- return node;
5076
- };
5077
- const safeText = (value) => String(value || "").trim();
5078
- const host = document.createElement("div");
5079
- host.id = hostId;
5080
- host.style.position = "fixed";
5081
- host.style.inset = "0";
5082
- host.style.width = "100vw";
5083
- host.style.height = "100vh";
5084
- host.style.pointerEvents = "none";
5085
- host.style.zIndex = "2147483646";
5086
- const shadow = host.attachShadow({ mode: "open" });
5087
- const style = document.createElement("style");
5088
- style.textContent = `
5089
- :host {
5090
- all: initial;
5091
- position: fixed;
5092
- inset: 0;
5093
- width: 100vw;
5094
- height: 100vh;
5095
- pointer-events: none;
5096
- z-index: 2147483646;
5097
- }
5098
-
5099
- .root {
5100
- position: absolute;
5101
- inset: 0;
5102
- width: 100%;
5103
- height: 100%;
5104
- overflow: hidden;
5105
- pointer-events: none;
5106
- font-family: MiSans, "SF Pro Display", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
5107
- }
5108
-
5109
- .watermark {
5110
- position: absolute;
5111
- inset: 0;
5112
- overflow: hidden;
5113
- pointer-events: none;
5114
- }
5115
-
5116
- .wmStamp {
5117
- position: absolute;
5118
- left: 0;
5119
- top: 0;
5120
- transform-origin: left center;
5121
- pointer-events: none;
5122
- white-space: nowrap;
5123
- }
5124
-
5125
- .wmStampText {
5126
- color: rgba(17, 17, 17, var(--wm-opacity, 0.15));
5127
- font-size: 19px;
5128
- font-weight: 720;
5129
- line-height: 1;
5130
- letter-spacing: 0.032em;
5131
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.44);
5132
- -webkit-text-stroke: 0.22px rgba(255, 255, 255, 0.12);
5133
- font-variant-numeric: tabular-nums;
5134
- filter: saturate(0.92);
5135
- }
5136
-
5137
- .strip {
5138
- position: absolute;
5139
- left: 0;
5140
- right: 0;
5141
- bottom: 0;
5142
- min-height: 78px;
5143
- padding: 0 22px 0 20px;
5144
- display: flex;
5145
- align-items: center;
5146
- overflow: hidden;
5147
- border-top: 1px solid rgba(17, 17, 17, 0.1);
5148
- background:
5149
- linear-gradient(180deg, rgba(255, 255, 255, 0.995) 0%, rgba(248, 248, 247, 0.985) 100%);
5150
- box-shadow:
5151
- 0 -22px 48px rgba(15, 23, 42, 0.1),
5152
- inset 0 1px 0 rgba(255, 255, 255, 0.92);
5153
- backdrop-filter: blur(16px);
5154
- -webkit-backdrop-filter: blur(16px);
5155
- }
5156
-
5157
- .strip::before {
5158
- content: "";
5159
- position: absolute;
5160
- left: 20px;
5161
- right: 52%;
5162
- top: 0;
5163
- height: 2px;
5164
- border-radius: 999px;
5165
- background: linear-gradient(
5166
- 90deg,
5167
- rgba(17, 17, 17, 0.98) 0%,
5168
- rgba(17, 17, 17, 0.68) 24%,
5169
- rgba(17, 17, 17, 0.08) 58%,
5170
- rgba(17, 17, 17, 0) 100%
5171
- );
5172
- }
5173
-
5174
- .stripInner {
5175
- position: relative;
5176
- z-index: 1;
5177
- display: flex;
5178
- align-items: center;
5179
- gap: 16px;
5180
- width: 100%;
5181
- min-width: 0;
5182
- }
5183
-
5184
- .stripLogo {
5185
- position: relative;
5186
- width: 28px;
5187
- height: 28px;
5188
- flex: none;
5189
- display: flex;
5190
- align-items: center;
5191
- justify-content: center;
5192
- }
5193
-
5194
- .stripLogoImg {
5195
- width: 28px;
5196
- height: 28px;
5197
- object-fit: contain;
5198
- display: block;
5199
- }
5200
-
5201
- .segments {
5202
- display: flex;
5203
- align-items: center;
5204
- gap: 0;
5205
- flex: 1 1 auto;
5206
- min-width: 0;
5207
- white-space: nowrap;
5208
- }
5209
-
5210
- .segment {
5211
- position: relative;
5212
- display: inline-flex;
5213
- align-items: center;
5214
- gap: 10px;
5215
- min-width: 0;
5216
- flex: none;
5217
- padding-right: 16px;
5218
- margin-right: 16px;
5219
- }
5220
-
5221
- .segment:not(:last-child)::after {
5222
- content: "";
5223
- position: absolute;
5224
- top: 50%;
5225
- right: 0;
5226
- width: 1px;
5227
- height: 25px;
5228
- transform: translateY(-50%);
5229
- background: linear-gradient(
5230
- 180deg,
5231
- rgba(148, 163, 184, 0) 0%,
5232
- rgba(148, 163, 184, 0.34) 24%,
5233
- rgba(148, 163, 184, 0.34) 76%,
5234
- rgba(148, 163, 184, 0) 100%
5235
- );
5236
- }
5237
-
5238
- .segmentTaskId {
5239
- flex: 1 1 auto;
5240
- max-width: none;
5241
- min-width: 0;
5242
- }
5243
-
5244
- .label {
5245
- width: 52px;
5246
- color: rgba(17, 17, 17, 0.42);
5247
- font-size: 12px;
5248
- font-weight: 600;
5249
- line-height: 1;
5250
- letter-spacing: 0.01em;
5251
- flex: none;
5252
- display: inline-flex;
5253
- align-items: center;
5254
- justify-content: flex-start;
5255
- }
5256
-
5257
- .value {
5258
- color: #111111;
5259
- font-size: 18px;
5260
- font-weight: 710;
5261
- line-height: 1;
5262
- min-width: 0;
5263
- overflow: hidden;
5264
- text-overflow: ellipsis;
5265
- font-variant-numeric: tabular-nums;
5266
- display: inline-flex;
5267
- align-items: center;
5268
- }
5269
-
5270
- .segmentTaskId .value {
5271
- color: #101010;
5272
- font-size: 15px;
5273
- font-weight: 760;
5274
- letter-spacing: -0.01em;
5275
- }
5276
-
5277
- .segmentTime .value {
5278
- color: #202020;
5279
- font-size: 17px;
5280
- font-weight: 700;
5281
- }
5282
-
5283
- .segmentLocation .value,
5284
- .segmentIp .value {
5285
- color: #2a2a2a;
5286
- font-size: 17px;
5287
- font-weight: 700;
5288
- }
5289
-
5290
- `;
5291
- const root = createNode("div", "root");
5292
- const watermarkNode = createNode("div", "watermark");
5293
- const stripNode = createNode("div", "strip");
5294
- const stripInner = createNode("div", "stripInner");
5295
- const stripLogo = createNode("div", "stripLogo");
5296
- const stripLogoImg = createNode("img", "stripLogoImg");
5297
- const stripSegmentsNode = createNode("div", "segments");
5298
- stripLogoImg.alt = "";
5299
- stripLogoImg.decoding = "async";
5300
- stripLogoImg.referrerPolicy = "no-referrer";
5301
- stripLogoImg.src = safeText(watermarkifyMeta?.stripLogoSrc) || defaults.logoUrl;
5302
- stripLogo.appendChild(stripLogoImg);
5303
- stripInner.appendChild(stripLogo);
5304
- stripInner.appendChild(stripSegmentsNode);
5305
- stripNode.appendChild(stripInner);
5306
- root.appendChild(watermarkNode);
5307
- root.appendChild(stripNode);
5308
- shadow.appendChild(style);
5309
- shadow.appendChild(root);
5310
- mountPoint.appendChild(host);
5311
- const buildSegmentNode = (segment) => {
5312
- const kind = String(segment?.kind || "").trim();
5313
- const label = String(segment?.label || "").trim();
5314
- const value = String(segment?.value || "").trim();
5315
- const rawValue = String(segment?.rawValue || value).trim();
5316
- const classSuffix = kind ? ` segment${kind.slice(0, 1).toUpperCase()}${kind.slice(1)}` : "";
5317
- const segmentNode = createNode("span", `segment${classSuffix}`);
5318
- const labelNode = createNode("span", "label");
5319
- const valueNode = createNode("span", "value");
5320
- labelNode.textContent = label;
5321
- valueNode.textContent = value;
5322
- segmentNode.title = rawValue ? `${label} ${rawValue}` : label;
5323
- segmentNode.appendChild(labelNode);
5324
- segmentNode.appendChild(valueNode);
5325
- return segmentNode;
5326
- };
5327
- const renderStrip = () => {
5328
- const stripSegments = Array.isArray(watermarkifyMeta?.stripSegments) ? watermarkifyMeta.stripSegments : [];
5329
- if (!watermarkifyMeta?.strip?.enabled || stripSegments.length === 0) {
5330
- stripNode.remove();
5331
- return;
5332
- }
5333
- const fragment = document.createDocumentFragment();
5334
- for (const segment of stripSegments) {
5335
- fragment.appendChild(buildSegmentNode(segment));
5336
- }
5337
- stripSegmentsNode.replaceChildren(fragment);
5338
- };
5339
- const waitForLogo = async () => {
5340
- if (!stripLogoImg.getAttribute("src")) {
5341
- return;
5342
- }
5343
- await new Promise((resolve) => {
5344
- let settled = false;
5345
- const done = () => {
5346
- if (settled) return;
5347
- settled = true;
5348
- resolve();
5349
- };
5350
- const fail = () => {
5351
- if (settled) return;
5352
- stripLogo.remove();
5353
- done();
5354
- };
5355
- const timeoutDone = () => {
5356
- if (settled) return;
5357
- done();
5358
- };
5359
- if (stripLogoImg.complete && stripLogoImg.naturalWidth > 0) {
5360
- done();
5361
- return;
5362
- }
5363
- if (stripLogoImg.complete && stripLogoImg.naturalWidth === 0) {
5364
- fail();
5365
- return;
5366
- }
5367
- stripLogoImg.addEventListener("load", done, { once: true });
5368
- stripLogoImg.addEventListener("error", fail, { once: true });
5369
- setTimeout(timeoutDone, 2500);
5370
- });
5371
- };
5372
- const renderWatermark = () => {
5373
- const stampText = safeText(watermarkifyMeta?.watermarkText);
5374
- if (!watermarkifyMeta?.watermark?.enabled || !stampText) {
5375
- watermarkNode.remove();
5376
- return;
5377
- }
5378
- const cellWidth = Math.max(680, Number(watermarkifyMeta.watermark.cellWidth) || defaults.cellWidth);
5379
- const cellHeight = Math.max(260, Number(watermarkifyMeta.watermark.cellHeight) || defaults.cellHeight);
5380
- const rotateDeg = Number(watermarkifyMeta.watermark.rotateDeg) || defaults.rotateDeg;
5381
- const opacity = Math.min(0.22, Math.max(0.05, Number(watermarkifyMeta.watermark.opacity) || defaults.opacity));
5382
- const width = watermarkNode.clientWidth || window.innerWidth || document.documentElement.clientWidth || cellWidth;
5383
- const height = watermarkNode.clientHeight || window.innerHeight || document.documentElement.clientHeight || cellHeight;
5384
- const rowOffset = Math.round(cellWidth * 0.24);
5385
- const startX = -Math.round(cellWidth * 0.16);
5386
- const startY = -Math.round(cellHeight * 0.12);
5387
- const cols = Math.ceil((width + cellWidth * 1.1) / cellWidth) + 1;
5388
- const rows = Math.ceil((height + cellHeight * 0.9) / cellHeight) + 1;
5389
- const fragment = document.createDocumentFragment();
5390
- for (let row = 0; row < rows; row += 1) {
5391
- for (let col = 0; col < cols; col += 1) {
5392
- const stamp = createNode("div", "wmStamp");
5393
- const stampTextNode = createNode("div", "wmStampText");
5394
- const x = startX + col * cellWidth + (row % 2 ? rowOffset : 0);
5395
- const y = startY + row * cellHeight + (row % 2 ? 14 : -8);
5396
- stamp.style.transform = `translate(${x}px, ${y}px) rotate(${rotateDeg}deg)`;
5397
- stampTextNode.style.setProperty("--wm-opacity", String(opacity));
5398
- stampTextNode.textContent = stampText;
5399
- stamp.appendChild(stampTextNode);
5400
- fragment.appendChild(stamp);
5401
- }
5247
+ if (cursor >= chars.length) {
5248
+ break;
5249
+ }
5250
+ const isLastLine = lines.length === maxLines - 1;
5251
+ const lineChars = [];
5252
+ while (cursor < chars.length) {
5253
+ const nextChars = [...lineChars, chars[cursor]];
5254
+ const remaining = cursor < chars.length - 1;
5255
+ const previewText = nextChars.join("") + (isLastLine && remaining ? "\u2026" : "");
5256
+ if (estimateTextWidth(previewText, fontSize) <= safeMaxWidth || lineChars.length === 0) {
5257
+ lineChars.push(chars[cursor]);
5258
+ cursor += 1;
5259
+ continue;
5402
5260
  }
5403
- watermarkNode.replaceChildren(fragment);
5404
- };
5405
- let resizeFrame = 0;
5406
- const queueWatermarkRender = () => {
5407
- if (!watermarkNode.isConnected) return;
5408
- cancelAnimationFrame(resizeFrame);
5409
- resizeFrame = requestAnimationFrame(() => {
5410
- renderWatermark();
5261
+ break;
5262
+ }
5263
+ while (cursor < chars.length && chars[cursor] === " ") {
5264
+ cursor += 1;
5265
+ }
5266
+ if (isLastLine && cursor < chars.length) {
5267
+ lines.push(fitTextWithEllipsis([...lineChars, ...chars.slice(cursor)], safeMaxWidth, fontSize));
5268
+ truncated = true;
5269
+ cursor = chars.length;
5270
+ continue;
5271
+ }
5272
+ lines.push(lineChars.join("").trim() || "-");
5273
+ }
5274
+ return {
5275
+ lines: lines.length > 0 ? lines : ["-"],
5276
+ truncated
5277
+ };
5278
+ };
5279
+ var resolveMetadataMinValueWidth = (segment) => {
5280
+ switch (segment?.kind) {
5281
+ case "time":
5282
+ return Math.max(148, estimateTextWidth("2026-04-23 21:27:16 UTC+8", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5283
+ case "ip":
5284
+ return Math.max(108, estimateTextWidth("255.255.255.255", DEFAULT_STRIP_VALUE_FONT_SIZE) + 8);
5285
+ case "location":
5286
+ return 76;
5287
+ default:
5288
+ return 56;
5289
+ }
5290
+ };
5291
+ var buildMetadataRowLayout = (segments, contentWidth) => {
5292
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5293
+ const naturalRow = buildStripRowLayout(safeSegments);
5294
+ if (!safeSegments.length || naturalRow.totalWidth <= contentWidth) {
5295
+ return naturalRow;
5296
+ }
5297
+ const sectionGap = naturalRow.sectionGap;
5298
+ const totalLabelWidth = naturalRow.layouts.reduce((sum, segment) => sum + segment.labelWidth, 0);
5299
+ const availableValueWidth = Math.max(
5300
+ 72,
5301
+ Math.floor(contentWidth - totalLabelWidth - Math.max(0, safeSegments.length - 1) * sectionGap)
5302
+ );
5303
+ const minValueWidths = naturalRow.layouts.map((segment) => resolveMetadataMinValueWidth(segment));
5304
+ const naturalValueWidths = naturalRow.layouts.map((segment) => segment.valueWidth);
5305
+ const valueWidths = naturalValueWidths.slice();
5306
+ let overflow = naturalRow.totalWidth - contentWidth;
5307
+ const shrinkOrder = ["location", "ip", "time"];
5308
+ shrinkOrder.forEach((kind) => {
5309
+ if (overflow <= 0) return;
5310
+ naturalRow.layouts.forEach((segment, index) => {
5311
+ if (overflow <= 0 || segment.kind !== kind) return;
5312
+ const minWidth = minValueWidths[index];
5313
+ const reducible = Math.max(0, valueWidths[index] - minWidth);
5314
+ if (!reducible) return;
5315
+ const reduction = Math.min(reducible, overflow);
5316
+ valueWidths[index] -= reduction;
5317
+ overflow -= reduction;
5318
+ });
5319
+ });
5320
+ if (overflow > 0) {
5321
+ const minTotalWidth = minValueWidths.reduce((sum, width) => sum + width, 0);
5322
+ if (minTotalWidth > availableValueWidth) {
5323
+ const flexibleIndexes = naturalRow.layouts.map((segment, index) => ({ segment, index })).filter(({ segment }) => segment.kind !== "time");
5324
+ let extraOverflow = minTotalWidth - availableValueWidth;
5325
+ flexibleIndexes.forEach(({ index }) => {
5326
+ if (extraOverflow <= 0) return;
5327
+ const minFloor = 40;
5328
+ const reducible = Math.max(0, minValueWidths[index] - minFloor);
5329
+ if (!reducible) return;
5330
+ const reduction = Math.min(reducible, extraOverflow);
5331
+ valueWidths[index] -= reduction;
5332
+ extraOverflow -= reduction;
5411
5333
  });
5412
- };
5413
- const handleResize = () => {
5414
- queueWatermarkRender();
5415
- };
5416
- host.__pkCleanup = () => {
5417
- cancelAnimationFrame(resizeFrame);
5418
- window.removeEventListener("resize", handleResize);
5419
- };
5420
- renderStrip();
5421
- renderWatermark();
5422
- queueWatermarkRender();
5423
- window.addEventListener("resize", handleResize, { passive: true });
5424
- return waitForLogo();
5425
- }, {
5426
- hostId: SCREENSHOT_WATERMARKIFY_HOST_ID,
5427
- watermarkifyMeta: meta,
5428
- defaults: {
5429
- opacity: DEFAULT_WATERMARK_OPACITY,
5430
- rotateDeg: DEFAULT_WATERMARK_ROTATE_DEG,
5431
- cellWidth: DEFAULT_WATERMARK_CELL_WIDTH,
5432
- cellHeight: DEFAULT_WATERMARK_CELL_HEIGHT,
5433
- logoUrl: DEFAULT_STRIP_LOGO_URL
5434
5334
  }
5335
+ }
5336
+ return buildStripRowLayout(safeSegments, {
5337
+ valueMaxWidths: valueWidths
5435
5338
  });
5436
- return async () => {
5437
- await page.evaluate((hostId) => {
5438
- const host = document.getElementById(hostId);
5439
- if (host && typeof host.__pkCleanup === "function") {
5440
- host.__pkCleanup();
5441
- }
5442
- host?.remove();
5443
- }, SCREENSHOT_WATERMARKIFY_HOST_ID).catch(() => {
5444
- });
5339
+ };
5340
+ var buildPromptWrapLayout = (segment, contentWidth) => {
5341
+ const label = normalizeWhitespace(segment?.label || "Prompt") || "Prompt";
5342
+ const rawValue = normalizeWhitespace(segment?.rawValue || segment?.value || "-") || "-";
5343
+ const labelFontSize = DEFAULT_STRIP_LABEL_FONT_SIZE;
5344
+ const valueFontSize = DEFAULT_STRIP_VALUE_FONT_SIZE;
5345
+ const labelWidth = Math.max(
5346
+ DEFAULT_STRIP_LABEL_WIDTH,
5347
+ estimateTextWidth(label, labelFontSize) + DEFAULT_STRIP_LABEL_PADDING
5348
+ );
5349
+ const safeTotalWidth = Math.max(labelWidth + 56, Number(contentWidth) || 0);
5350
+ const valueWidth = Math.max(
5351
+ 56,
5352
+ safeTotalWidth - labelWidth - DEFAULT_STRIP_CONTENT_SAFE_PADDING_X
5353
+ );
5354
+ const result = splitTextToLines(rawValue, valueWidth, valueFontSize, DEFAULT_STRIP_PROMPT_MAX_LINES);
5355
+ const lineHeight = Math.max(18, Math.round(valueFontSize * DEFAULT_STRIP_LINE_HEIGHT_RATIO));
5356
+ const lineWidths = result.lines.map((line) => estimateTextWidth(line, valueFontSize) + DEFAULT_STRIP_VALUE_PADDING);
5357
+ const measuredValueWidth = Math.min(
5358
+ valueWidth,
5359
+ Math.max(56, ...lineWidths)
5360
+ );
5361
+ const rowHeight = Math.round(Math.max(labelFontSize, valueFontSize) * 1.42);
5362
+ return {
5363
+ mode: result.lines.length === 1 ? "single" : "wrapped",
5364
+ label,
5365
+ rawValue,
5366
+ labelFontSize,
5367
+ labelWidth,
5368
+ valueFontSize,
5369
+ valueWidth,
5370
+ lines: result.lines,
5371
+ truncated: result.truncated,
5372
+ lineHeight,
5373
+ blockHeight: result.lines.length === 1 ? rowHeight : Math.max(lineHeight, result.lines.length * lineHeight),
5374
+ totalWidth: labelWidth + measuredValueWidth
5375
+ };
5376
+ };
5377
+ var buildStripLayout = (segments, contentWidth) => {
5378
+ const safeSegments = Array.isArray(segments) ? segments.filter(Boolean) : [];
5379
+ const promptSegment = safeSegments.find((segment) => segment.kind === "prompt") || safeSegments[0] || null;
5380
+ const metadataSegments = safeSegments.filter((segment) => segment !== promptSegment);
5381
+ const zoneGap = DEFAULT_STRIP_ZONE_GAP;
5382
+ let metadataRow = buildMetadataRowLayout(
5383
+ metadataSegments,
5384
+ Math.max(220, contentWidth)
5385
+ );
5386
+ let metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5387
+ let promptAvailableWidth = Math.max(
5388
+ 220,
5389
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5390
+ );
5391
+ if (promptAvailableWidth < DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH && metadataSegments.length > 0) {
5392
+ const tighterMetadataWidth = Math.max(180, contentWidth - DEFAULT_STRIP_PROMPT_MIN_TOTAL_WIDTH - zoneGap);
5393
+ metadataRow = buildMetadataRowLayout(metadataSegments, tighterMetadataWidth);
5394
+ metadataStartX = Math.max(0, contentWidth - (metadataRow?.totalWidth || 0));
5395
+ promptAvailableWidth = Math.max(
5396
+ 220,
5397
+ metadataStartX - zoneGap - DEFAULT_STRIP_PROMPT_TRAILING_PADDING
5398
+ );
5399
+ }
5400
+ const preferredPromptWidth = Math.min(
5401
+ promptAvailableWidth,
5402
+ Math.max(280, Math.round(contentWidth * DEFAULT_STRIP_PROMPT_PREFERRED_WIDTH_RATIO))
5403
+ );
5404
+ let promptRow = buildPromptWrapLayout(promptSegment, preferredPromptWidth);
5405
+ if (promptRow?.truncated && promptAvailableWidth > preferredPromptWidth + 24) {
5406
+ promptRow = buildPromptWrapLayout(promptSegment, promptAvailableWidth);
5407
+ }
5408
+ const contentHeight = Math.max(
5409
+ promptRow?.blockHeight || 0,
5410
+ metadataRow?.rowHeight || 0,
5411
+ 24
5412
+ );
5413
+ const verticalPadding = contentHeight > (metadataRow?.rowHeight || 0) ? 18 : 16;
5414
+ const stripHeight = Math.max(
5415
+ contentHeight > (metadataRow?.rowHeight || 0) ? DEFAULT_STRIP_WRAPPED_MIN_HEIGHT : DEFAULT_STRIP_ONE_LINE_HEIGHT,
5416
+ Math.ceil(contentHeight + verticalPadding * 2)
5417
+ );
5418
+ return {
5419
+ mode: "zoned",
5420
+ height: stripHeight,
5421
+ contentHeight,
5422
+ promptRow,
5423
+ promptStartX: 0,
5424
+ metadataRow,
5425
+ metadataStartX
5445
5426
  };
5446
5427
  };
5428
+ var renderStripRow = (row, options = {}) => {
5429
+ const startX = Number(options.startX) || 0;
5430
+ const centerY = Number(options.centerY) || 0;
5431
+ const fontFamily = String(options.fontFamily || "");
5432
+ const showDividers = options.showDividers !== false;
5433
+ const layouts = Array.isArray(row?.layouts) ? row.layouts : [];
5434
+ if (!layouts.length) {
5435
+ return "";
5436
+ }
5437
+ const rowHeight = Math.max(18, Number(row?.rowHeight) || 18);
5438
+ const dividerHalfHeight = Math.max(12, Math.round(rowHeight * 0.56));
5439
+ const sectionGap = Math.max(0, Number(row?.sectionGap) || 0);
5440
+ const svgSegments = [];
5441
+ let currentX = startX;
5442
+ layouts.forEach((segment, index) => {
5443
+ const label = escapeXml(segment.label || "");
5444
+ const value = escapeXml(segment.renderValue || "-");
5445
+ const labelX = currentX;
5446
+ const valueX = currentX + segment.labelWidth;
5447
+ svgSegments.push(`
5448
+ <text
5449
+ x="${labelX}"
5450
+ y="${centerY}"
5451
+ fill="#111111"
5452
+ fill-opacity="0.54"
5453
+ font-family="${fontFamily}"
5454
+ font-size="${segment.labelFontSize}"
5455
+ font-weight="650"
5456
+ dominant-baseline="middle"
5457
+ >${label}</text>
5458
+ <text
5459
+ x="${valueX}"
5460
+ y="${centerY}"
5461
+ fill="#111111"
5462
+ font-family="${fontFamily}"
5463
+ font-size="${segment.valueFontSize}"
5464
+ font-weight="${segment.kind === "prompt" ? 760 : 710}"
5465
+ dominant-baseline="middle"
5466
+ >${value}</text>
5467
+ `);
5468
+ currentX += segment.segmentWidth;
5469
+ if (showDividers && index < layouts.length - 1) {
5470
+ const lineX = currentX + Math.round(sectionGap / 2);
5471
+ svgSegments.push(`
5472
+ <line
5473
+ x1="${lineX}"
5474
+ y1="${centerY - dividerHalfHeight}"
5475
+ x2="${lineX}"
5476
+ y2="${centerY + dividerHalfHeight}"
5477
+ stroke="#94a3b8"
5478
+ stroke-opacity="0.34"
5479
+ stroke-width="1"
5480
+ />
5481
+ `);
5482
+ currentX += sectionGap;
5483
+ }
5484
+ });
5485
+ return svgSegments.join("");
5486
+ };
5487
+ var renderStripDivider = (x, centerY, height) => {
5488
+ const safeX = Number(x) || 0;
5489
+ const safeCenterY = Number(centerY) || 0;
5490
+ const safeHeight = Math.max(18, Number(height) || 18);
5491
+ const halfHeight = Math.max(12, Math.round(safeHeight * 0.56));
5492
+ return `
5493
+ <line
5494
+ x1="${safeX}"
5495
+ y1="${safeCenterY - halfHeight}"
5496
+ x2="${safeX}"
5497
+ y2="${safeCenterY + halfHeight}"
5498
+ stroke="#94a3b8"
5499
+ stroke-opacity="0.34"
5500
+ stroke-width="1"
5501
+ />
5502
+ `;
5503
+ };
5504
+ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
5505
+ const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
5506
+ const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
5507
+ if (!hasWatermark && !hasStrip) {
5508
+ return "";
5509
+ }
5510
+ const width = Math.max(1, Number(imageWidth) || 1);
5511
+ const height = Math.max(1, Number(imageHeight) || 1);
5512
+ const fontFamily = escapeXml(buildFontFamily());
5513
+ const parts = [];
5514
+ if (hasWatermark) {
5515
+ const stampText = escapeXml(normalizeText(meta.watermarkText));
5516
+ const cellWidth = Math.max(680, Number(meta.watermark?.cellWidth) || DEFAULT_WATERMARK_CELL_WIDTH);
5517
+ const cellHeight = Math.max(260, Number(meta.watermark?.cellHeight) || DEFAULT_WATERMARK_CELL_HEIGHT);
5518
+ const rotateDeg = Number(meta.watermark?.rotateDeg) || DEFAULT_WATERMARK_ROTATE_DEG;
5519
+ const opacity = Math.min(0.22, Math.max(0.05, Number(meta.watermark?.opacity) || DEFAULT_WATERMARK_OPACITY));
5520
+ const rowOffset = Math.round(cellWidth * 0.24);
5521
+ const startX = -Math.round(cellWidth * 0.16);
5522
+ const startY = -Math.round(cellHeight * 0.12);
5523
+ const cols = Math.ceil((width + cellWidth * 1.1) / cellWidth) + 1;
5524
+ const rows = Math.ceil((height + cellHeight * 0.9) / cellHeight) + 1;
5525
+ const stamps = [];
5526
+ for (let row = 0; row < rows; row += 1) {
5527
+ for (let col = 0; col < cols; col += 1) {
5528
+ const x = startX + col * cellWidth + (row % 2 ? rowOffset : 0);
5529
+ const y = startY + row * cellHeight + (row % 2 ? 14 : -8);
5530
+ stamps.push(`
5531
+ <g transform="translate(${x} ${y}) rotate(${rotateDeg})">
5532
+ <text
5533
+ x="0"
5534
+ y="0"
5535
+ fill="#111111"
5536
+ fill-opacity="${opacity}"
5537
+ stroke="#ffffff"
5538
+ stroke-opacity="0.12"
5539
+ stroke-width="0.22"
5540
+ font-family="${fontFamily}"
5541
+ font-size="19"
5542
+ font-weight="720"
5543
+ letter-spacing="0.6"
5544
+ >${stampText}</text>
5545
+ </g>
5546
+ `);
5547
+ }
5548
+ }
5549
+ parts.push(`<g id="watermarks">${stamps.join("")}</g>`);
5550
+ }
5551
+ if (hasStrip) {
5552
+ const logoSize = 28;
5553
+ const logoX = DEFAULT_STRIP_PADDING_LEFT;
5554
+ const contentStartX = logoX + logoSize + DEFAULT_STRIP_GAP;
5555
+ const contentWidth = width - contentStartX - DEFAULT_STRIP_PADDING_RIGHT;
5556
+ const stripLayout = buildStripLayout(meta.stripSegments, contentWidth);
5557
+ const stripHeight = stripLayout.height;
5558
+ const stripY = height - stripHeight;
5559
+ const logoY = Number((stripY + (stripHeight - logoSize) / 2).toFixed(2));
5560
+ let stripContentSvg = "";
5561
+ const centerY = stripY + stripHeight / 2 + 1;
5562
+ const promptRow = stripLayout.promptRow;
5563
+ const metadataRow = stripLayout.metadataRow;
5564
+ const promptParts = [];
5565
+ const hasPrompt = Boolean(promptRow?.label);
5566
+ const hasMetadata = Array.isArray(metadataRow?.layouts) && metadataRow.layouts.length > 0;
5567
+ const zoneDividerX = hasPrompt && hasMetadata ? contentStartX + stripLayout.metadataStartX - DEFAULT_STRIP_ZONE_GAP / 2 : null;
5568
+ if (promptRow?.mode === "single") {
5569
+ promptParts.push(renderStripRow({
5570
+ layouts: [
5571
+ {
5572
+ kind: "prompt",
5573
+ label: promptRow.label,
5574
+ renderValue: promptRow.lines[0] || "-",
5575
+ labelFontSize: promptRow.labelFontSize,
5576
+ valueFontSize: promptRow.valueFontSize,
5577
+ labelWidth: promptRow.labelWidth,
5578
+ segmentWidth: promptRow.labelWidth + promptRow.valueWidth
5579
+ }
5580
+ ],
5581
+ sectionGap: 0,
5582
+ rowHeight: promptRow.blockHeight
5583
+ }, {
5584
+ startX: contentStartX + stripLayout.promptStartX,
5585
+ centerY,
5586
+ fontFamily,
5587
+ showDividers: false
5588
+ }));
5589
+ } else if (promptRow) {
5590
+ const promptStartX = contentStartX + stripLayout.promptStartX;
5591
+ const labelX = promptStartX;
5592
+ const valueX = promptStartX + promptRow.labelWidth;
5593
+ const blockTop = centerY - promptRow.blockHeight / 2;
5594
+ const labelY = centerY;
5595
+ promptParts.push(`
5596
+ <text
5597
+ x="${labelX}"
5598
+ y="${labelY}"
5599
+ fill="#111111"
5600
+ fill-opacity="0.54"
5601
+ font-family="${fontFamily}"
5602
+ font-size="${promptRow.labelFontSize}"
5603
+ font-weight="650"
5604
+ dominant-baseline="middle"
5605
+ >${escapeXml(promptRow.label)}</text>
5606
+ `);
5607
+ promptRow.lines.forEach((line, index) => {
5608
+ const lineY = blockTop + index * promptRow.lineHeight + promptRow.lineHeight / 2;
5609
+ promptParts.push(`
5610
+ <text
5611
+ x="${valueX}"
5612
+ y="${lineY}"
5613
+ fill="#111111"
5614
+ font-family="${fontFamily}"
5615
+ font-size="${promptRow.valueFontSize}"
5616
+ font-weight="760"
5617
+ dominant-baseline="middle"
5618
+ >${escapeXml(line)}</text>
5619
+ `);
5620
+ });
5621
+ }
5622
+ stripContentSvg = [
5623
+ promptParts.join(""),
5624
+ zoneDividerX == null ? "" : renderStripDivider(
5625
+ zoneDividerX,
5626
+ centerY,
5627
+ Math.max(promptRow?.blockHeight || 0, metadataRow?.rowHeight || 0)
5628
+ ),
5629
+ renderStripRow(metadataRow, {
5630
+ startX: contentStartX + stripLayout.metadataStartX,
5631
+ centerY,
5632
+ fontFamily,
5633
+ showDividers: true
5634
+ })
5635
+ ].join("");
5636
+ parts.push(`
5637
+ <g id="strip">
5638
+ <rect x="0" y="${stripY}" width="${width}" height="${stripHeight}" fill="#ffffff" fill-opacity="0.985" />
5639
+ <rect x="0" y="${stripY}" width="${width}" height="1" fill="#111111" fill-opacity="0.10" />
5640
+ <rect x="${DEFAULT_STRIP_PADDING_LEFT}" y="${stripY}" width="${Math.max(120, Math.round(width * 0.42))}" height="2" fill="url(#stripAccent)" />
5641
+ ${meta.stripLogoSrc ? `<image href="${escapeXml(meta.stripLogoSrc)}" x="${logoX}" y="${logoY}" width="${logoSize}" height="${logoSize}" />` : ""}
5642
+ ${stripContentSvg}
5643
+ </g>
5644
+ `);
5645
+ }
5646
+ return `
5647
+ <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
5648
+ <defs>
5649
+ <linearGradient id="stripAccent" x1="0%" y1="0%" x2="100%" y2="0%">
5650
+ <stop offset="0%" stop-color="#111111" stop-opacity="0.98" />
5651
+ <stop offset="24%" stop-color="#111111" stop-opacity="0.68" />
5652
+ <stop offset="58%" stop-color="#111111" stop-opacity="0.08" />
5653
+ <stop offset="100%" stop-color="#111111" stop-opacity="0" />
5654
+ </linearGradient>
5655
+ </defs>
5656
+ ${parts.join("")}
5657
+ </svg>
5658
+ `;
5659
+ };
5660
+ var watermarkifyScreenshotBuffer = async (buffer, meta) => {
5661
+ const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
5662
+ const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
5663
+ if (!Buffer.isBuffer(buffer) || !meta || !hasWatermark && !hasStrip) {
5664
+ return buffer;
5665
+ }
5666
+ const image = (0, import_sharp.default)(buffer, { failOn: "none" });
5667
+ const metadata = await image.metadata().catch(() => null);
5668
+ const width = Math.max(1, Number(metadata?.width) || 0);
5669
+ const height = Math.max(1, Number(metadata?.height) || 0);
5670
+ if (!width || !height) {
5671
+ return buffer;
5672
+ }
5673
+ const overlaySvg = buildWatermarkifySvg(meta, width, height);
5674
+ if (!overlaySvg) {
5675
+ return buffer;
5676
+ }
5677
+ return await image.composite([{ input: Buffer.from(overlaySvg), top: 0, left: 0 }]).png().toBuffer();
5678
+ };
5447
5679
 
5448
5680
  // src/share.js
5449
5681
  var logger11 = createInternalLogger("Share");
@@ -5523,6 +5755,20 @@ var normalizeShare = (share) => {
5523
5755
  xurl: normalizeXurl(source.xurl)
5524
5756
  };
5525
5757
  };
5758
+ var resolveCaptureScreenWatermarkify = (page, optionValue) => {
5759
+ if (optionValue === false) {
5760
+ return normalizeScreenshotWatermarkify(false);
5761
+ }
5762
+ const runtimeQuery = String(page?.[PageRuntimeStateKey]?.query || "").trim();
5763
+ if (optionValue == null || optionValue === true) {
5764
+ return normalizeScreenshotWatermarkify(runtimeQuery ? { query: runtimeQuery } : true);
5765
+ }
5766
+ const optionSource = optionValue && typeof optionValue === "object" ? optionValue : {};
5767
+ return normalizeScreenshotWatermarkify({
5768
+ ...runtimeQuery ? { query: runtimeQuery } : {},
5769
+ ...optionSource
5770
+ });
5771
+ };
5526
5772
  var getByPathSegments = (source, pathSegments) => {
5527
5773
  if (!Array.isArray(pathSegments) || pathSegments.length === 0) return void 0;
5528
5774
  let current = source;
@@ -5846,14 +6092,12 @@ var Share = {
5846
6092
  * @returns {Promise<string>} base64 png
5847
6093
  */
5848
6094
  async captureScreen(page, options = {}) {
5849
- const originalViewport = page.viewportSize();
5850
- const defaultBuffer = Math.round((originalViewport?.height || 1080) / 2);
6095
+ const originalViewport = await resolveCurrentViewportSize(page);
6096
+ const defaultBuffer = Math.round((originalViewport.height || 1080) / 2);
5851
6097
  const buffer = options.buffer ?? defaultBuffer;
5852
6098
  const restore = options.restore ?? false;
5853
6099
  const maxHeight = options.maxHeight ?? 8e3;
5854
- const screenshotWatermarkify = normalizeScreenshotWatermarkify(options.watermarkify ?? true);
5855
- let cleanupScreenshotWatermarkify = async () => {
5856
- };
6100
+ const screenshotWatermarkify = resolveCaptureScreenWatermarkify(page, options.watermarkify);
5857
6101
  try {
5858
6102
  const maxScrollHeight = await page.evaluate(() => {
5859
6103
  let maxHeight2 = document.body.scrollHeight;
@@ -5877,23 +6121,26 @@ var Share = {
5877
6121
  });
5878
6122
  const targetHeight = Math.min(maxScrollHeight + buffer, maxHeight);
5879
6123
  await page.setViewportSize({
5880
- width: originalViewport?.width || 1280,
6124
+ width: originalViewport.width,
5881
6125
  height: targetHeight
5882
6126
  });
5883
6127
  await (0, import_delay2.default)(1e3);
5884
- if (screenshotWatermarkify.enabled) {
5885
- const watermarkifyMeta = await resolveScreenshotWatermarkifyMeta(page, screenshotWatermarkify);
5886
- cleanupScreenshotWatermarkify = await installScreenshotWatermarkify(page, watermarkifyMeta);
5887
- await (0, import_delay2.default)(120);
5888
- }
5889
- const buffer_ = await capturePageScreenshot(page, {
6128
+ const capturedAt = /* @__PURE__ */ new Date();
6129
+ const rawBuffer = await capturePageScreenshot(page, {
5890
6130
  fullPage: true,
5891
6131
  type: "png",
5892
6132
  maxClipHeight: targetHeight
5893
6133
  });
6134
+ if (!screenshotWatermarkify.enabled) {
6135
+ return rawBuffer.toString("base64");
6136
+ }
6137
+ const watermarkifyMeta = await resolveScreenshotWatermarkifyMeta(page, {
6138
+ ...screenshotWatermarkify,
6139
+ capturedAt
6140
+ });
6141
+ const buffer_ = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta);
5894
6142
  return buffer_.toString("base64");
5895
6143
  } finally {
5896
- await cleanupScreenshotWatermarkify();
5897
6144
  if (restore) {
5898
6145
  await page.evaluate(() => {
5899
6146
  document.querySelectorAll(".__pk_expanded__").forEach((el) => {
@@ -5906,9 +6153,7 @@ var Share = {
5906
6153
  el.classList.remove("__pk_expanded__");
5907
6154
  });
5908
6155
  });
5909
- if (originalViewport) {
5910
- await page.setViewportSize(originalViewport);
5911
- }
6156
+ await page.setViewportSize(originalViewport);
5912
6157
  }
5913
6158
  }
5914
6159
  }