clipwise 0.7.1 → 0.7.2

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.
@@ -460,6 +460,44 @@ import sharp5 from "sharp";
460
460
  function escapeXml(s) {
461
461
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
462
462
  }
463
+ function isCJK(ch) {
464
+ const code = ch.codePointAt(0) ?? 0;
465
+ return code >= 4352 && code <= 4607 || // 한글 자모
466
+ code >= 11904 && code <= 40959 || // CJK 부수, 한자
467
+ code >= 44032 && code <= 55215 || // 한글 음절
468
+ code >= 63744 && code <= 64255 || // CJK 호환 한자
469
+ code >= 65072 && code <= 65103 || // CJK 호환 형태
470
+ code >= 65280 && code <= 65519 || // Full-width 문자
471
+ code >= 12288 && code <= 12543 || // CJK 기호, 히라가나, 가타카나
472
+ code >= 12784 && code <= 12799 || // 가타카나 확장
473
+ code >= 131072 && code <= 195103;
474
+ }
475
+ function displayWidth(text) {
476
+ let w = 0;
477
+ for (const ch of text) {
478
+ w += isCJK(ch) ? 1.7 : 1;
479
+ }
480
+ return w;
481
+ }
482
+ function wrapText(text, maxWidth) {
483
+ if (displayWidth(text) <= maxWidth) return [text];
484
+ const lines = [];
485
+ let current = "";
486
+ let currentWidth = 0;
487
+ for (const ch of text) {
488
+ const chWidth = isCJK(ch) ? 1.7 : 1;
489
+ if (currentWidth + chWidth > maxWidth && current.length > 0) {
490
+ lines.push(current);
491
+ current = ch;
492
+ currentWidth = chWidth;
493
+ } else {
494
+ current += ch;
495
+ currentWidth += chWidth;
496
+ }
497
+ }
498
+ if (current.length > 0) lines.push(current);
499
+ return lines;
500
+ }
463
501
  function buildSessions(keystrokes) {
464
502
  const hasSessionIds = keystrokes.some((k) => k.sessionId !== void 0);
465
503
  if (!hasSessionIds) {
@@ -493,16 +531,22 @@ async function renderKeystrokeHud(frameBuffer, keystrokes, frameTimestamp, confi
493
531
  const lineGap = Math.round(fontSize * 0.45);
494
532
  const charWidth = fontSize * 0.615;
495
533
  const maxHudWidth = frameWidth - 60 * dpr;
496
- const maxCharsPerLine = Math.max(10, Math.floor((maxHudWidth - hudPadH * 2) / charWidth));
497
- const lines = sessions.map(
498
- (text) => text.length > maxCharsPerLine ? text.slice(-maxCharsPerLine) : text
499
- );
500
- const maxLineLen = Math.max(...lines.map((l) => l.length));
534
+ const maxDisplayWidth = Math.max(10, (maxHudWidth - hudPadH * 2) / charWidth);
535
+ const wrappedLines = [];
536
+ sessions.forEach((text, sIdx) => {
537
+ const wrapped = wrapText(text, maxDisplayWidth);
538
+ for (const line of wrapped) {
539
+ wrappedLines.push({ text: line, sessionIdx: sIdx });
540
+ }
541
+ });
542
+ const lines = wrappedLines.map((l) => l.text);
543
+ const totalLineCount = lines.length;
544
+ const maxLineDisplayWidth = Math.max(...lines.map((l) => displayWidth(l)));
501
545
  const hudWidth = Math.min(
502
- Math.ceil(maxLineLen * charWidth) + hudPadH * 2,
546
+ Math.ceil(maxLineDisplayWidth * charWidth) + hudPadH * 2,
503
547
  maxHudWidth
504
548
  );
505
- const hudHeight = Math.ceil(fontSize * lineCount + lineGap * (lineCount - 1) + hudPadV * 2);
549
+ const hudHeight = Math.ceil(fontSize * totalLineCount + lineGap * (totalLineCount - 1) + hudPadV * 2);
506
550
  const margin = 30 * dpr;
507
551
  const hudY = frameHeight - hudHeight - margin;
508
552
  let hudX;
@@ -517,18 +561,20 @@ async function renderKeystrokeHud(frameBuffer, keystrokes, frameTimestamp, confi
517
561
  default:
518
562
  hudX = Math.round((frameWidth - hudWidth) / 2);
519
563
  }
520
- const LINE_OPACITY_FACTORS = [0.45, 0.7, 1];
521
- const opacityFactors = LINE_OPACITY_FACTORS.slice(-lineCount);
564
+ const SESSION_OPACITY_FACTORS = [0.45, 0.7, 1];
565
+ const sessionOpacities = SESSION_OPACITY_FACTORS.slice(-lineCount);
522
566
  const rx = (8 * dpr).toFixed(1);
523
567
  const boxOp = (globalOpacity * 0.92).toFixed(3);
524
568
  const textX = hudX + hudPadH;
525
569
  const baselineY = hudY + hudPadV + fontSize * 0.82;
526
- const textElements = lines.map((line, i) => {
527
- const op = (globalOpacity * opacityFactors[i]).toFixed(3);
570
+ const textElements = wrappedLines.map(({ text, sessionIdx }, i) => {
571
+ const sessionPos = sessions.length <= 3 ? sessionIdx : sessionIdx - (sessions.length - 3);
572
+ const opFactor = sessionOpacities[Math.max(0, sessionPos)] ?? 1;
573
+ const op = (globalOpacity * opFactor).toFixed(3);
528
574
  const lineY = baselineY + i * (fontSize + lineGap);
529
575
  return `<text x="${textX}" y="${lineY}"
530
576
  font-family="monospace, Menlo, Consolas" font-size="${fontSize}"
531
- fill="${config.textColor}" opacity="${op}">${escapeXml(line)}</text>`;
577
+ fill="${config.textColor}" opacity="${op}">${escapeXml(text)}</text>`;
532
578
  }).join("\n ");
533
579
  const hudSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${frameWidth}" height="${frameHeight}" shape-rendering="geometricPrecision" text-rendering="geometricPrecision">
534
580
  <rect x="${hudX}" y="${hudY}" width="${hudWidth}" height="${hudHeight}"