primocode 8.34.1 → 8.36.0

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.
@@ -4,6 +4,60 @@
4
4
  Dois modos: layout nomeado (o estúdio posiciona) ou "livre" (x/y/l/a).
5
5
  ========================================================================== */
6
6
 
7
+ /* ── A QUEBRA DA LEGENDA ─────────────────────────────────────────────────
8
+ * Legenda de vídeo tem regra de ofício, e ela não é opinião: linha curta o
9
+ * bastante para o olho pegar de relance (~38 caracteres em 1080p), no máximo
10
+ * três linhas, e a quebra caindo em espaço — nunca no meio da palavra.
11
+ *
12
+ * E as linhas saem EQUILIBRADAS. Encher a primeira até o talo e deixar duas
13
+ * palavras na segunda é o que faz a legenda parecer torta mesmo estando
14
+ * centralizada — foi essa a reclamação.
15
+ */
16
+ var LEGENDA_LARGURA = 38;
17
+
18
+ /** Quebra gulosa numa largura dada: enche a linha até não caber mais. */
19
+ function envolver(palavras, largura) {
20
+ var linhas = [];
21
+ var atual = "";
22
+ for (var i = 0; i < palavras.length; i++) {
23
+ var tenta = atual ? atual + " " + palavras[i] : palavras[i];
24
+ if (atual && tenta.length > largura) { linhas.push(atual); atual = palavras[i]; }
25
+ else atual = tenta;
26
+ }
27
+ if (atual) linhas.push(atual);
28
+ return linhas;
29
+ }
30
+
31
+ function quebrarLegenda(texto) {
32
+ var t = String(texto == null ? "" : texto).replace(/\s+/g, " ").trim();
33
+ if (!t) return "";
34
+ if (t.length <= LEGENDA_LARGURA) return t;
35
+
36
+ var palavras = t.split(" ");
37
+ var quantas = envolver(palavras, LEGENDA_LARGURA).length;
38
+
39
+ /* O EQUILÍBRIO.
40
+ *
41
+ * A quebra gulosa enche a primeira linha até o talo e larga duas palavras na
42
+ * última — é isso que faz a legenda parecer torta mesmo estando
43
+ * centralizada, e foi essa a reclamação.
44
+ *
45
+ * A primeira tentativa foi dividir o comprimento pelo número de linhas e
46
+ * cortar no alvo. Não serve: o resto se acumula na última, que estourava a
47
+ * largura (41 caracteres numa régua de 38).
48
+ *
49
+ * O que serve é apertar a régua: procurar a MENOR largura que ainda cabe no
50
+ * mesmo número de linhas. Aí nenhuma linha passa do teto — a régua só
51
+ * encolheu — e todas ficam parecidas, porque foi o aperto que as igualou.
52
+ */
53
+ var melhor = LEGENDA_LARGURA;
54
+ for (var w = LEGENDA_LARGURA - 1; w >= 12; w--) {
55
+ if (envolver(palavras, w).length > quantas) break;
56
+ melhor = w;
57
+ }
58
+ return envolver(palavras, melhor).join("\n");
59
+ }
60
+
7
61
  var PAPEIS = {
8
62
  chapeu: { classe: "chapeu" },
9
63
  titulo: { tamanho: 84, peso: 660, altura: 1.06 },
@@ -481,7 +535,15 @@ function renderElemento(e, ctx) {
481
535
  if (e.peso || papel.peso) no.style.fontWeight = e.peso || papel.peso;
482
536
  if (papel.altura) no.style.lineHeight = papel.altura;
483
537
  no.style.color = e.cor || papel.cor || "";
484
- if (e.alinhar) { no.style.textAlign = e.alinhar; no.style.alignItems = e.alinhar === "centro" ? "center" : (e.alinhar === "direita" ? "flex-end" : "flex-start"); }
538
+ /* LEGENDA DE FALA É SEMPRE CENTRALIZADA, e o `alinhar` do modelo não
539
+ manda nela. Legenda de vídeo alinhada à esquerda não existe em lugar
540
+ nenhum — ela é lida em movimento, e o olho volta para o meio da tela.
541
+ Bastava o modelo escrever `alinhar: "esquerda"` uma vez para a legenda
542
+ sair torta, e ele escrevia. */
543
+ if (e.alinhar && e.papel !== "fala") {
544
+ no.style.textAlign = e.alinhar;
545
+ no.style.alignItems = e.alinhar === "centro" ? "center" : (e.alinhar === "direita" ? "flex-end" : "flex-start");
546
+ }
485
547
  if (e.fonte === "mono") no.style.fontFamily = "var(--f-mono)";
486
548
  if (e.fonte === "display") no.style.fontFamily = "var(--f-display)";
487
549
  if (e.maiusculas) no.style.textTransform = "uppercase";
@@ -489,7 +551,26 @@ function renderElemento(e, ctx) {
489
551
  if (e.altura != null) no.style.lineHeight = e.altura;
490
552
  if (e.estilo) no.setAttribute("data-estilo", e.estilo);
491
553
 
492
- var corpoTexto = _rico(e.texto);
554
+ /* A LEGENDA SE QUEBRA SOZINHA.
555
+ "A IA não consegue formatar bem" — e não vai conseguir: ela escreve a
556
+ frase, não a diagramação. Quem sabe quantos caracteres cabem numa
557
+ linha de 1080p é este código, e é ele que quebra.
558
+
559
+ Sem isto a legenda saía como parágrafo: cinco linhas subindo pela
560
+ tela, tapando o rosto de quem fala. */
561
+ var corpoTexto = _rico(e.papel === "fala" ? quebrarLegenda(e.texto) : e.texto);
562
+ if (e.papel === "fala" && !e.tamanho) {
563
+ /* Legenda longa ENCOLHE em vez de crescer para cima. Três linhas com o
564
+ corpo de uma linha comem metade do quadro, e o rosto de quem fala
565
+ fica atrás delas.
566
+
567
+ Não se corta palavra: perder o que foi dito é pior que letra menor.
568
+ O piso de 62% é onde a letra ainda se lê num celular. */
569
+ var nLinhas = String(quebrarLegenda(e.texto)).split("\n").length;
570
+ var base = (PAPEIS.fala.tamanho || 52) * (ctx.escalaTipo || 1);
571
+ var fator = Math.max(0.62, 1 - (nLinhas - 1) * 0.10);
572
+ if (nLinhas > 1) no.style.fontSize = _px(Math.round(base * fator));
573
+ }
493
574
 
494
575
  // marcador e fita precisam envolver cada linha para o fundo acompanhar
495
576
  if (e.estilo === "marcador" || e.estilo === "fita") {
@@ -844,6 +925,32 @@ function renderElemento(e, ctx) {
844
925
  if (typeof e.de === "number") vid.dataset.de = e.de;
845
926
  if (typeof e.ate === "number") vid.dataset.ate = e.ate;
846
927
  no.style.borderRadius = _px(e.raio != null ? e.raio : 0);
928
+
929
+ /* ── SÓ A VOZ, COM A TELA MONTADA POR CIMA ────────────────────────
930
+ *
931
+ * "Não deixa opção de manter só a voz com uma tela de exemplos."
932
+ *
933
+ * Faltava mesmo, e não era teimosia do agente: o vocabulário tinha
934
+ * `mudo` (imagem sem som) e NÃO tinha o contrário. Sem isto, usar a
935
+ * gravação como narração — a voz da pessoa por cima de telas montadas
936
+ * pelo estúdio — era impossível de escrever, por mais que se pedisse.
937
+ *
938
+ * O <video> continua no lugar, carregando e tocando: é ele que leva o
939
+ * áudio para o grafo da trilha (e daí para o MP4) e é o `de`/`ate` dele
940
+ * que dá a duração da cena. O que muda é que ele não aparece — some da
941
+ * tela e some do quadro exportado, que é o `data-som` lido no
942
+ * `video.js`. Um <video> com `display:none` também continuaria tocando,
943
+ * mas alguns navegadores param de decodificar o que não está no
944
+ * layout, e sem quadro decodificado o player o trata como não-pronto. */
945
+ if (e.apenas === "som" || e.som === true) {
946
+ vid.dataset.som = "1";
947
+ no.style.position = "absolute";
948
+ no.style.left = "0"; no.style.top = "0";
949
+ no.style.width = "1px"; no.style.height = "1px";
950
+ no.style.opacity = "0";
951
+ no.style.pointerEvents = "none";
952
+ no.style.overflow = "hidden";
953
+ }
847
954
  no.appendChild(vid);
848
955
  break;
849
956
  }
@@ -192,6 +192,10 @@ var VideoExport = (function () {
192
192
  // para desenhar levanta exceção em alguns navegadores e mancha o
193
193
  // canvas em outros.
194
194
  if (v.readyState < 2 || !v.videoWidth) return;
195
+ // Clipe de SÓ ÁUDIO: entra pelo som e não pelo quadro. Ele tem 1px na
196
+ // tela, então sem esta linha o export desenharia uma tira de um pixel
197
+ // esticada por cima da cena montada — visível, e sem causa aparente.
198
+ if (v.dataset && v.dataset.som === "1") return;
195
199
  var r = v.getBoundingClientRect();
196
200
  var dx = (r.left - caixaCena.left) * escX;
197
201
  var dy = (r.top - caixaCena.top) * escY;