npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

prova-pdf

v0.1.3

Published

Purpose-built exam PDF generator compiled to WebAssembly

Readme

prova-pdf

Gerador de PDF de provas acadêmicas compilado para WebAssembly. Recebe um JSON estruturado (ExamSpec), retorna bytes de PDF. Zero dependência de DOM, browser ou Chromium.

Suporte nativo a: 6 tipos de questão (objetiva, dissertativa, somatório, cloze, redação, arquivo), textos-base em 7 posições, cabeçalho institucional, layout bicolunado, fórmulas LaTeX e fontes TTF/OTF.

Instalação

# Node.js / Browser
npm install prova-pdf

# Python
pip install prova-pdf

# Go
go get github.com/Dickson-Pinheiro/prova-pdf/packages/go/provapdf

Uso — Browser

<script type="module">
  import init, { add_font, generate_pdf } from "https://cdn.jsdelivr.net/npm/[email protected]/prova_pdf.js";

  await init();

  // Registrar fonte (obrigatório: pelo menos "body" regular)
  const fontRes = await fetch("/fonts/DejaVuSans.ttf");
  add_font("body", 0, new Uint8Array(await fontRes.arrayBuffer()));

  // Gerar PDF
  const pdf = generate_pdf({
    sections: [{
      title: "Matemática",
      questions: [{
        kind: "choice",
        stem: [{ type: "text", value: "Quanto é 2 + 2?" }],
        answer: {
          type: "choice",
          alternatives: [
            { label: "A", content: [{ type: "text", value: "3" }] },
            { label: "B", content: [{ type: "text", value: "4" }] },
            { label: "C", content: [{ type: "text", value: "5" }] },
          ],
        },
      }],
    }],
  });

  // Download
  const blob = new Blob([pdf], { type: "application/pdf" });
  const a = document.createElement("a");
  a.href = URL.createObjectURL(blob);
  a.download = "prova.pdf";
  a.click();
</script>

Uso — Node.js (TypeScript)

import { readFileSync, writeFileSync } from "fs";
import { createRequire } from "module";
import { initSync, add_font, generate_pdf, clear_all } from "prova-pdf";
import type { ExamSpec, ChoiceAnswer } from "prova-pdf/types";

const require = createRequire(import.meta.url);
const wasmBytes = readFileSync(require.resolve("prova-pdf/prova_pdf_bg.wasm"));
initSync({ module: wasmBytes });

clear_all();
add_font("body", 0, new Uint8Array(readFileSync("DejaVuSans.ttf")));

const spec: ExamSpec = {
  config: { columns: 2, fontSize: 11 },
  header: {
    institution: "Escola Municipal",
    title: "Prova de Ciências",
    studentFields: [{ label: "Nome" }, { label: "Turma", widthCm: 5 }],
  },
  sections: [{
    title: "Questões Objetivas",
    questions: [{
      kind: "choice",
      stem: [{ type: "text", value: "Qual é o maior planeta do sistema solar?" }],
      answer: {
        type: "choice",
        alternatives: [
          { label: "A", content: [{ type: "text", value: "Terra" }] },
          { label: "B", content: [{ type: "text", value: "Júpiter" }] },
          { label: "C", content: [{ type: "text", value: "Saturno" }] },
        ],
      } satisfies ChoiceAnswer,
      points: 1.0,
    }],
  }],
};

writeFileSync("prova.pdf", generate_pdf(spec));

Uso — Python

from pathlib import Path
from prova_pdf import generate_pdf

font = Path("DejaVuSans.ttf").read_bytes()

spec = {
    "config": {"columns": 2, "fontSize": 11},
    "header": {
        "institution": "Escola Municipal",
        "title": "Prova de Ciências",
        "studentFields": [{"label": "Nome"}, {"label": "Turma", "widthCm": 5}],
    },
    "sections": [{
        "title": "Questões Objetivas",
        "questions": [{
            "kind": "choice",
            "stem": [{"type": "text", "value": "Qual é o maior planeta do sistema solar?"}],
            "answer": {
                "type": "choice",
                "alternatives": [
                    {"label": "A", "content": [{"type": "text", "value": "Terra"}]},
                    {"label": "B", "content": [{"type": "text", "value": "Júpiter"}]},
                    {"label": "C", "content": [{"type": "text", "value": "Saturno"}]},
                ],
            },
            "points": 1.0,
        }],
    }],
}

pdf = generate_pdf(spec, fonts=[{"family": "body", "variant": 0, "data": font}])
Path("prova.pdf").write_bytes(pdf)

Uso — Go

package main

import (
    "os"
    "github.com/Dickson-Pinheiro/prova-pdf/packages/go/provapdf"
)

