@linto-ai/transcript-ui-plugin-subtitle 0.9.23 → 0.9.24-unstable.202610151359

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linto-ai/transcript-ui-plugin-subtitle",
3
- "version": "0.9.23",
3
+ "version": "0.9.24-unstable.202610151359",
4
4
  "description": "Subtitle display plugin for @linto-ai/transcript-ui — fullscreen/banner captions with an optional watermark",
5
5
  "keywords": [
6
6
  "vue",
@@ -38,8 +38,8 @@
38
38
  "vue": "^3.5.0"
39
39
  },
40
40
  "dependencies": {
41
- "@linto-ai/transcript-ui-core": "0.9.23",
42
- "@linto-ai/transcript-ui-i18n": "0.9.23",
41
+ "@linto-ai/transcript-ui-core": "0.9.24-unstable.202610151359",
42
+ "@linto-ai/transcript-ui-i18n": "0.9.24-unstable.202610151359",
43
43
  "diff": "^8.0.3",
44
44
  "lucide-vue-next": "^0.564.0"
45
45
  },
@@ -43,8 +43,7 @@ onBeforeUnmount(() => {
43
43
  <canvas
44
44
  ref="canvas"
45
45
  class="subtitle-canvas"
46
- :class="{ 'subtitle-canvas--shrunk': watermarkVisible }"
47
- :height="canvasHeight"></canvas>
46
+ :class="{ 'subtitle-canvas--shrunk': watermarkVisible }"></canvas>
48
47
  <SubtitleWatermark :visible="watermarkVisible" />
49
48
  </div>
50
49
  </template>
@@ -92,13 +92,16 @@ onUnmounted(() => {
92
92
  </template>
93
93
 
94
94
  <style scoped>
95
+ /* Fixed to the viewport on its own: when requestFullscreen() is refused
96
+ (iPhone Safari, WebViews), the view must still cover the editor instead of
97
+ sitting as one more flex child of the layout. */
95
98
  .subtitle-fullscreen {
96
- position: relative;
99
+ position: fixed;
100
+ inset: 0;
101
+ z-index: var(--z-fullscreen);
97
102
  display: flex;
98
103
  align-items: center;
99
104
  justify-content: center;
100
- width: 100%;
101
- height: 100%;
102
105
  background-color: var(--color-black);
103
106
  }
104
107
 
@@ -21,6 +21,10 @@ export class SubtitleDrawer {
21
21
  protected font: string
22
22
  protected paddingInline: number
23
23
  protected isResizing = false
24
+ /** Canvas size in CSS pixels: the unit of every draw and measure call. The
25
+ * bitmap itself is scaled by devicePixelRatio (see resizeBitmap). */
26
+ protected width = 0
27
+ protected height = 0
24
28
  private resizeObserver: ResizeObserver
25
29
 
26
30
  constructor(
@@ -40,13 +44,11 @@ export class SubtitleDrawer {
40
44
  this.font = font
41
45
  this.paddingInline = paddingInline
42
46
 
43
- this.canvas.width = this.canvas.clientWidth
44
- this.canvas.height = this.canvas.clientHeight
47
+ this.resizeBitmap()
45
48
 
46
49
  this.resizeObserver = new ResizeObserver(() => {
47
50
  this.isResizing = true
48
- this.canvas.width = this.canvas.clientWidth
49
- this.canvas.height = this.canvas.clientHeight
51
+ this.resizeBitmap()
50
52
  this.onResize()
51
53
  this.isResizing = false
52
54
  })
@@ -66,7 +68,20 @@ export class SubtitleDrawer {
66
68
 
67
69
  resetDrawing(): void {
68
70
  const ctx = this.canvas.getContext("2d")!
69
- ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
71
+ ctx.clearRect(0, 0, this.width, this.height)
72
+ }
73
+
74
+ // Sizes the bitmap in device pixels so text stays sharp on high-density
75
+ // screens, then scales the context back so callers keep drawing in CSS
76
+ // pixels. Assigning canvas.width resets the context, hence the transform
77
+ // being set again on every call.
78
+ private resizeBitmap(): void {
79
+ const pixelRatio = window.devicePixelRatio || 1
80
+ this.width = this.canvas.clientWidth
81
+ this.height = this.canvas.clientHeight
82
+ this.canvas.width = Math.round(this.width * pixelRatio)
83
+ this.canvas.height = Math.round(this.height * pixelRatio)
84
+ this.canvas.getContext("2d")!.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
70
85
  }
71
86
 
72
87
  protected drawText(text: string, x: number, y: number): void {
@@ -77,11 +92,17 @@ export class SubtitleDrawer {
77
92
  }
78
93
 
79
94
  protected drawFirstLine(text: string): void {
80
- this.drawText(text, 0, this.fontSize)
95
+ this.drawText(text, 0, this.computeTextBlockTop() + this.fontSize)
81
96
  }
82
97
 
83
98
  protected drawSecondLine(text: string): void {
84
- this.drawText(text, 0, this.fontSize + this.lineHeight)
99
+ this.drawText(text, 0, this.computeTextBlockTop() + this.fontSize + this.lineHeight)
100
+ }
101
+
102
+ // The two lines form a block of 2 × lineHeight, centered vertically. The
103
+ // banner canvas is exactly that tall, so there the block starts at the top.
104
+ private computeTextBlockTop(): number {
105
+ return (this.height - 2 * this.lineHeight) / 2
85
106
  }
86
107
 
87
108
  protected onResize(): void {
@@ -125,6 +146,15 @@ export class SubtitleScroller extends SubtitleDrawer {
125
146
  this.draw()
126
147
  }
127
148
 
149
+ /** Drops the provisional line being built and repaints without it — the
150
+ * last finalized line stays up. What the canvas showed just before the
151
+ * partial started, in other words. */
152
+ clearPartial(): void {
153
+ if (this.isResizing) return
154
+ this.currentState = { previousText: "", previousIndexes: [] }
155
+ this.draw()
156
+ }
157
+
128
158
  newFinal(text: string): void {
129
159
  if (this.isResizing) return
130
160
  this.currentState = splitPartialSubtitles(
@@ -186,13 +216,13 @@ export class SubtitleScroller extends SubtitleDrawer {
186
216
 
187
217
  // Normal cut threshold: keeps both inline margins free.
188
218
  private computeIfTextIsTooLong(text: string): boolean {
189
- return this.textWidth(text) > this.canvas.width - 2 * this.paddingInline
219
+ return this.textWidth(text) > this.width - 2 * this.paddingInline
190
220
  }
191
221
 
192
222
  // Overflow threshold: the text is drawn at x = paddingInline, so it reaches the
193
- // canvas edge once its width exceeds canvas.width - paddingInline. Spilling into
223
+ // canvas edge once its width exceeds width - paddingInline. Spilling into
194
224
  // the inline margin is tolerated; going past this re-cuts an already-shown line.
195
225
  private computeIfTextOverflows(text: string): boolean {
196
- return this.textWidth(text) > this.canvas.width - this.paddingInline
226
+ return this.textWidth(text) > this.width - this.paddingInline
197
227
  }
198
228
  }
@@ -36,6 +36,16 @@ export function useSubtitleScroller(options: UseSubtitleScrollerOptions) {
36
36
  },
37
37
  )
38
38
 
39
+ // Clearing core.live.partial is routine — it happens on every finalized
40
+ // turn — so the scroller ignores it and keeps the text on screen. Turning
41
+ // the setting off is the one case where the line must actually go.
42
+ watch(
43
+ () => core.live?.partialsVisible.value,
44
+ (visible) => {
45
+ if (visible === false) scroller?.clearPartial()
46
+ },
47
+ )
48
+
39
49
  const unsubTurnAdd = core.onActiveTranslation("turn:add", ({ turn }) => {
40
50
  if (!scroller) return
41
51
  const text =