primocode 11.1.0 → 11.2.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.
- package/README.md +16 -2
- package/bin/primocode-rpc.js +100 -0
- package/lib/agentes-pessoais.js +117 -0
- package/lib/app-server/cli.js +61 -0
- package/lib/app-server/index.js +3 -0
- package/lib/app-server/pareamento.js +61 -0
- package/lib/app-server/protocolo.js +86 -0
- package/lib/app-server/servidor.js +685 -0
- package/lib/app-server/websocket.js +87 -0
- package/lib/avisos.js +64 -0
- package/lib/cli/sessao.js +28 -1
- package/lib/cli-nova.js +237 -0
- package/lib/config/config.js +7 -2
- package/lib/motor/aprovacao.js +114 -0
- package/lib/motor/eventos.js +77 -0
- package/lib/motor/index.js +8 -0
- package/lib/motor/tela-terminal.js +64 -0
- package/lib/motor/turno.js +43 -0
- package/lib/motor/voz.js +65 -0
- package/lib/piloto.js +39 -0
- package/lib/plugin/estudio/criar.js +113 -0
- package/lib/plugin/estudio/historico.js +60 -0
- package/lib/plugin/estudio/index.js +14 -1
- package/lib/plugin/estudio/miniatura.js +56 -0
- package/lib/plugin/video/auto.js +1 -1
- package/lib/plugin/video/catalogo.js +20 -0
- package/lib/plugin/video/mutar.js +28 -0
- package/lib/plugin/video/ops-fx.js +6 -6
- package/lib/plugin/video/ops.js +2 -13
- package/lib/plugin/video/pedido.js +1 -1
- package/lib/plugin/video/zoom.js +1 -1
- package/lib/projetos-meta.js +90 -0
- package/lib/qr.js +209 -0
- package/lib/session/runner/act.js +104 -64
- package/lib/skill/catalogo.js +3 -1
- package/lib/tool/tools.js +6 -0
- package/lib/tui/ui.js +1 -1
- package/package.json +6 -2
|
@@ -215,7 +215,7 @@ const OPS = {
|
|
|
215
215
|
ate = Math.min(asset.seconds, ate);
|
|
216
216
|
if (!(ate > de + 0.5)) throw T.erro('o momento 3D precisa de pelo menos meio segundo');
|
|
217
217
|
}
|
|
218
|
-
return require('./
|
|
218
|
+
return require('./mutar.js').mutar(ctx, 'separate_layers', (tl) => {
|
|
219
219
|
const pessoa = camadaDaPessoa(tl, asset, rs);
|
|
220
220
|
const out = { layers: { pessoa: pessoa.length, fundo: 'a gravação embaixo' } };
|
|
221
221
|
if (a.show3d !== undefined) { C.limparPapel(tl, 'camadas3d', asset.id); C.limparGerados(tl); }
|
|
@@ -261,7 +261,7 @@ const OPS = {
|
|
|
261
261
|
const seg = a.at === undefined ? await momento(ctx, asset, 'snap').catch(() => 0) : await momento(ctx, asset, a.at);
|
|
262
262
|
const medido = await r().medirAsset(ctx.tl, arquivo);
|
|
263
263
|
const ganho = ganhoDeCor(await corMedia(rs.arquivos.fundo), await corMedia(arquivo));
|
|
264
|
-
return require('./
|
|
264
|
+
return require('./mutar.js').mutar(ctx, 'replace_background', (tl) => {
|
|
265
265
|
if (!temPessoa(tl, asset.id)) camadaDaPessoa(tl, asset, rs);
|
|
266
266
|
const F = quadro({ tl }, asset, seg) ?? 0;
|
|
267
267
|
const fim = a.until !== undefined ? Math.round(Number(a.until)) : T.duracao(tl);
|
|
@@ -334,7 +334,7 @@ const OPS = {
|
|
|
334
334
|
if (de === null) de = t[0];
|
|
335
335
|
const ate = a.to !== undefined ? await momento(ctx, asset, a.to) : Math.min(asset.seconds, Math.max(t[1] + 0.3, de + 1.5));
|
|
336
336
|
if (!(ate > de)) throw T.erro('"to" antes de "from"');
|
|
337
|
-
return require('./
|
|
337
|
+
return require('./mutar.js').mutar(ctx, 'attach_3d', (tl) => {
|
|
338
338
|
const f = fxDe(tl, asset.id);
|
|
339
339
|
const o = { id: `o${f.objetos.length + 1}`, modelo, mao, de: r2(de * 1000) / 1000, ate: r2(ate * 1000) / 1000 };
|
|
340
340
|
if (a.color) o.cor = C.cor(a.color);
|
|
@@ -365,7 +365,7 @@ const OPS = {
|
|
|
365
365
|
}
|
|
366
366
|
const ateFusao = a.until !== undefined ? await momento(ctx, asset, a.until) : null;
|
|
367
367
|
const depois = a.then === 'none' || a.then === false ? null : C.modeloDe(a.then || 'brain') || 'cerebro';
|
|
368
|
-
return require('./
|
|
368
|
+
return require('./mutar.js').mutar(ctx, 'merge_on_join', (tl) => {
|
|
369
369
|
const f = fxDe(tl, asset.id);
|
|
370
370
|
// Os objetos vivos na junção ficam até ela (e se fundem).
|
|
371
371
|
for (const o of f.objetos) if (o.de < junta && o.ate < junta + 0.5) o.ate = r2((junta + 0.6) * 1000) / 1000;
|
|
@@ -400,7 +400,7 @@ const OPS = {
|
|
|
400
400
|
if (i1 > i0) ranges.push([T.origemParaQuadro(ctx.tl, c, i0), T.origemParaQuadro(ctx.tl, c, i1)]);
|
|
401
401
|
}
|
|
402
402
|
if (!ranges.length) return { ok: false, error: 'esse silêncio já foi cortado' };
|
|
403
|
-
return require('./
|
|
403
|
+
return require('./mutar.js').mutar(ctx, 'cut_silence_at', (tl) => ({ silence: { de: r2(de), ate: r2(ate) }, ...E.apagarTrechos(tl, ranges) }));
|
|
404
404
|
},
|
|
405
405
|
},
|
|
406
406
|
style_captions: {
|
|
@@ -423,7 +423,7 @@ const OPS = {
|
|
|
423
423
|
const CY = { bottom: 0.78, baixo: 0.78, top: 0.14, topo: 0.14, center: 0.5, centro: 0.5, middle: 0.5 };
|
|
424
424
|
if (pos && !seguir && CY[pos] === undefined) throw T.erro(`position "${a.position}" (use above_head, bottom, top ou center)`);
|
|
425
425
|
const corPedida = a.color !== undefined ? C.cor(a.color) : undefined;
|
|
426
|
-
return require('./
|
|
426
|
+
return require('./mutar.js').mutar(ctx, 'style_captions', (t) => {
|
|
427
427
|
for (const ref of alvo) {
|
|
428
428
|
const c = T.achar(t, ref.id).clip;
|
|
429
429
|
const st = { ...(c.text.style || {}) };
|
package/lib/plugin/video/ops.js
CHANGED
|
@@ -17,6 +17,7 @@ const T = require('./timeline.js');
|
|
|
17
17
|
const E = require('./edits.js');
|
|
18
18
|
const F = require('./filters.js');
|
|
19
19
|
const { diff } = require('./delta.js');
|
|
20
|
+
const { mutar } = require('./mutar.js');
|
|
20
21
|
const P = require('./projeto.js');
|
|
21
22
|
const TR = require('./transcript.js');
|
|
22
23
|
const A = require('./analise.js');
|
|
@@ -35,19 +36,6 @@ function alvos(tl, args, { tipos = ['video', 'image'] } = {}) {
|
|
|
35
36
|
return refs.map((ref) => T.achar(tl, ref).clip.id);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
async function mutar(ctx, op, fn) {
|
|
39
|
-
const antes = T.clone(ctx.tl);
|
|
40
|
-
const extra = (await fn(ctx.tl)) || {};
|
|
41
|
-
T.ordenar(ctx.tl);
|
|
42
|
-
const probs = T.problemas(ctx.tl);
|
|
43
|
-
if (probs.length) throw T.erro(`edição recusada (nada mudou): ${probs.slice(0, 4).join('; ')}`);
|
|
44
|
-
P.fotografar(ctx.p, antes, op);
|
|
45
|
-
P.salvar(ctx.p, ctx.tl);
|
|
46
|
-
const d = diff(antes, ctx.tl, extra);
|
|
47
|
-
P.anotar(ctx.p, op, { dur: d.dur, added: (d.added || []).length, removed: (d.removed || []).length });
|
|
48
|
-
return d;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
39
|
function precisaProjeto(ctx) {
|
|
52
40
|
if (!ctx.tl) throw T.erro('nenhum projeto aberto: comece com import {"path":"..."} (cria o projeto) ou new');
|
|
53
41
|
}
|
|
@@ -311,6 +299,7 @@ const PED = require('./pedido.js');
|
|
|
311
299
|
const AUTO = require('./auto.js');
|
|
312
300
|
const ZOOM = require('./zoom.js'); // o zoom que segue o cursor (gravação de tela): fica na ajuda principal
|
|
313
301
|
Object.assign(OPS, FXO.OPS, PED.OPS, AUTO.OPS, ZOOM.OPS);
|
|
302
|
+
require('./catalogo.js').registrar(OPS); // auto e pedido encadeiam operações por aqui
|
|
314
303
|
|
|
315
304
|
const ALIAS = { timeline: 'get_timeline', transcript: 'get_transcript', captions: 'add_captions', texts: 'add_texts', render: 'export', pedido: 'do', analyze: 'track' };
|
|
316
305
|
|
|
@@ -152,7 +152,7 @@ const OPS = {
|
|
|
152
152
|
const resumo = plano.passos.map((p) => ({ op: p.op, args: p.args }));
|
|
153
153
|
if (a.dryRun) return { ok: true, plano: resumo, ...(plano.avisos.length ? { avisos: plano.avisos } : {}) };
|
|
154
154
|
if (!tl) throw T.erro('nenhum projeto aberto: import {"path":"..."} primeiro');
|
|
155
|
-
const
|
|
155
|
+
const TODAS = require('./catalogo.js').catalogo();
|
|
156
156
|
// O que é lento e não depende de nada roda JUNTO antes do primeiro passo.
|
|
157
157
|
const C2 = require('./camadas.js');
|
|
158
158
|
const asset = C2.base(tl);
|
package/lib/plugin/video/zoom.js
CHANGED
|
@@ -231,7 +231,7 @@ const OPS = {
|
|
|
231
231
|
async run(ctx, a) {
|
|
232
232
|
const T = require('./timeline.js');
|
|
233
233
|
const E = require('./edits.js');
|
|
234
|
-
const { mutar } = require('./
|
|
234
|
+
const { mutar } = require('./mutar.js');
|
|
235
235
|
if (!ctx.tl) throw T.erro('nenhum projeto aberto: comece com import {"path":"..."}');
|
|
236
236
|
const primeiro = T.todosOsClipes(ctx.tl).find((x) => x.clip.type === 'video');
|
|
237
237
|
if (!a.id && !primeiro) throw T.erro('não há clipe de vídeo');
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Projetos do app: pasta em ~/primocode/<projeto>/ + .primocode/projeto.json
|
|
2
|
+
// (nome, descrição, ícone, cor). docs/estrutura.md, seção 7. Node puro.
|
|
3
|
+
// "Apagar" só arquiva (marca no projeto.json): os arquivos da pessoa nunca são removidos.
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const ICONES = ['folder', 'film', 'palette', 'workflow', 'pen', 'home', 'shield', 'bell', 'sliders'];
|
|
10
|
+
const COR_PADRAO = '#22d3ee';
|
|
11
|
+
const hexOk = (h) => /^#[0-9a-f]{6}$/i.test(String(h || ''));
|
|
12
|
+
const cfg = () => require('./config/config');
|
|
13
|
+
const raiz = () => cfg().ROOT_DIR;
|
|
14
|
+
const metaDe = (dir) => path.join(dir, '.primocode', 'projeto.json');
|
|
15
|
+
|
|
16
|
+
function lerMeta(dir) { try { return JSON.parse(fs.readFileSync(metaDe(dir), 'utf8')); } catch { return {}; } }
|
|
17
|
+
function gravarMeta(dir, meta) {
|
|
18
|
+
fs.mkdirSync(path.join(dir, '.primocode'), { recursive: true });
|
|
19
|
+
fs.writeFileSync(metaDe(dir), JSON.stringify(meta, null, 2) + '\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* id = nome da pasta. Só vale se a pasta existe dentro da raiz. */
|
|
23
|
+
function pastaDe(id) {
|
|
24
|
+
const s = String(id || '');
|
|
25
|
+
if (!/^[\w][\w.-]{0,80}$/.test(s)) return null;
|
|
26
|
+
const dir = path.join(raiz(), s);
|
|
27
|
+
try { return fs.statSync(dir).isDirectory() ? dir : null; } catch { return null; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Se a pasta é um repositório git, devolve { git: true, ramo }: lido do .git/HEAD, sem rodar o git. */
|
|
31
|
+
function ramoGit(dir) {
|
|
32
|
+
try {
|
|
33
|
+
const cabeca = fs.readFileSync(path.join(dir, '.git', 'HEAD'), 'utf8').trim();
|
|
34
|
+
const m = /^ref: refs\/heads\/(.+)$/.exec(cabeca);
|
|
35
|
+
return { git: true, ramo: m ? m[1] : cabeca.slice(0, 7) };
|
|
36
|
+
} catch { return { git: false, ramo: '' }; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resumo(id) {
|
|
40
|
+
const dir = path.join(raiz(), id);
|
|
41
|
+
const m = lerMeta(dir);
|
|
42
|
+
return {
|
|
43
|
+
id, nome: m.nome || id, descricao: m.descricao || '',
|
|
44
|
+
icone: ICONES.includes(m.icone) ? m.icone : 'folder', cor: hexOk(m.cor) ? m.cor : COR_PADRAO,
|
|
45
|
+
caminho: dir, arquivado: !!m.arquivado, criado: m.criado || null,
|
|
46
|
+
...ramoGit(dir),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function listar({ arquivados = false } = {}) {
|
|
51
|
+
return cfg().listProjects().map(resumo).filter((p) => arquivados || !p.arquivado);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function limpar(c, { criando = false } = {}) {
|
|
55
|
+
const out = {};
|
|
56
|
+
if ('nome' in c) { out.nome = String(c.nome || '').trim().slice(0, 80); if (!out.nome) throw new Error('falta o nome'); }
|
|
57
|
+
else if (criando) throw new Error('falta o nome');
|
|
58
|
+
if ('descricao' in c) out.descricao = String(c.descricao || '').trim().slice(0, 240);
|
|
59
|
+
if ('icone' in c && c.icone) { if (!ICONES.includes(c.icone)) throw new Error(`ícone inválido (use: ${ICONES.join(', ')})`); out.icone = c.icone; }
|
|
60
|
+
if ('cor' in c && c.cor) { if (!hexOk(c.cor)) throw new Error('cor inválida (use #rrggbb)'); out.cor = String(c.cor).toLowerCase(); }
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function criar(c = {}) {
|
|
65
|
+
const campos = limpar(c, { criando: true });
|
|
66
|
+
const dir = cfg().createProjectDir(campos.nome);
|
|
67
|
+
const id = path.basename(dir);
|
|
68
|
+
const atual = lerMeta(dir);
|
|
69
|
+
if (atual.nome && !atual.arquivado) throw new Error(`já existe o projeto "${atual.nome}"`);
|
|
70
|
+
gravarMeta(dir, { ...atual, ...campos, arquivado: false, criado: atual.criado || new Date().toISOString() });
|
|
71
|
+
return resumo(id);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function atualizar(id, c = {}) {
|
|
75
|
+
const dir = pastaDe(id);
|
|
76
|
+
if (!dir) throw new Error(`não achei o projeto ${id}`);
|
|
77
|
+
const campos = limpar(c);
|
|
78
|
+
if (!Object.keys(campos).length) throw new Error('nada para mudar (nome, descricao, icone, cor)');
|
|
79
|
+
gravarMeta(dir, { ...lerMeta(dir), ...campos });
|
|
80
|
+
return resumo(path.basename(dir));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function arquivar(id) {
|
|
84
|
+
const dir = pastaDe(id);
|
|
85
|
+
if (!dir) throw new Error(`não achei o projeto ${id}`);
|
|
86
|
+
gravarMeta(dir, { ...lerMeta(dir), arquivado: true });
|
|
87
|
+
return { id: path.basename(dir), arquivado: true, arquivos: 'mantidos' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { ICONES, listar, criar, atualizar, arquivar, pastaDe, resumo };
|
package/lib/qr.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Gerador de QR code mínimo, sem dependência: modo byte (UTF-8), correção L ou M, versões 1 a 10
|
|
2
|
+
// (até 213 bytes no M, 271 no L). Serve para o pareamento do celular (docs/estrutura.md, seção 15).
|
|
3
|
+
// Algoritmo da ISO/IEC 18004: Reed-Solomon em GF(256), 8 máscaras, escolhe a de menor penalidade.
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const ECC_POR_BLOCO = { L: [7, 10, 15, 20, 26, 18, 20, 24, 30, 18], M: [10, 16, 26, 18, 24, 16, 18, 22, 22, 26] };
|
|
7
|
+
const NUM_BLOCOS = { L: [1, 1, 1, 1, 1, 2, 2, 2, 2, 4], M: [1, 1, 1, 2, 2, 4, 4, 4, 5, 5] };
|
|
8
|
+
const FORMATO_ECC = { L: 1, M: 0 };
|
|
9
|
+
const VERSAO_MAX = 10;
|
|
10
|
+
|
|
11
|
+
/* Módulos que sobram para dados + correção, descontados padrões de função. */
|
|
12
|
+
function modulosBrutos(v) {
|
|
13
|
+
let r = (16 * v + 128) * v + 64;
|
|
14
|
+
if (v >= 2) {
|
|
15
|
+
const n = Math.floor(v / 7) + 2;
|
|
16
|
+
r -= (25 * n - 10) * n - 55;
|
|
17
|
+
if (v >= 7) r -= 36;
|
|
18
|
+
}
|
|
19
|
+
return r;
|
|
20
|
+
}
|
|
21
|
+
const dadosCodewords = (v, ecc) => Math.floor(modulosBrutos(v) / 8) - ECC_POR_BLOCO[ecc][v - 1] * NUM_BLOCOS[ecc][v - 1];
|
|
22
|
+
|
|
23
|
+
/* GF(256) com o polinômio 0x11D. */
|
|
24
|
+
function multiplicar(x, y) {
|
|
25
|
+
let z = 0;
|
|
26
|
+
for (let i = 7; i >= 0; i--) { z = (z << 1) ^ ((z >>> 7) * 0x11D); z ^= ((y >>> i) & 1) * x; }
|
|
27
|
+
return z;
|
|
28
|
+
}
|
|
29
|
+
function divisor(grau) {
|
|
30
|
+
const r = new Array(grau).fill(0);
|
|
31
|
+
r[grau - 1] = 1;
|
|
32
|
+
let raiz = 1;
|
|
33
|
+
for (let i = 0; i < grau; i++) {
|
|
34
|
+
for (let j = 0; j < r.length; j++) { r[j] = multiplicar(r[j], raiz); if (j + 1 < r.length) r[j] ^= r[j + 1]; }
|
|
35
|
+
raiz = multiplicar(raiz, 2);
|
|
36
|
+
}
|
|
37
|
+
return r;
|
|
38
|
+
}
|
|
39
|
+
function resto(dados, div) {
|
|
40
|
+
const r = new Array(div.length).fill(0);
|
|
41
|
+
for (const b of dados) {
|
|
42
|
+
const f = b ^ r.shift();
|
|
43
|
+
r.push(0);
|
|
44
|
+
div.forEach((c, i) => { r[i] ^= multiplicar(c, f); });
|
|
45
|
+
}
|
|
46
|
+
return r;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bitsDoTexto(bytes, v, ecc) {
|
|
50
|
+
const bits = [];
|
|
51
|
+
const poe = (valor, n) => { for (let i = n - 1; i >= 0; i--) bits.push((valor >>> i) & 1); };
|
|
52
|
+
poe(0b0100, 4);
|
|
53
|
+
poe(bytes.length, v <= 9 ? 8 : 16);
|
|
54
|
+
for (const b of bytes) poe(b, 8);
|
|
55
|
+
const cap = dadosCodewords(v, ecc) * 8;
|
|
56
|
+
poe(0, Math.min(4, cap - bits.length));
|
|
57
|
+
poe(0, (8 - (bits.length % 8)) % 8);
|
|
58
|
+
for (let pad = 0xEC; bits.length < cap; pad ^= 0xEC ^ 0x11) poe(pad, 8);
|
|
59
|
+
const cw = [];
|
|
60
|
+
for (let i = 0; i < bits.length; i += 8) cw.push(parseInt(bits.slice(i, i + 8).join(''), 2));
|
|
61
|
+
return cw;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Divide em blocos, calcula a correção e intercala. */
|
|
65
|
+
function intercalar(dados, v, ecc) {
|
|
66
|
+
const nb = NUM_BLOCOS[ecc][v - 1];
|
|
67
|
+
const eb = ECC_POR_BLOCO[ecc][v - 1];
|
|
68
|
+
const total = Math.floor(modulosBrutos(v) / 8);
|
|
69
|
+
const curtos = nb - (total % nb);
|
|
70
|
+
const lenCurto = Math.floor(total / nb) - eb;
|
|
71
|
+
const div = divisor(eb);
|
|
72
|
+
const blocos = [];
|
|
73
|
+
for (let i = 0, k = 0; i < nb; i++) {
|
|
74
|
+
const dat = dados.slice(k, k + lenCurto + (i < curtos ? 0 : 1));
|
|
75
|
+
k += dat.length;
|
|
76
|
+
blocos.push({ dat, ecc: resto(dat, div) });
|
|
77
|
+
}
|
|
78
|
+
const out = [];
|
|
79
|
+
for (let i = 0; i <= lenCurto; i++) for (const b of blocos) if (i < b.dat.length) out.push(b.dat[i]);
|
|
80
|
+
for (let i = 0; i < eb; i++) for (const b of blocos) out.push(b.ecc[i]);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function criarMatriz(v) {
|
|
85
|
+
const n = v * 4 + 17;
|
|
86
|
+
return { n, m: Array.from({ length: n }, () => new Array(n).fill(false)), f: Array.from({ length: n }, () => new Array(n).fill(false)) };
|
|
87
|
+
}
|
|
88
|
+
function posAlinhamento(v) {
|
|
89
|
+
if (v === 1) return [];
|
|
90
|
+
const n = Math.floor(v / 7) + 2;
|
|
91
|
+
const passo = v === 32 ? 26 : Math.ceil((v * 4 + 4) / (n * 2 - 2)) * 2;
|
|
92
|
+
const r = [6];
|
|
93
|
+
for (let p = v * 4 + 10; r.length < n; p -= passo) r.splice(1, 0, p);
|
|
94
|
+
return r;
|
|
95
|
+
}
|
|
96
|
+
function desenharFuncoes(Q, v, ecc) {
|
|
97
|
+
const { n, m, f } = Q;
|
|
98
|
+
const set = (x, y, cor) => { if (x >= 0 && x < n && y >= 0 && y < n) { m[y][x] = cor; f[y][x] = true; } };
|
|
99
|
+
for (let i = 0; i < n; i++) { set(6, i, i % 2 === 0); set(i, 6, i % 2 === 0); }
|
|
100
|
+
const busca = (cx, cy) => { for (let dy = -4; dy <= 4; dy++) for (let dx = -4; dx <= 4; dx++) { const d = Math.max(Math.abs(dx), Math.abs(dy)); set(cx + dx, cy + dy, d !== 2 && d !== 4); } };
|
|
101
|
+
busca(3, 3); busca(n - 4, 3); busca(3, n - 4);
|
|
102
|
+
const al = posAlinhamento(v);
|
|
103
|
+
for (let i = 0; i < al.length; i++) for (let j = 0; j < al.length; j++) {
|
|
104
|
+
if ((i === 0 && j === 0) || (i === 0 && j === al.length - 1) || (i === al.length - 1 && j === 0)) continue;
|
|
105
|
+
for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) set(al[i] + dx, al[j] + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
|
|
106
|
+
}
|
|
107
|
+
desenharFormato(Q, ecc, 0);
|
|
108
|
+
if (v >= 7) {
|
|
109
|
+
let r = v;
|
|
110
|
+
for (let i = 0; i < 12; i++) r = (r << 1) ^ ((r >>> 11) * 0x1F25);
|
|
111
|
+
const bits = (v << 12) | r;
|
|
112
|
+
for (let i = 0; i < 18; i++) {
|
|
113
|
+
const cor = ((bits >>> i) & 1) !== 0;
|
|
114
|
+
const a = n - 11 + (i % 3);
|
|
115
|
+
const b = Math.floor(i / 3);
|
|
116
|
+
set(a, b, cor); set(b, a, cor);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function desenharFormato(Q, ecc, mascara) {
|
|
121
|
+
const { n, m, f } = Q;
|
|
122
|
+
const dados = (FORMATO_ECC[ecc] << 3) | mascara;
|
|
123
|
+
let r = dados;
|
|
124
|
+
for (let i = 0; i < 10; i++) r = (r << 1) ^ ((r >>> 9) * 0x537);
|
|
125
|
+
const bits = ((dados << 10) | r) ^ 0x5412;
|
|
126
|
+
const bit = (i) => ((bits >>> i) & 1) !== 0;
|
|
127
|
+
const set = (x, y, cor) => { m[y][x] = cor; f[y][x] = true; };
|
|
128
|
+
for (let i = 0; i <= 5; i++) set(8, i, bit(i));
|
|
129
|
+
set(8, 7, bit(6)); set(8, 8, bit(7)); set(7, 8, bit(8));
|
|
130
|
+
for (let i = 9; i < 15; i++) set(14 - i, 8, bit(i));
|
|
131
|
+
for (let i = 0; i < 8; i++) set(n - 1 - i, 8, bit(i));
|
|
132
|
+
for (let i = 8; i < 15; i++) set(8, n - 15 + i, bit(i));
|
|
133
|
+
set(8, n - 8, true);
|
|
134
|
+
}
|
|
135
|
+
function colocarDados(Q, cw) {
|
|
136
|
+
const { n, m, f } = Q;
|
|
137
|
+
let i = 0;
|
|
138
|
+
for (let direita = n - 1; direita >= 1; direita -= 2) {
|
|
139
|
+
if (direita === 6) direita = 5;
|
|
140
|
+
for (let vert = 0; vert < n; vert++) for (let j = 0; j < 2; j++) {
|
|
141
|
+
const x = direita - j;
|
|
142
|
+
const y = ((direita + 1) & 2) === 0 ? n - 1 - vert : vert;
|
|
143
|
+
if (!f[y][x] && i < cw.length * 8) { m[y][x] = ((cw[i >>> 3] >>> (7 - (i & 7))) & 1) !== 0; i++; }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const MASCARAS = [
|
|
148
|
+
(x, y) => (x + y) % 2 === 0, (x, y) => y % 2 === 0, (x) => x % 3 === 0, (x, y) => (x + y) % 3 === 0,
|
|
149
|
+
(x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0, (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
|
|
150
|
+
(x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0, (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
|
|
151
|
+
];
|
|
152
|
+
function aplicarMascara(Q, k) {
|
|
153
|
+
for (let y = 0; y < Q.n; y++) for (let x = 0; x < Q.n; x++) if (!Q.f[y][x] && MASCARAS[k](x, y)) Q.m[y][x] = !Q.m[y][x];
|
|
154
|
+
}
|
|
155
|
+
function penalidade(Q) {
|
|
156
|
+
const { n, m } = Q;
|
|
157
|
+
let p = 0;
|
|
158
|
+
const corrida = (linha) => {
|
|
159
|
+
let s = 0; let run = 1;
|
|
160
|
+
for (let i = 1; i <= linha.length; i++) {
|
|
161
|
+
if (i < linha.length && linha[i] === linha[i - 1]) run++;
|
|
162
|
+
else { if (run >= 5) s += run - 2; run = 1; }
|
|
163
|
+
}
|
|
164
|
+
const str = linha.map((b) => (b ? 1 : 0)).join('');
|
|
165
|
+
for (const pad of ['10111010000', '00001011101']) { let at = str.indexOf(pad); while (at >= 0) { s += 40; at = str.indexOf(pad, at + 1); } }
|
|
166
|
+
return s;
|
|
167
|
+
};
|
|
168
|
+
for (let y = 0; y < n; y++) p += corrida(m[y]);
|
|
169
|
+
for (let x = 0; x < n; x++) p += corrida(m.map((l) => l[x]));
|
|
170
|
+
for (let y = 0; y < n - 1; y++) for (let x = 0; x < n - 1; x++) if (m[y][x] === m[y][x + 1] && m[y][x] === m[y + 1][x] && m[y][x] === m[y + 1][x + 1]) p += 3;
|
|
171
|
+
const escuros = m.reduce((a, l) => a + l.filter(Boolean).length, 0);
|
|
172
|
+
p += (Math.ceil(Math.abs(escuros * 20 - n * n * 10) / (n * n)) - 1) * 10;
|
|
173
|
+
return p;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Devolve { tamanho, modulos: boolean[][], versao }. Lança se o texto não cabe. */
|
|
177
|
+
function gerarQR(texto, { ecc = 'M' } = {}) {
|
|
178
|
+
if (!ECC_POR_BLOCO[ecc]) throw new Error('ecc deve ser L ou M');
|
|
179
|
+
const bytes = Buffer.from(String(texto), 'utf8');
|
|
180
|
+
let v = 1;
|
|
181
|
+
for (; v <= VERSAO_MAX; v++) {
|
|
182
|
+
const bitsLen = 4 + (v <= 9 ? 8 : 16) + bytes.length * 8;
|
|
183
|
+
if (bitsLen <= dadosCodewords(v, ecc) * 8) break;
|
|
184
|
+
}
|
|
185
|
+
if (v > VERSAO_MAX) throw new Error(`texto grande demais para o QR (${bytes.length} bytes; máximo ${dadosCodewords(VERSAO_MAX, ecc) - 3})`);
|
|
186
|
+
const cw = intercalar(bitsDoTexto(bytes, v, ecc), v, ecc);
|
|
187
|
+
let melhor = null;
|
|
188
|
+
for (let k = 0; k < 8; k++) {
|
|
189
|
+
const Q = criarMatriz(v);
|
|
190
|
+
desenharFuncoes(Q, v, ecc);
|
|
191
|
+
colocarDados(Q, cw);
|
|
192
|
+
aplicarMascara(Q, k);
|
|
193
|
+
desenharFormato(Q, ecc, k);
|
|
194
|
+
const pen = penalidade(Q);
|
|
195
|
+
if (!melhor || pen < melhor.pen) melhor = { pen, Q };
|
|
196
|
+
}
|
|
197
|
+
return { tamanho: melhor.Q.n, modulos: melhor.Q.m, versao: v };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* SVG pronto (módulos escuros em `cor`, fundo em `fundo`, com a zona de silêncio de 4 módulos). */
|
|
201
|
+
function qrSvg(texto, { ecc = 'M', cor = '#000', fundo = '#fff' } = {}) {
|
|
202
|
+
const { tamanho, modulos } = gerarQR(texto, { ecc });
|
|
203
|
+
const t = tamanho + 8;
|
|
204
|
+
let d = '';
|
|
205
|
+
for (let y = 0; y < tamanho; y++) for (let x = 0; x < tamanho; x++) if (modulos[y][x]) d += `M${x + 4} ${y + 4}h1v1h-1z`;
|
|
206
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${t} ${t}" shape-rendering="crispEdges" role="img" aria-label="QR code de pareamento"><rect width="${t}" height="${t}" fill="${fundo}"/><path d="${d}" fill="${cor}"/></svg>`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { gerarQR, qrSvg };
|