func main() {
    fontBytes, _ := os.ReadFile("DejaVuSans.ttf")

    spec := provapdf.ExamSpec{
        Config: provapdf.PrintConfig{
            Columns:  u8Ptr(2),
            FontSize: f64Ptr(11),
        },
        Header: provapdf.InstitutionalHeader{
            Institution: strPtr("Escola Municipal"),
            Title:       strPtr("Prova de Ciências"),
            StudentFields: []provapdf.StudentField{
                {Label: "Nome"},
                {Label: "Turma", WidthCm: f64Ptr(5)},
            },
        },
        Sections: []provapdf.Section{{
            Title: strPtr("Questões Objetivas"),
            Questions: []provapdf.Question{{
                Kind: provapdf.QuestionKindChoice,
                Stem: []provapdf.InlineContent{
                    provapdf.TextContent("Qual é o maior planeta do sistema solar?"),
                },
                Answer: provapdf.AnswerSpace{
                    Type: "choice",
                    Alternatives: []provapdf.Alternative{
                        {Label: "A", Content: []provapdf.InlineContent{provapdf.TextContent("Terra")}},
                        {Label: "B", Content: []provapdf.InlineContent{provapdf.TextContent("Júpiter")}},
                        {Label: "C", Content: []provapdf.InlineContent{provapdf.TextContent("Saturno")}},
                    },
                },
                Points: f64Ptr(1.0),
            }},
        }},
    }

    pdf, _ := provapdf.GeneratePDF(spec, []provapdf.FontInput{
        {Family: "body", Variant: 0, Data: fontBytes},
    })

    os.WriteFile("prova.pdf", pdf, 0644)
}

func strPtr(s string) *string   { return &s }
func f64Ptr(f float64) *float64 { return &f }
func u8Ptr(n uint8) *uint8      { return &n }

Tipos de Questão

| Tipo | kind | Descrição | |------|--------|-----------| | Objetiva | choice | Alternativas A/B/C/D/E com conteúdo inline | | Dissertativa | textual | Linhas ou espaço em branco para resposta | | Somatório | sum | Itens com valores 01/02/04/08/16 e caixa de soma | | Cloze | cloze | Lacunas inline no enunciado + banco de palavras | | Redação | essay | Espaço grande com muitas linhas | | Arquivo | file | Placeholder de upload digital |

Conteúdo Inline

O campo stem e os conteúdos de alternativas usam InlineContent[]:

[
  { "type": "text", "value": "A fórmula é ", "style": { "bold": true } },
  { "type": "math", "latex": "E = mc^2", "display": false },
  { "type": "image", "key": "fig1", "widthCm": 8 },
  { "type": "sub", "content": [{ "type": "text", "value": "2" }] },
  { "type": "sup", "content": [{ "type": "text", "value": "n" }] },
  { "type": "blank", "widthCm": 4.0 }
]

Fontes

Registre pelo menos a família "body" (variante 0 = regular) antes de gerar. Variantes: 0 regular, 1 bold, 2 italic, 3 bold-italic.

add_font("body", 0, regularBytes);
add_font("body", 1, boldBytes);
add_font("heading", 0, headingBytes);
set_font_rules({ body: "body", heading: "heading" });

PrintConfig

| Campo | Tipo | Default | Descrição | |-------|------|---------|-----------| | pageSize | "A4" | "Ata" | {widthMm, heightMm} | "A4" | Tamanho da página | | columns | 1 | 2 | 1 | Número de colunas | | fontSize | number | 12 | Tamanho base em pt | | lineSpacing | "normal" | "oneAndHalf" | "twoAndHalf" | "threeAndHalf" | "normal" | Espaçamento | | margins | {top, bottom, left, right} | 0.6/0.6/1.5/1.5 | Margens em cm | | economyMode | boolean | false | Força 2 colunas, reduz espaços | | allBlack | boolean | false | Força todas as cores para preto | | showScore | boolean | false | Exibe pontuação por questão | | breakAllQuestions | boolean | false | Quebra de página antes de cada questão | | imageGrayscale | boolean | false | Converte imagens para escala de cinza |

Referência completa em PROJECT.md.

Performance

| Cenário | Meta | vs Chromium | |---------|------|-------------| | 50 questões sem math | < 50ms | 150-300x mais rápido | | 50 questões com LaTeX | < 200ms | 40-75x mais rápido | | Prova completa com imagens | < 400ms | 20-40x mais rápido |

WASM gzipped: ~793 KB (browser) / ~770 KB (WASI).

Arquitetura

ExamSpec (JSON) → Validação → Cascata de Estilo → Layout → Emissão PDF → Vec<u8>

4 fases, ~15.500 LOC Rust, 486 testes. Detalhes em ARCHITECTURE.md.

Desenvolvimento

# Testes unitários
cargo test

# Build browser (pkg/)
make build-browser

# Build WASI (wasm/)
make build-wasi

# Teste cross-platform (Python + Node.js + Go)
bash tests/cross-platform/run.sh

# Benchmarks
cargo bench

Licença

MIT