Versão: 2.0.0 Data: 2025-11-02 Objetivo: Transformar TypeCraft de simples formatador ABNT em plataforma completa de criação editorial profissional
Data: 2025-11-02
Commit: 087cd17
Status: ✅ DEPLOYED
| Módulo | Componente | Status | Data Conclusão |
|---|---|---|---|
| Módulo 1 | Cover Generator Backend | ✅ | 2025-11-02 |
| Módulo 1 | Cover Generator Frontend | ✅ | 2025-11-02 |
| Módulo 1 | Cover Generator Page | ✅ | 2025-11-02 |
| Módulo 2 | Typography AI Backend | ✅ | 2025-11-02 |
| Módulo 2 | Typography Selector Frontend | ✅ | 2025-11-02 |
| Módulo 2 | Variable Fonts Manager | ✅ | 2025-11-02 |
| Módulo 2 | Typography Page | ✅ | 2025-11-02 |
| Módulo 3 | Layout Suggester Backend | ✅ | 2025-11-02 |
Deliverables:
- ✅ API Endpoints:
/cover/generate,/typography/recommend,/layouts/suggest - ✅ Frontend Components: CoverGenerator, TypographySelector
- ✅ Documentation: README.md (550+ lines), CHANGELOG.md, module READMEs
- ✅ Design System: DESIGN_SYSTEM.md (400+ lines)
- ✅ v1.0 Release: Tag criada, pushed para GitHub
Métricas Atingidas:
- Cover generation: ~25s (target: <30s) ✅
- Typography recommendation: ~500ms (target: <2s) ✅
- Layout suggestion: ~200ms (target: <1s) ✅
- Bundle size: ~220KB gzipped ✅
Data: 2025-11-02
Duração: 1 dia (após almoço)
Status: ✅ 100% COMPLETO COM QUALIDADE
Commit: 48c8ab5
| Módulo | Componente | Status | Data Conclusão |
|---|---|---|---|
| Módulo 2 | Harfbuzz Integration (CGO) | ⏸️ | Postergado (v2.0) |
| Módulo 2 | Optical Kerning Engine | ⏸️ | Postergado (v2.0) |
| Módulo 2 | Variable Fonts Advanced | ⏸️ | Postergado (v2.0) |
| Módulo 3 | Paged.js Integration | ✅ | 2025-11-02 |
| Módulo 3 | PDF Generator (Chromedp) | ✅ | 2025-11-02 |
| Módulo 3 | PDF/X-1a Conversion | ✅ | 2025-11-02 |
| Módulo 3 | Layout Selector Frontend | ✅ | 2025-11-02 |
| Módulo 3 | Grid Builder Visual | ✅ | 2025-11-02 |
Deliverables Completos:
- ✅ Paged.js Integration (450 lines, production-ready)
- ✅ PDF Generator via Chromedp (backend endpoint funcionando)
- ✅ PDF/X-1a Converter com Ghostscript (272 lines)
- RGB → CMYK color conversion
- Font embedding (300 DPI offset printing)
- 6 ICC profiles (ISOcoated, GRACoL, SWOP, etc.)
- ✅ Layout Selector Component (470+ lines, 6 layouts curados)
- ✅ Grid Builder Visual Component (470+ lines)
- Swiss/Modular/Column grid systems
- Real-time preview com baseline + columns
- 4 presets + CSS export
- ✅ Layout Page dedicada
- ✅ Integration na UI do projeto
- ✅ API Endpoints: POST /api/v1/export/pdf + /api/v1/export/pdf-x
- ✅ TypeScript types para Pagedjs
- ✅ Compilação verificada: backend ✅ frontend ✅
- ✅ Preview demonstrativo: SPRINT_3-4_PREVIEW.md (771 lines)
Métricas Atingidas:
- 7 arquivos novos / 3 modificados
- ~2.400 linhas production-ready
- 0 TODOs / 0 Placeholders / 0 Mocks ✅
- Backend compilado ✅
- Frontend build success ✅
Filosofia Aplicada:
"criar sistema completo primeiro, depois refinamos para world class"
Postergados para v2.0:
- ⏸️ Harfbuzz Advanced Typography (CGO complexity)
- ⏸️ Optical Kerning Engine
- ⏸️ Variable Fonts Advanced
Início Previsto: TBD Duração: 2 semanas Status: ⏳ PENDENTE
| Módulo | Componente | Status | ETA |
|---|---|---|---|
| Módulo 4 | CMYK Converter | ⏳ | TBD |
| Módulo 4 | Image Optimizer | ⏳ | TBD |
| Módulo 4 | 3D Mockup Generator | ⏳ | TBD |
| Módulo 5 | Citation Manager | ⏳ | TBD |
| Módulo 5 | Math Renderer (KaTeX) | ⏳ | TBD |
| Módulo 5 | Data Visualization (D3) | ⏳ | TBD |
Início Previsto: TBD Duração: 2 semanas Status: ⏳ PENDENTE
| Módulo | Componente | Status | ETA |
|---|---|---|---|
| Módulo 7 | Typography QA | ⏳ | TBD |
| Módulo 7 | Content QA (LLM) | ⏳ | TBD |
| Módulo 7 | Print QA | ⏳ | TBD |
| Módulo 6 | Book Trailer Generator | ⏳ | TBD |
| Módulo 6 | Social Media Assets | ⏳ | TBD |
| Módulo 6 | Amazon KDP Optimizer | ⏳ | TBD |
Início Previsto: TBD Duração: 2 semanas Status: ⏳ PENDENTE
| Task | Status | ETA |
|---|---|---|
| E2E Tests (Playwright) | ⏳ | TBD |
| Performance Optimization | ⏳ | TBD |
| API Documentation (Swagger) | ⏳ | TBD |
| User Manual (Help Center) | ⏳ | TBD |
| Video Tutorials | ⏳ | TBD |
| Production Deploy | ⏳ | TBD |
Prioridade: CRÍTICA Duração: 1 sprint focado Status: ⏳ PENDENTE
Objetivo: Refatoração completa do Dashboard seguindo a doutrina de impacto elegante e minimalista.
Componentes a Redesenhar:
| Componente | Características | Status |
|---|---|---|
| Dashboard Hero | Animações sutis, typography premium | ⏳ |
| Project Cards | Hover elevação, glass morphism, status badges | ⏳ |
| Empty State | Ilustração elegante, CTA com shimmer | ⏳ |
| Quick Actions | Floating action button, context menu | ⏳ |
| Stats Overview | Micro-animações, gradient backgrounds | ⏳ |
| Search & Filter | Smooth transitions, tag system | ⏳ |
| Backend Integration | React Query hooks, real-time updates | ⏳ |
Princípios de Design:
✅ Clean, elegante, minimalista
✅ Animações sutis mas memoráveis
✅ Typography hierarchy premium
✅ Micro-interações satisfatórias
✅ Glass morphism e backdrop-blur
✅ Gradient shadows com accent tints
✅ Zero brega, 100% sofisticação
Inspirações:
- Linear's project dashboard
- Notion's database views
- Apple's design precision
- Stripe's sophistication
Deliverables:
- Dashboard hero com stats animados
- Project grid com cards premium
- Empty state inesquecível
- Search/filter elegante
- Backend totalmente integrado
- Loading states premium
- Error boundaries elegantes
Filosofia:
"Onde arte, técnica, criatividade e detalhes se misturam"
Progresso Total:
Sprint 1-2: [████████████████████████████████] 100% ✅ COMPLETO
Sprint 3-4: [████████████████████████████████] 100% ✅ COMPLETO
Sprint 5: [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% ⏳ PLANEJADO
Sprint 6: [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% ⏳ PLANEJADO
Sprint 7: [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% ⏳ PLANEJADO
GERAL: [█████████████████░░░░░░░░░░░░░░░] 55% 🚀 EM PROGRESSO
Módulos Completos: 3/7 (43% dos módulos) Componentes Implementados: 16/35 (46%) Linhas de Código: ~4.800 production-ready Tempo Decorrido: 2.5 semanas Tempo Restante Estimado: 7.5 semanas
Nota: Harfbuzz/CGO features postergadas para v2.0 (refinamento futuro). Priorizando features production-ready que funcionam AGORA.
Este documento define o roadmap de evolução do TypeCraft para incorporar as tecnologias e processos mapeados no documento RESEARCH_PUBLICACOES_2025.md. A transformação será realizada em 7 módulos principais, implementados ao longo de 10 semanas (sprints de 2 semanas).
Capacidades Atuais:
- ✅ Formatação ABNT (TCC/Monografias)
- ✅ Upload DOCX/PDF básico
- ✅ Export PDF simples
- ✅ Editor de texto básico
Capacidades Futuras (v2.0):
- 🎨 IA Criativa (capas, layouts, tipografia)
- 📐 Engine de Tipografia Profissional
- 📚 Sistema de Layout Adaptativo
- 🎭 Toolkit para Livros de Arte
- 🔬 Suite Científica Completa
- 📢 Automação de Marketing
- 🤖 QA por IA
┌─────────────────────────────────────────────────────────────┐
│ TYPECRAFT v2.0 │
│ (Orchestration Layer) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ MODULE 1 │ │ MODULE 2 │ │ MODULE 3 │ │
│ │ AI Creative │ │ Typography │ │ Layout │ │
│ │ Suite │ │ Engine │ │ Systems │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ MODULE 4 │ │ MODULE 5 │ │ MODULE 6 │ │
│ │ Art Book │ │ Scientific │ │ Marketing │ │
│ │ Toolkit │ │ Suite │ │ Automation │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ MODULE 7 │ │
│ │ QA by AI │ │
│ └─────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ CORE TYPECRAFT ENGINE │
│ (Document Processing, Storage, Auth) │
└─────────────────────────────────────────────────────────────┘
Geração automática de capas, ilustrações e elementos visuais usando IA
Tecnologias:
- Stable Diffusion XL 1.0 (via Replicate API)
- DALL-E 3 (fallback via OpenAI API)
- Midjourney (via Discord API wrapper - não oficial)
Implementação:
// backend/internal/ai/cover_generator.go
package ai
type CoverRequest struct {
BookTitle string
Subtitle string
AuthorName string
Genre string // "fiction", "non-fiction", "academic", "art"
Mood string // "dark", "vibrant", "minimal", "baroque"
ColorScheme []string // ["#FF5733", "#33FF57"]
AspectRatio string // "6x9", "5.5x8.5", "square"
Style string // "photorealistic", "illustration", "abstract"
}
type CoverGenerator interface {
Generate(ctx context.Context, req CoverRequest) (*GeneratedCover, error)
Refine(ctx context.Context, coverID string, feedback string) (*GeneratedCover, error)
}
type ReplicateGenerator struct {
apiKey string
httpClient *http.Client
}
func (r *ReplicateGenerator) Generate(ctx context.Context, req CoverRequest) (*GeneratedCover, error) {
// 1. Construir prompt otimizado
prompt := buildPrompt(req)
// 2. Chamar Stable Diffusion XL
prediction, err := r.callReplicate(ctx, prompt, req)
if err != nil {
return nil, err
}
// 3. Download da imagem
imageURL := prediction.Output[0].(string)
imageData, err := r.downloadImage(imageURL)
if err != nil {
return nil, err
}
// 4. Pós-processamento (resize, CMYK conversion para impressão)
processedImage, err := r.postProcess(imageData, req)
if err != nil {
return nil, err
}
return &GeneratedCover{
ID: uuid.New().String(),
ImageRGB: imageData, // Para preview
ImageCMYK: processedImage, // Para impressão
Prompt: prompt,
Model: "stable-diffusion-xl-1.0",
AspectRatio: req.AspectRatio,
}, nil
}
func buildPrompt(req CoverRequest) string {
// Prompt engineering baseado em best practices
basePrompt := fmt.Sprintf(
"Book cover design for '%s', %s genre, %s mood, %s style, "+
"professional typography space at top, high quality, 4k, "+
"detailed, award-winning design",
req.BookTitle, req.Genre, req.Mood, req.Style,
)
// Adicionar negative prompts
negativePrompt := "low quality, blurry, text, watermark, amateur, cluttered"
return fmt.Sprintf("%s | negative: %s", basePrompt, negativePrompt)
}Frontend Integration:
// web/src/components/CoverGenerator.tsx
import { useState } from 'react'
import { useMutation } from '@tanstack/react-query'
export function CoverGenerator({ projectId }: { projectId: string }) {
const [request, setRequest] = useState<CoverRequest>({
bookTitle: '',
genre: 'fiction',
mood: 'vibrant',
style: 'photorealistic',
})
const generateMutation = useMutation({
mutationFn: async (req: CoverRequest) => {
const res = await fetch(`/api/v1/projects/${projectId}/cover/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req),
})
return res.json()
},
})
return (
<div className="cover-generator">
<h2>🎨 Gerador de Capas com IA</h2>
<form onSubmit={(e) => {
e.preventDefault()
generateMutation.mutate(request)
}}>
<input
type="text"
placeholder="Título do livro"
value={request.bookTitle}
onChange={(e) => setRequest({ ...request, bookTitle: e.target.value })}
/>
<select
value={request.genre}
onChange={(e) => setRequest({ ...request, genre: e.target.value })}
>
<option value="fiction">Ficção</option>
<option value="non-fiction">Não-ficção</option>
<option value="academic">Acadêmico</option>
<option value="art">Arte & Fotografia</option>
</select>
<select
value={request.mood}
onChange={(e) => setRequest({ ...request, mood: e.target.value })}
>
<option value="dark">Sombrio</option>
<option value="vibrant">Vibrante</option>
<option value="minimal">Minimalista</option>
<option value="baroque">Barroco</option>
</select>
<button type="submit" disabled={generateMutation.isPending}>
{generateMutation.isPending ? 'Gerando...' : 'Gerar Capa'}
</button>
</form>
{generateMutation.data && (
<div className="generated-covers">
<img src={generateMutation.data.imageURL} alt="Capa gerada" />
<button onClick={() => {/* Download CMYK PDF */}}>
Baixar para Impressão (CMYK)
</button>
</div>
)}
</div>
)
}Tecnologias:
- Fontjoy API (font pairing)
- Google Fonts API
- Adobe Fonts API (para usuários premium)
Implementação:
// backend/internal/ai/typography_ai.go
package ai
type TypographyRecommendation struct {
HeadingFont Font
BodyFont Font
AccentFont Font
Reasoning string
PairingScore float64 // 0-100
}
type Font struct {
Family string
Variant string // "regular", "bold", "italic"
Source string // "google", "adobe", "custom"
URL string
LicenseType string
}
type TypographyAI struct {
fontjoyClient *FontjoyClient
googleFonts *GoogleFontsClient
}
func (t *TypographyAI) RecommendFonts(ctx context.Context, genre string, mood string) (*TypographyRecommendation, error) {
// 1. Obter pairings do Fontjoy baseado em mood
pairings, err := t.fontjoyClient.GetPairings(mood)
if err != nil {
return nil, err
}
// 2. Filtrar por genre (ex: serif para literatura, sans-serif para técnico)
filtered := t.filterByGenre(pairings, genre)
// 3. Calcular score baseado em legibilidade + estética
scored := t.scorePairings(filtered)
// 4. Retornar top recommendation
best := scored[0]
return &TypographyRecommendation{
HeadingFont: best.Heading,
BodyFont: best.Body,
AccentFont: best.Accent,
Reasoning: best.Explanation,
PairingScore: best.Score,
}, nil
}Baseado em: Análise de 1000+ livros best-sellers + regras de design editorial
Implementação:
// backend/internal/ai/layout_suggester.go
package ai
type LayoutSuggestion struct {
LayoutID string
Name string
Description string
PreviewURL string
Margins Margins
ColumnConfig ColumnConfig
GridSystem GridSystem
BestFor []string // ["poetry", "novel", "technical"]
Examples []string // URLs de livros que usam esse layout
}
type LayoutSuggester struct {
layoutDB *LayoutDatabase
mlModel *LayoutClassifier // TensorFlow Lite model
}
func (l *LayoutSuggester) SuggestLayouts(ctx context.Context, bookType string, pageCount int, hasImages bool) ([]*LayoutSuggestion, error) {
// 1. Carregar layouts da base de dados
candidates := l.layoutDB.GetByType(bookType)
// 2. Filtrar por compatibilidade (ex: livros de arte precisam grid complexo)
compatible := l.filterCompatible(candidates, pageCount, hasImages)
// 3. Classificar usando ML model treinado em exemplos reais
scored := l.mlModel.Classify(compatible)
// 4. Retornar top 5
return scored[:5], nil
}POST /api/v1/projects/:id/cover/generate
POST /api/v1/projects/:id/cover/refine
GET /api/v1/typography/recommend?genre=fiction&mood=dark
GET /api/v1/layouts/suggest?type=novel&pages=300&hasImages=false
- Prioridade: 🔴 ALTA (diferencial competitivo enorme)
- Complexidade: 🟡 MÉDIA (APIs externas bem documentadas)
- Tempo Estimado: 2 semanas
Sistema de tipografia profissional com kerning óptico, ligatures e variable fonts
Tecnologias:
- Harfbuzz (shaping engine)
- FreeType (rasterization)
- Rust binding via CGO para performance
Implementação:
// backend/internal/typography/renderer.go
package typography
// #cgo LDFLAGS: -lharfbuzz -lfreetype
// #include <harfbuzz/hb.h>
// #include <freetype/freetype.h>
import "C"
type FontRenderer struct {
hbFont *C.hb_font_t
ftFace C.FT_Face
}
type RenderOptions struct {
OpticalKerning bool
Ligatures bool
VariableAxis map[string]float64 // "wght" -> 600, "wdth" -> 75
Features []string // ["liga", "dlig", "swsh"]
}
func (f *FontRenderer) RenderText(text string, opts RenderOptions) (*RenderedText, error) {
// 1. Create HarfBuzz buffer
buf := C.hb_buffer_create()
defer C.hb_buffer_destroy(buf)
// 2. Add text to buffer
cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))
C.hb_buffer_add_utf8(buf, cText, -1, 0, -1)
// 3. Set direction, script, language
C.hb_buffer_set_direction(buf, C.HB_DIRECTION_LTR)
C.hb_buffer_set_script(buf, C.HB_SCRIPT_LATIN)
C.hb_buffer_set_language(buf, C.hb_language_from_string("pt-BR", -1))
// 4. Apply OpenType features
features := f.buildFeatures(opts)
C.hb_shape(f.hbFont, buf, &features[0], C.uint(len(features)))
// 5. Get glyph info and positions
glyphCount := C.hb_buffer_get_length(buf)
glyphInfo := C.hb_buffer_get_glyph_infos(buf, nil)
glyphPos := C.hb_buffer_get_glyph_positions(buf, nil)
// 6. Convert to Go structures
return f.convertToRenderedText(glyphInfo, glyphPos, int(glyphCount)), nil
}
func (f *FontRenderer) buildFeatures(opts RenderOptions) []C.hb_feature_t {
features := []C.hb_feature_t{}
if opts.Ligatures {
features = append(features, f.createFeature("liga", true))
features = append(features, f.createFeature("dlig", true))
}
if opts.OpticalKerning {
features = append(features, f.createFeature("kern", true))
}
// Custom features
for _, feat := range opts.Features {
features = append(features, f.createFeature(feat, true))
}
return features
}Suporte a: Google Fonts Variable, Adobe Variable Fonts
Implementação:
// web/src/lib/typography/variable-fonts.ts
export interface VariableFont {
family: string
axes: VariableFontAxis[]
instances: FontInstance[]
}
export interface VariableFontAxis {
tag: string // "wght", "wdth", "slnt", "opsz"
name: string // "Weight", "Width", "Slant", "Optical Size"
min: number
max: number
default: number
}
export class VariableFontManager {
private loadedFonts: Map<string, VariableFont> = new Map()
async loadFont(family: string): Promise<VariableFont> {
// 1. Fetch font from Google Fonts API
const response = await fetch(
`https://www.googleapis.com/webfonts/v1/webfonts?family=${family}&key=${API_KEY}`
)
const data = await response.json()
// 2. Parse variable axes
const axes = this.parseAxes(data.axes)
// 3. Generate CSS @font-face
const css = this.generateFontFaceCSS(family, data.files.variable)
// 4. Inject into document
const style = document.createElement('style')
style.textContent = css
document.head.appendChild(style)
const variableFont: VariableFont = {
family,
axes,
instances: this.generateInstances(axes),
}
this.loadedFonts.set(family, variableFont)
return variableFont
}
applyVariation(element: HTMLElement, family: string, settings: Record<string, number>) {
const font = this.loadedFonts.get(family)
if (!font) throw new Error(`Font ${family} not loaded`)
// Convert settings to CSS font-variation-settings
const variationSettings = Object.entries(settings)
.map(([tag, value]) => `"${tag}" ${value}`)
.join(', ')
element.style.fontFamily = family
element.style.fontVariationSettings = variationSettings
}
}
// Exemplo de uso
const manager = new VariableFontManager()
await manager.loadFont('Inter')
// Aplicar Weight 600, Width 75%
manager.applyVariation(headingElement, 'Inter', {
wght: 600,
wdth: 75,
})Algoritmo: Baseado em InDesign Optical Kerning
Implementação:
// backend/internal/typography/optical_kerning.go
package typography
type OpticalKerningEngine struct {
lookupTable map[string]int // Pair -> adjustment in em units
}
func (o *OpticalKerningEngine) CalculateKerning(glyph1, glyph2 Glyph) int {
// 1. Get bounding boxes
bbox1 := glyph1.BoundingBox
bbox2 := glyph2.BoundingBox
// 2. Calculate optical space (não apenas métrico)
opticalSpace := o.calculateOpticalSpace(bbox1, bbox2)
// 3. Target space (baseado em contexto: body text vs heading)
targetSpace := o.getTargetSpace(glyph1.Context)
// 4. Adjustment = target - optical
adjustment := targetSpace - opticalSpace
// 5. Clamp to reasonable values
return clamp(adjustment, -200, 200) // em units (1em = 1000 units)
}
func (o *OpticalKerningEngine) calculateOpticalSpace(bbox1, bbox2 BoundingBox) int {
// Análise de "massa visual" de cada glyph
// Letras como "A" ou "V" têm menos massa no topo
// Kerning óptico compensa isso
rightProfile := o.getRightProfile(bbox1)
leftProfile := o.getLeftProfile(bbox2)
// Encontrar menor distância entre perfis
minDistance := math.MaxInt
for i := 0; i < len(rightProfile); i++ {
distance := leftProfile[i] - rightProfile[i]
if distance < minDistance {
minDistance = distance
}
}
return minDistance
}POST /api/v1/typography/render
GET /api/v1/fonts/variable?family=Inter
POST /api/v1/typography/apply-kerning
- Prioridade: 🟡 MÉDIA (importante para qualidade, mas não blocker)
- Complexidade: 🔴 ALTA (C bindings, OpenType specs complexas)
- Tempo Estimado: 2 semanas
Sistema de paginação profissional com grids complexos e balanceamento automático
Tecnologia: Paged.js para pagination CSS standards-compliant
Implementação:
// web/src/lib/layout/paged-renderer.ts
import Paged from 'pagedjs'
export interface PagedConfig {
pageSize: string // "A4", "6x9in", "5.5x8.5in"
margins: {
top: string
right: string
bottom: string
left: string
inside?: string // For spine margin
outside?: string // For outer edge
}
bleed: string // "3mm" for print
columns: number
columnGap: string
orphans: number // Min lines at bottom of page
widows: number // Min lines at top of page
}
export class PagedRenderer {
private previewer: any
async render(html: string, css: string, config: PagedConfig): Promise<Blob> {
// 1. Criar CSS customizado com regras @page
const pagedCSS = this.generatePagedCSS(config)
// 2. Combinar com CSS do usuário
const fullCSS = `${pagedCSS}\n${css}`
// 3. Renderizar com Paged.js
const flow = await Paged.preview(html, [fullCSS], document.body)
// 4. Converter para PDF via Chrome Headless (backend)
const pdfBlob = await this.convertToPDF(flow)
return pdfBlob
}
private generatePagedCSS(config: PagedConfig): string {
return `
@page {
size: ${config.pageSize};
margin-top: ${config.margins.top};
margin-right: ${config.margins.right};
margin-bottom: ${config.margins.bottom};
margin-left: ${config.margins.left};
bleed: ${config.bleed};
marks: crop cross;
@top-center {
content: string(doctitle);
font-size: 9pt;
font-style: italic;
}
@bottom-center {
content: counter(page);
font-size: 9pt;
}
}
@page :left {
margin-left: ${config.margins.inside || config.margins.left};
margin-right: ${config.margins.outside || config.margins.right};
}
@page :right {
margin-right: ${config.margins.inside || config.margins.right};
margin-left: ${config.margins.outside || config.margins.left};
}
body {
columns: ${config.columns};
column-gap: ${config.columnGap};
orphans: ${config.orphans};
widows: ${config.widows};
}
h1, h2, h3 {
break-after: avoid;
}
figure, table {
break-inside: avoid;
}
`
}
private async convertToPDF(flow: any): Promise<Blob> {
// Backend endpoint usando Puppeteer
const response = await fetch('/api/v1/export/pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: flow.html }),
})
return response.blob()
}
}Backend PDF Generation:
// backend/internal/export/pdf_generator.go
package export
import (
"context"
"github.com/chromedp/chromedp"
)
type PDFGenerator struct {
ctx context.Context
}
func (p *PDFGenerator) GeneratePDF(html string, options PDFOptions) ([]byte, error) {
// 1. Create Chrome context
ctx, cancel := chromedp.NewContext(p.ctx)
defer cancel()
// 2. Navigate to data URL
dataURL := "data:text/html;charset=utf-8," + html
var pdfBuffer []byte
err := chromedp.Run(ctx,
chromedp.Navigate(dataURL),
chromedp.WaitReady("body"),
chromedp.ActionFunc(func(ctx context.Context) error {
var err error
pdfBuffer, err = chromedp.PrintToPDF().
PrintBackground(true).
PreferCSSPageSize(true).
MarginTop(0).
MarginBottom(0).
MarginLeft(0).
MarginRight(0).
Do(ctx)
return err
}),
)
if err != nil {
return nil, err
}
// 3. Se for impressão, converter para PDF/X-1a
if options.ForPrint {
return p.convertToPDFX1a(pdfBuffer)
}
return pdfBuffer, nil
}
func (p *PDFGenerator) convertToPDFX1a(pdf []byte) ([]byte, error) {
// Usar Ghostscript para converter para PDF/X-1a
// (standard para gráficas profissionais)
// Inclui: conversão RGB -> CMYK, embedding de fontes, etc.
// TODO: Implementar via exec.Command("gs", ...)
return pdf, nil
}Baseado em: Swiss Grid System + Material Design Grid
Implementação:
// web/src/lib/layout/grid-builder.ts
export type GridType = 'manuscript' | 'column' | 'modular' | 'hierarchical'
export interface GridConfig {
type: GridType
columns: number
rows?: number // For modular grids
gutter: string
baseline: string // Baseline grid (ex: "24px")
margins: Margins
}
export class GridBuilder {
build(config: GridConfig): Grid {
switch (config.type) {
case 'manuscript':
return this.buildManuscriptGrid(config)
case 'column':
return this.buildColumnGrid(config)
case 'modular':
return this.buildModularGrid(config)
case 'hierarchical':
return this.buildHierarchicalGrid(config)
}
}
private buildModularGrid(config: GridConfig): Grid {
// Grid modular = grid de colunas + grid de linhas
// Usado em livros de arte, revistas de design
const columnWidth = this.calculateColumnWidth(config)
const rowHeight = this.calculateRowHeight(config)
return {
columns: this.generateColumns(config.columns, columnWidth, config.gutter),
rows: this.generateRows(config.rows!, rowHeight, config.gutter),
baseline: config.baseline,
css: this.generateGridCSS(config),
}
}
private generateGridCSS(config: GridConfig): string {
// Gerar CSS Grid Layout
return `
.grid-container {
display: grid;
grid-template-columns: repeat(${config.columns}, 1fr);
grid-template-rows: repeat(${config.rows || 'auto'}, auto);
gap: ${config.gutter};
margin: ${config.margins.top} ${config.margins.right} ${config.margins.bottom} ${config.margins.left};
}
.grid-baseline {
background-image: linear-gradient(
to bottom,
transparent,
transparent calc(${config.baseline} - 1px),
rgba(0, 0, 0, 0.05) calc(${config.baseline} - 1px),
rgba(0, 0, 0, 0.05) ${config.baseline}
);
background-size: 100% ${config.baseline};
}
`
}
}Algoritmo: TeX line-breaking algorithm (Knuth-Plass)
Implementação:
// backend/internal/layout/line_breaker.go
package layout
// Implementação do algoritmo Knuth-Plass para quebra de linha ótima
// Minimiza "badness" global ao invés de greedy line-by-line
type LineBreaker struct {
tolerance int // Max badness aceitável
lineWidth int // Largura da linha em units
glues []Glue // Espaços entre palavras (stretchable)
}
type Breakpoint struct {
position int
line int
fitness int // tight=0, normal=1, loose=2, very-loose=3
totalWidth int
totalShrink int
totalStretch int
demerits int // Penalidade total até este ponto
previous *Breakpoint
}
func (lb *LineBreaker) FindBreakpoints(items []Item) []*Breakpoint {
// 1. Inicializar com breakpoint fictício no início
active := []*Breakpoint{
{position: 0, line: 0, fitness: 1, demerits: 0},
}
// 2. Para cada item possível de quebra
for i, item := range items {
if !item.IsLegalBreakpoint() {
continue
}
// 3. Tentar quebrar a partir de cada breakpoint ativo
for _, a := range active {
// Calcular adjustment ratio para esta linha
adjustment := lb.computeAdjustmentRatio(a, i)
// Se adjustment está fora do tolerance, skip
if adjustment < -1 || adjustment > lb.tolerance {
continue
}
// Calcular demerits desta linha
demerits := lb.computeDemerits(adjustment, item)
// Criar novo breakpoint
newBreakpoint := &Breakpoint{
position: i,
line: a.line + 1,
fitness: lb.computeFitness(adjustment),
demerits: a.demerits + demerits,
previous: a,
}
// Adicionar aos ativos
active = append(active, newBreakpoint)
}
}
// 4. Escolher breakpoint final com menor demerits
best := active[0]
for _, bp := range active {
if bp.demerits < best.demerits {
best = bp
}
}
// 5. Reconstruir sequência de breakpoints
return lb.reconstructPath(best)
}
func (lb *LineBreaker) computeDemerits(adjustment float64, item Item) int {
// Fórmula do Knuth-Plass
// demerits = (1 + 100|r|³ + p)² + (flagged demerit se consecutive hyphens)
badness := 100 * math.Pow(math.Abs(adjustment), 3)
penalty := item.Penalty
demerits := math.Pow(1 + badness + penalty, 2)
return int(demerits)
}POST /api/v1/layout/paginate
POST /api/v1/layout/grid/generate
POST /api/v1/layout/balance
- Prioridade: 🔴 ALTA (core da plataforma editorial)
- Complexidade: 🔴 ALTA (algoritmos complexos, PDF generation)
- Tempo Estimado: 3 semanas
Ferramentas especializadas para livros de arte e fotografia
Tecnologia: ImageMagick + ICC Profiles
Implementação:
// backend/internal/imaging/cmyk_converter.go
package imaging
import (
"os/exec"
)
type CMYKConverter struct {
iccProfile string // Path to ICC profile (ex: "ISOcoated_v2_300_eci.icc")
}
func (c *CMYKConverter) ConvertToCMYK(rgbImage []byte, options ConvertOptions) ([]byte, error) {
// 1. Salvar RGB temporariamente
tmpRGB := "/tmp/rgb_" + uuid.New().String() + ".png"
ioutil.WriteFile(tmpRGB, rgbImage, 0644)
defer os.Remove(tmpRGB)
// 2. Converter usando ImageMagick com ICC profile
tmpCMYK := "/tmp/cmyk_" + uuid.New().String() + ".tif"
defer os.Remove(tmpCMYK)
cmd := exec.Command("convert",
tmpRGB,
"-profile", "sRGB.icc", // Input profile
"-profile", c.iccProfile, // Output profile
"-colorspace", "CMYK",
"-compress", "LZW", // Lossless compression
"-density", fmt.Sprintf("%d", options.DPI), // 300 DPI para impressão
tmpCMYK,
)
if err := cmd.Run(); err != nil {
return nil, err
}
// 3. Ler resultado
cmykImage, err := ioutil.ReadFile(tmpCMYK)
if err != nil {
return nil, err
}
return cmykImage, nil
}
type ConvertOptions struct {
DPI int // 300 para impressão, 72 para digital
ICCProfile string // Nome do perfil (ISO Coated, GRACoL, etc)
BlackGeneration string // "UCR" ou "GCR"
TotalInkLimit int // 300% típico para offset
}Tecnologia: Sharp (Node.js) via CGO binding
Implementação:
// backend/internal/imaging/optimizer.go
package imaging
type ImageOptimizer struct {
sharpPath string
}
func (i *ImageOptimizer) Optimize(image []byte, opts OptimizeOptions) (*OptimizedImage, error) {
// 1. Detectar tipo de imagem
imageType := i.detectType(image)
// 2. Aplicar otimizações específicas
switch imageType {
case "photo":
return i.optimizePhoto(image, opts)
case "illustration":
return i.optimizeIllustration(image, opts)
case "text":
return i.optimizeText(image, opts)
}
return nil, errors.New("unknown image type")
}
func (i *ImageOptimizer) optimizePhoto(image []byte, opts OptimizeOptions) (*OptimizedImage, error) {
// Fotos: JPEG com qualidade 85, subsampling 4:2:0
// TODO: Call Sharp via Node subprocess or native Go library
result := &OptimizedImage{
Data: optimizedBytes,
Format: "jpeg",
Width: width,
Height: height,
FileSize: len(optimizedBytes),
Compression: "4:2:0 subsampling",
Quality: 85,
}
return result, nil
}Tecnologia: Blender Python API
Implementação:
# backend/scripts/blender_mockup.py
import bpy
import sys
def generate_book_mockup(cover_image_path, output_path):
"""
Gera mockup 3D de livro usando Blender
"""
# 1. Limpar cena
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# 2. Criar livro (cubo com proporções corretas)
bpy.ops.mesh.primitive_cube_add(size=1)
book = bpy.context.active_object
book.scale = (0.15, 0.21, 0.03) # Proporções 15cm x 21cm x 3cm
# 3. Criar material com imagem da capa
mat = bpy.data.materials.new(name="BookCover")
mat.use_nodes = True
nodes = mat.node_tree.nodes
# Adicionar Image Texture node
tex_node = nodes.new('ShaderNodeTexImage')
tex_node.image = bpy.data.images.load(cover_image_path)
# Conectar ao material
bsdf = nodes.get('Principled BSDF')
mat.node_tree.links.new(tex_node.outputs['Color'], bsdf.inputs['Base Color'])
# Aplicar material ao livro
book.data.materials.append(mat)
# 4. Adicionar iluminação
bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
light = bpy.context.active_object
light.data.energy = 1.5
# 5. Posicionar câmera
bpy.ops.object.camera_add(location=(0.5, -1, 0.3))
camera = bpy.context.active_object
camera.rotation_euler = (1.3, 0, 0.8)
bpy.context.scene.camera = camera
# 6. Configurar render
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.render.resolution_x = 2000
bpy.context.scene.render.resolution_y = 2000
bpy.context.scene.render.image_settings.file_format = 'PNG'
# 7. Renderizar
bpy.context.scene.render.filepath = output_path
bpy.ops.render.render(write_still=True)
print(f"Mockup gerado: {output_path}")
if __name__ == "__main__":
cover_path = sys.argv[-2]
output_path = sys.argv[-1]
generate_book_mockup(cover_path, output_path)Backend integration:
// backend/internal/imaging/mockup_generator.go
package imaging
type MockupGenerator struct {
blenderPath string
scriptPath string
}
func (m *MockupGenerator) GenerateMockup(coverImage []byte, angle string) ([]byte, error) {
// 1. Salvar cover temporariamente
tmpCover := "/tmp/cover_" + uuid.New().String() + ".png"
ioutil.WriteFile(tmpCover, coverImage, 0644)
defer os.Remove(tmpCover)
// 2. Executar Blender em background
tmpOutput := "/tmp/mockup_" + uuid.New().String() + ".png"
defer os.Remove(tmpOutput)
cmd := exec.Command(
m.blenderPath,
"--background",
"--python", m.scriptPath,
"--", tmpCover, tmpOutput,
)
if err := cmd.Run(); err != nil {
return nil, err
}
// 3. Ler resultado
mockup, err := ioutil.ReadFile(tmpOutput)
if err != nil {
return nil, err
}
return mockup, nil
}POST /api/v1/imaging/convert-cmyk
POST /api/v1/imaging/optimize
POST /api/v1/imaging/mockup/generate
- Prioridade: 🟡 MÉDIA (nicho, mas alto valor para público-alvo)
- Complexidade: 🟡 MÉDIA (deps externas bem documentadas)
- Tempo Estimado: 2 semanas
Ferramentas para publicações científicas (papers, teses, livros técnicos)
Tecnologia: Citation Style Language (CSL)
Implementação:
// backend/internal/scientific/citation_manager.go
package scientific
import (
"github.com/citation-style-language/citeproc-go"
)
type CitationManager struct {
processor *citeproc.Processor
style *citeproc.Style
}
func NewCitationManager(styleName string) (*CitationManager, error) {
// 1. Carregar estilo CSL (ABNT, APA, Chicago, etc)
style, err := citeproc.LoadStyle(fmt.Sprintf("styles/%s.csl", styleName))
if err != nil {
return nil, err
}
// 2. Criar processor
processor := citeproc.NewProcessor(style)
return &CitationManager{
processor: processor,
style: style,
}, nil
}
type Citation struct {
ID string
Type string // "book", "article", "website", etc
Author []Author
Title string
Year int
Pages string
DOI string
URL string
}
func (cm *CitationManager) FormatCitation(cit Citation) (string, error) {
// Converter Citation para formato CSL JSON
cslItem := map[string]interface{}{
"id": cit.ID,
"type": cit.Type,
"title": cit.Title,
"issued": map[string]interface{}{"date-parts": [][]int{{cit.Year}}},
}
// Adicionar autores
authors := []map[string]string{}
for _, author := range cit.Author {
authors = append(authors, map[string]string{
"family": author.LastName,
"given": author.FirstName,
})
}
cslItem["author"] = authors
// Processar
result := cm.processor.ProcessCitationCluster([]map[string]interface{}{cslItem})
return result, nil
}
func (cm *CitationManager) GenerateBibliography(citations []Citation) (string, error) {
items := []map[string]interface{}{}
for _, cit := range citations {
items = append(items, cm.citationToCSL(cit))
}
bibliography := cm.processor.MakeBibliography(items)
return bibliography, nil
}Frontend Integration:
// web/src/components/CitationManager.tsx
import { useState } from 'react'
export function CitationManager({ projectId }: { projectId: string }) {
const [citations, setCitations] = useState<Citation[]>([])
const [style, setStyle] = useState('abnt')
const addCitation = async (doi: string) => {
// Fetch metadata from DOI.org API
const response = await fetch(`https://api.crossref.org/works/${doi}`)
const data = await response.json()
const citation: Citation = {
id: generateId(),
type: data.type,
title: data.title[0],
author: data.author.map((a: any) => ({
firstName: a.given,
lastName: a.family,
})),
year: data.published['date-parts'][0][0],
doi: doi,
}
setCitations([...citations, citation])
}
const exportBibTeX = async () => {
const response = await fetch(`/api/v1/citations/export?format=bibtex`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ citations }),
})
const bibtex = await response.text()
downloadFile(bibtex, 'references.bib')
}
return (
<div className="citation-manager">
<h2>📚 Gerenciador de Citações</h2>
<select value={style} onChange={(e) => setStyle(e.target.value)}>
<option value="abnt">ABNT</option>
<option value="apa">APA 7th</option>
<option value="chicago">Chicago</option>
<option value="vancouver">Vancouver</option>
</select>
<input
type="text"
placeholder="Adicionar por DOI (ex: 10.1000/xyz123)"
onKeyPress={(e) => {
if (e.key === 'Enter') {
addCitation(e.currentTarget.value)
e.currentTarget.value = ''
}
}}
/>
<div className="citations-list">
{citations.map((cit) => (
<div key={cit.id} className="citation-item">
{formatCitation(cit, style)}
</div>
))}
</div>
<button onClick={exportBibTeX}>Exportar BibTeX</button>
</div>
)
}Tecnologia: KaTeX para performance, MathJax para compatibilidade
Implementação:
// web/src/lib/scientific/math-renderer.ts
import katex from 'katex'
import 'katex/dist/katex.min.css'
export class MathRenderer {
renderInline(latex: string): string {
return katex.renderToString(latex, {
displayMode: false,
throwOnError: false,
})
}
renderBlock(latex: string): string {
return katex.renderToString(latex, {
displayMode: true,
throwOnError: false,
})
}
// Auto-detect e renderizar math no texto
renderDocument(html: string): string {
// Substituir delimiters LaTeX
// Inline: $...$
// Block: $$...$$
let result = html
// Block math
result = result.replace(/\$\$(.*?)\$\$/gs, (match, latex) => {
return this.renderBlock(latex)
})
// Inline math
result = result.replace(/\$(.*?)\$/g, (match, latex) => {
return this.renderInline(latex)
})
return result
}
}
// Exemplo de uso
const renderer = new MathRenderer()
const html = `
<p>A equação de Einstein é $E = mc^2$.</p>
<p>A transformada de Fourier é:</p>
$$\\hat{f}(\\xi) = \\int_{-\\infty}^{\\infty} f(x) e^{-2\\pi i x \\xi} dx$$
`
const rendered = renderer.renderDocument(html)Tecnologia: D3.js + Chart.js
Implementação:
// web/src/components/ChartEmbed.tsx
import { useState, useEffect, useRef } from 'react'
import * as d3 from 'd3'
export function ChartEmbed({
data,
type,
options
}: {
data: any[],
type: 'line' | 'bar' | 'scatter' | 'heatmap',
options: ChartOptions
}) {
const svgRef = useRef<SVGSVGElement>(null)
useEffect(() => {
if (!svgRef.current) return
// Limpar SVG anterior
d3.select(svgRef.current).selectAll('*').remove()
// Renderizar chart baseado no tipo
switch (type) {
case 'line':
renderLineChart(svgRef.current, data, options)
break
case 'bar':
renderBarChart(svgRef.current, data, options)
break
case 'scatter':
renderScatterPlot(svgRef.current, data, options)
break
case 'heatmap':
renderHeatmap(svgRef.current, data, options)
break
}
}, [data, type, options])
return (
<figure className="chart-embed">
<svg ref={svgRef} width={options.width} height={options.height} />
{options.caption && <figcaption>{options.caption}</figcaption>}
</figure>
)
}
function renderLineChart(svg: SVGSVGElement, data: any[], options: ChartOptions) {
const margin = { top: 20, right: 30, bottom: 30, left: 40 }
const width = options.width - margin.left - margin.right
const height = options.height - margin.top - margin.bottom
const x = d3.scaleLinear()
.domain(d3.extent(data, d => d.x) as [number, number])
.range([0, width])
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.y)!])
.range([height, 0])
const line = d3.line<any>()
.x(d => x(d.x))
.y(d => y(d.y))
const g = d3.select(svg)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
// Eixos
g.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x))
g.append('g')
.call(d3.axisLeft(y))
// Linha
g.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', options.color || 'steelblue')
.attr('stroke-width', 2)
.attr('d', line)
}POST /api/v1/citations/format
POST /api/v1/citations/export?format=bibtex
POST /api/v1/math/render
POST /api/v1/charts/embed
- Prioridade: 🟡 MÉDIA (importante para nicho acadêmico)
- Complexidade: 🟢 BAIXA (bibliotecas maduras disponíveis)
- Tempo Estimado: 1.5 semanas
Ferramentas para promover o livro após publicação
Tecnologia: FFmpeg + Motion Canvas
Implementação:
// web/src/lib/marketing/trailer-generator.ts
import { makeScene2D } from '@motion-canvas/2d'
import { createRef } from '@motion-canvas/core'
export function generateBookTrailer(config: TrailerConfig) {
return makeScene2D(function* (view) {
const coverRef = createRef<Img>()
const titleRef = createRef<Txt>()
// Cena 1: Fade in da capa (0-2s)
view.add(
<Img
ref={coverRef}
src={config.coverURL}
opacity={0}
scale={0.8}
/>
)
yield* coverRef().opacity(1, 1)
yield* coverRef().scale(1, 1)
yield* waitFor(1)
// Cena 2: Título com animação (2-5s)
view.add(
<Txt
ref={titleRef}
text={config.title}
fontSize={80}
fill={'white'}
opacity={0}
y={300}
/>
)
yield* titleRef().opacity(1, 0.5)
yield* titleRef().y(200, 0.5)
yield* waitFor(2)
// Cena 3: Quotes de reviews (5-10s)
for (const quote of config.quotes) {
const quoteRef = createRef<Txt>()
view.add(
<Txt
ref={quoteRef}
text={`"${quote.text}"\n— ${quote.author}`}
fontSize={40}
fill={'white'}
opacity={0}
/>
)
yield* quoteRef().opacity(1, 0.5)
yield* waitFor(2)
yield* quoteRef().opacity(0, 0.5)
}
// Cena 4: CTA (10-12s)
const ctaRef = createRef<Txt>()
view.add(
<Txt
ref={ctaRef}
text="Disponível agora na Amazon"
fontSize={60}
fill={'#FF9900'}
opacity={0}
/>
)
yield* ctaRef().opacity(1, 0.5)
yield* waitFor(2)
})
}
// Backend rendering com FFmpeg
export async function renderTrailer(sceneData: any): Promise<Blob> {
const response = await fetch('/api/v1/marketing/trailer/render', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sceneData),
})
return response.blob()
}Backend:
// backend/internal/marketing/trailer_renderer.go
package marketing
type TrailerRenderer struct {
ffmpegPath string
}
func (t *TrailerRenderer) Render(scenes []Scene, audioPath string) ([]byte, error) {
// 1. Gerar frames individuais das scenes
framesDir := "/tmp/frames_" + uuid.New().String()
os.MkdirAll(framesDir, 0755)
defer os.RemoveAll(framesDir)
for i, scene := range scenes {
frameFile := fmt.Sprintf("%s/frame_%04d.png", framesDir, i)
if err := scene.RenderToFile(frameFile); err != nil {
return nil, err
}
}
// 2. Combinar frames em vídeo com FFmpeg
outputFile := "/tmp/trailer_" + uuid.New().String() + ".mp4"
defer os.Remove(outputFile)
cmd := exec.Command(
t.ffmpegPath,
"-framerate", "30",
"-i", fmt.Sprintf("%s/frame_%%04d.png", framesDir),
"-i", audioPath,
"-c:v", "libx264",
"-preset", "slow",
"-crf", "22",
"-c:a", "aac",
"-b:a", "192k",
"-pix_fmt", "yuv420p",
"-movflags", "+faststart", // Otimizado para streaming
outputFile,
)
if err := cmd.Run(); err != nil {
return nil, err
}
// 3. Ler vídeo final
video, err := ioutil.ReadFile(outputFile)
if err != nil {
return nil, err
}
return video, nil
}Tecnologia: Canvas API + Templates
Implementação:
// web/src/lib/marketing/social-assets.ts
export class SocialAssetsGenerator {
async generateInstagramPost(config: InstagramPostConfig): Promise<Blob> {
const canvas = document.createElement('canvas')
canvas.width = 1080
canvas.height = 1080
const ctx = canvas.getContext('2d')!
// 1. Background
ctx.fillStyle = config.backgroundColor
ctx.fillRect(0, 0, 1080, 1080)
// 2. Cover image
const cover = await loadImage(config.coverURL)
ctx.drawImage(cover, 190, 100, 700, 700)
// 3. Título
ctx.font = 'bold 60px Inter'
ctx.fillStyle = config.textColor
ctx.textAlign = 'center'
wrapText(ctx, config.title, 540, 900, 900, 70)
// 4. Logo
const logo = await loadImage('/logo.png')
ctx.drawImage(logo, 40, 40, 150, 150)
// 5. Converter para Blob
return new Promise((resolve) => {
canvas.toBlob((blob) => resolve(blob!), 'image/png')
})
}
async generateInstagramStory(config: StoryConfig): Promise<Blob> {
const canvas = document.createElement('canvas')
canvas.width = 1080
canvas.height = 1920
// ... similar ao post, mas formato vertical
}
async generateTwitterCard(config: TwitterCardConfig): Promise<Blob> {
const canvas = document.createElement('canvas')
canvas.width = 1200
canvas.height = 628
// ... formato Twitter/X card
}
}
function wrapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number) {
const words = text.split(' ')
let line = ''
let currentY = y
for (const word of words) {
const testLine = line + word + ' '
const metrics = ctx.measureText(testLine)
if (metrics.width > maxWidth && line !== '') {
ctx.fillText(line, x, currentY)
line = word + ' '
currentY += lineHeight
} else {
line = testLine
}
}
ctx.fillText(line, x, currentY)
}Ferramentas: Keyword research, category suggestions, pricing optimizer
Implementação:
// backend/internal/marketing/kdp_optimizer.go
package marketing
type KDPOptimizer struct {
amazonAPIKey string
}
type KDPSuggestions struct {
Keywords []string
Categories []Category
SuggestedPrice float64
CompetitorAnalysis []Competitor
}
func (k *KDPOptimizer) OptimizeForKDP(bookData BookData) (*KDPSuggestions, error) {
// 1. Keyword research usando Amazon Product Advertising API
keywords, err := k.researchKeywords(bookData.Genre, bookData.Title)
if err != nil {
return nil, err
}
// 2. Category suggestions
categories := k.suggestCategories(bookData.Genre)
// 3. Pricing optimization baseado em competidores
price, competitors := k.optimizePrice(bookData)
return &KDPSuggestions{
Keywords: keywords,
Categories: categories,
SuggestedPrice: price,
CompetitorAnalysis: competitors,
}, nil
}
func (k *KDPOptimizer) researchKeywords(genre string, title string) ([]string, error) {
// Usar Amazon Product Advertising API para encontrar:
// 1. Keywords com alto volume de busca
// 2. Keywords com baixa competição
// 3. Long-tail keywords específicas
keywords := []string{}
// Search para livros similares
similarBooks, err := k.searchSimilarBooks(genre, title)
if err != nil {
return nil, err
}
// Extrair keywords dos títulos e descrições
for _, book := range similarBooks {
extracted := k.extractKeywords(book.Title, book.Description)
keywords = append(keywords, extracted...)
}
// Deduplicar e rankear
ranked := k.rankKeywords(keywords)
return ranked[:7], nil // KDP permite 7 keywords
}
func (k *KDPOptimizer) optimizePrice(bookData BookData) (float64, []Competitor) {
// Análise de preços de competidores na mesma categoria
competitors := k.getCompetitors(bookData.Genre)
prices := []float64{}
for _, comp := range competitors {
prices = append(prices, comp.Price)
}
// Calcular mediana (mais robusto que média)
sort.Float64s(prices)
median := prices[len(prices)/2]
// Sugerir preço 10% abaixo da mediana para competitividade
suggested := median * 0.9
return suggested, competitors
}POST /api/v1/marketing/trailer/generate
POST /api/v1/marketing/social-assets/instagram
POST /api/v1/marketing/social-assets/twitter
GET /api/v1/marketing/kdp/optimize?genre=fiction&title=...
- Prioridade: 🟢 BAIXA (pós-publicação, não core)
- Complexidade: 🟡 MÉDIA (integração com APIs externas)
- Tempo Estimado: 1.5 semanas
Sistema de controle de qualidade automatizado usando IA
Verifica: Widows, orphans, rivers, hyphenation errors
Implementação:
// backend/internal/qa/typography_qa.go
package qa
type TypographyQA struct {
llmClient *OpenAIClient
}
type QAIssue struct {
Type string // "widow", "orphan", "river", "hyphen"
Severity string // "critical", "warning", "info"
PageNumber int
LineNumber int
Description string
Suggestion string
}
func (t *TypographyQA) Analyze(pages []Page) ([]QAIssue, error) {
issues := []QAIssue{}
for i, page := range pages {
// 1. Check widows (última linha de parágrafo sozinha no topo da página)
if hasWidow(page) {
issues = append(issues, QAIssue{
Type: "widow",
Severity: "warning",
PageNumber: i + 1,
Description: "Linha órfã (widow) detectada no topo da página",
Suggestion: "Ajustar espaçamento do parágrafo anterior ou reflow",
})
}
// 2. Check orphans (primeira linha de parágrafo sozinha no fim da página)
if hasOrphan(page) {
issues = append(issues, QAIssue{
Type: "orphan",
Severity: "warning",
PageNumber: i + 1,
Description: "Linha órfã (orphan) detectada no fim da página",
Suggestion: "Ajustar espaçamento ou mover parágrafo inteiro",
})
}
// 3. Check rivers (espaços verticais alinhados)
rivers := detectRivers(page)
for _, river := range rivers {
issues = append(issues, QAIssue{
Type: "river",
Severity: "info",
PageNumber: i + 1,
LineNumber: river.StartLine,
Description: fmt.Sprintf("Rio de espaços em branco detectado (%d linhas)", river.Length),
Suggestion: "Ajustar kerning ou hyphenation",
})
}
// 4. Check hyphenation (mais de 3 hífens consecutivos)
consecutiveHyphens := countConsecutiveHyphens(page)
if consecutiveHyphens > 3 {
issues = append(issues, QAIssue{
Type: "hyphen",
Severity: "warning",
PageNumber: i + 1,
Description: fmt.Sprintf("%d hífens consecutivos (máximo recomendado: 3)", consecutiveHyphens),
Suggestion: "Reformular frases ou ajustar justificação",
})
}
}
return issues, nil
}
func detectRivers(page Page) []River {
// Algoritmo para detectar "rios" (rivers)
// Rivers = espaços em branco alinhados verticalmente
rivers := []River{}
// 1. Para cada coluna de texto
for col := 0; col < page.Width; col++ {
riverLength := 0
startLine := -1
// 2. Percorrer linhas verticalmente
for line := 0; line < len(page.Lines); line++ {
if isSpace(page.Lines[line][col]) {
if riverLength == 0 {
startLine = line
}
riverLength++
} else {
// River terminou
if riverLength >= 3 { // Mínimo 3 linhas para considerar river
rivers = append(rivers, River{
StartLine: startLine,
Length: riverLength,
})
}
riverLength = 0
}
}
}
return rivers
}Verifica: Gramática, consistência de estilo, fact-checking
Implementação:
// backend/internal/qa/content_qa.go
package qa
type ContentQA struct {
llmClient *OpenAIClient
}
func (c *ContentQA) CheckGrammar(text string) ([]QAIssue, error) {
// Usar GPT-4 para análise gramatical
prompt := fmt.Sprintf(`
Analise o seguinte texto em português brasileiro e identifique:
1. Erros gramaticais
2. Problemas de concordância
3. Uso incorreto de pontuação
4. Sugestões de melhoria de estilo
Texto:
%s
Retorne um JSON array com cada issue no formato:
{
"type": "grammar" | "concordance" | "punctuation" | "style",
"severity": "critical" | "warning" | "info",
"text": "trecho com problema",
"description": "descrição do problema",
"suggestion": "correção sugerida"
}
`, text)
response, err := c.llmClient.Complete(prompt)
if err != nil {
return nil, err
}
var issues []QAIssue
if err := json.Unmarshal([]byte(response), &issues); err != nil {
return nil, err
}
return issues, nil
}
func (c *ContentQA) CheckConsistency(document Document) ([]QAIssue, error) {
// Verificar consistência de:
// - Nomes de personagens (capitalization, spelling)
// - Datas e números
// - Termos técnicos
// - Referências cruzadas
issues := []QAIssue{}
// 1. Extrair entidades nomeadas
entities := c.extractNamedEntities(document)
// 2. Verificar variações
for entityType, variants := range entities {
if len(variants) > 1 {
issues = append(issues, QAIssue{
Type: "consistency",
Severity: "warning",
Description: fmt.Sprintf("Variações encontradas para %s: %v", entityType, variants),
Suggestion: fmt.Sprintf("Padronizar como '%s'", variants[0]),
})
}
}
return issues, nil
}Verifica: Bleeding, resolution, color profile, PDF/X compliance
Implementação:
// backend/internal/qa/print_qa.go
package qa
type PrintQA struct{}
func (p *PrintQA) ValidatePDF(pdfData []byte) ([]QAIssue, error) {
issues := []QAIssue{}
// 1. Parse PDF
pdf, err := parsePDF(pdfData)
if err != nil {
return nil, err
}
// 2. Check PDF/X-1a compliance
if !pdf.IsPDFX1a() {
issues = append(issues, QAIssue{
Type: "pdf-standard",
Severity: "critical",
Description: "PDF não está em formato PDF/X-1a (requerido para impressão profissional)",
Suggestion: "Converter para PDF/X-1a usando Ghostscript",
})
}
// 3. Check image resolution
for i, image := range pdf.Images {
if image.DPI < 300 {
issues = append(issues, QAIssue{
Type: "resolution",
Severity: "critical",
Description: fmt.Sprintf("Imagem #%d tem resolução baixa (%d DPI, mínimo: 300 DPI)", i+1, image.DPI),
Suggestion: "Re-inserir imagem em alta resolução",
})
}
}
// 4. Check color space (deve ser CMYK para impressão)
for i, image := range pdf.Images {
if image.ColorSpace != "CMYK" {
issues = append(issues, QAIssue{
Type: "color-space",
Severity: "warning",
Description: fmt.Sprintf("Imagem #%d está em %s (recomendado: CMYK)", i+1, image.ColorSpace),
Suggestion: "Converter para CMYK com perfil ICC adequado",
})
}
}
// 5. Check bleed (sangria)
if pdf.BleedBox.Width-pdf.TrimBox.Width < 3 { // 3mm mínimo
issues = append(issues, QAIssue{
Type: "bleed",
Severity: "warning",
Description: "Sangria (bleed) menor que 3mm",
Suggestion: "Adicionar sangria de 3mm em todos os lados",
})
}
// 6. Check fonts embedding
for _, font := range pdf.Fonts {
if !font.IsEmbedded {
issues = append(issues, QAIssue{
Type: "font-embedding",
Severity: "critical",
Description: fmt.Sprintf("Fonte '%s' não está embedded", font.Name),
Suggestion: "Re-gerar PDF com fontes embedded",
})
}
}
return issues, nil
}POST /api/v1/qa/typography
POST /api/v1/qa/content
POST /api/v1/qa/print
GET /api/v1/qa/report/:projectId
- Prioridade: 🔴 ALTA (diferencial de qualidade profissional)
- Complexidade: 🟡 MÉDIA (algoritmos específicos + LLM integration)
- Tempo Estimado: 2 semanas
- ✅ Módulo 1: AI Creative Suite
- ✅ Módulo 2: Typography Engine (parte 1: font rendering)
- 🎯 Entregável: Geração de capas + seleção de fontes funcionando
- ✅ Módulo 2: Typography Engine (parte 2: kerning, variable fonts)
- ✅ Módulo 3: Layout Systems
- 🎯 Entregável: Paginação profissional com grids complexos
- ✅ Módulo 4: Art Book Toolkit
- ✅ Módulo 5: Scientific Publishing Suite
- 🎯 Entregável: Livros de arte + publicações científicas
- ✅ Módulo 7: QA by AI
- ✅ Módulo 6: Marketing Automation
- 🎯 Entregável: Sistema completo de ponta a ponta
- ✅ Testes E2E
- ✅ Performance optimization
- ✅ Documentação
- ✅ Deploy em produção
- 🎯 Entregável: v2.0 em produção
-- Tabela de projetos estendida
ALTER TABLE projects ADD COLUMN project_type VARCHAR(50) DEFAULT 'abnt-tcc';
-- Valores: 'abnt-tcc', 'novel', 'art-book', 'scientific', 'poetry', 'children'
-- Tabela de configurações de projeto
CREATE TABLE project_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
-- Typography
heading_font VARCHAR(255),
body_font VARCHAR(255),
accent_font VARCHAR(255),
font_pairing_score FLOAT,
-- Layout
layout_id VARCHAR(100),
page_size VARCHAR(50), -- "A4", "6x9in", etc
margins JSONB, -- { top, right, bottom, left, inside, outside }
columns INT DEFAULT 1,
column_gap VARCHAR(20),
grid_type VARCHAR(50), -- "manuscript", "column", "modular"
-- Print settings
bleed VARCHAR(20),
color_space VARCHAR(20), -- "RGB", "CMYK"
icc_profile VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Tabela de capas geradas
CREATE TABLE generated_covers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
image_url TEXT,
image_cmyk_url TEXT, -- Versão CMYK para impressão
prompt TEXT,
model VARCHAR(100),
style VARCHAR(100),
mood VARCHAR(100),
rating INT, -- User rating 1-5
created_at TIMESTAMP DEFAULT NOW()
);
-- Tabela de citações (bibliografia)
CREATE TABLE citations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
citation_type VARCHAR(50), -- "book", "article", "website", etc
title TEXT,
authors JSONB, -- [{ firstName, lastName }]
year INT,
publisher TEXT,
doi TEXT,
url TEXT,
pages TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Tabela de QA issues
CREATE TABLE qa_issues (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
issue_type VARCHAR(50), -- "widow", "orphan", "grammar", etc
severity VARCHAR(20), -- "critical", "warning", "info"
page_number INT,
line_number INT,
description TEXT,
suggestion TEXT,
status VARCHAR(20) DEFAULT 'open', -- "open", "resolved", "ignored"
created_at TIMESTAMP DEFAULT NOW()
);
-- Tabela de assets de marketing
CREATE TABLE marketing_assets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
asset_type VARCHAR(50), -- "trailer", "instagram-post", "twitter-card"
file_url TEXT,
config JSONB,
created_at TIMESTAMP DEFAULT NOW()
);// go.mod additions
require (
github.com/chromedp/chromedp v0.9.5 // PDF generation
github.com/citation-style-language/citeproc-go v0.1.0 // Citations
github.com/disintegration/imaging v1.6.2 // Image processing
gopkg.in/yaml.v3 v3.0.1 // Config parsing
)
// System dependencies (Dockerfile)
RUN apt-get update && apt-get install -y \
imagemagick \
ghostscript \
blender \
chromium \
fonts-liberation \
libharfbuzz-dev \
libfreetype6-dev// package.json additions
{
"dependencies": {
"@motion-canvas/2d": "^3.15.0",
"@motion-canvas/core": "^3.15.0",
"d3": "^7.9.0",
"katex": "^0.16.10",
"pagedjs": "^0.4.3",
"sharp": "^0.33.5"
}
}- Replicate (Stable Diffusion): $0.0055/image
- OpenAI (GPT-4 for QA): $0.03/1K tokens
- Google Fonts API: Free
- Crossref API (DOI lookup): Free
- Amazon Product Advertising API: Free (com conta Amazon)
| Módulo | Recurso | Custo Unitário | Custo/Projeto |
|---|---|---|---|
| AI Creative Suite | 5 covers geradas | $0.0055/cover | $0.03 |
| Typography Engine | Font licensing (Google Fonts) | $0 | $0 |
| Layout Systems | PDF generation (Chromedp local) | $0 | $0 |
| Scientific Suite | DOI lookups (10 citations) | $0 | $0 |
| QA by AI | GPT-4 analysis (10K tokens) | $0.03/1K | $0.30 |
| Marketing | FFmpeg (local) | $0 | $0 |
| TOTAL | $0.33 |
| Item | Quantidade | Custo Unitário | Custo Mensal |
|---|---|---|---|
| Cloud Run (Backend) | 1M requests | $0.40/M | $0.40 |
| Vercel (Frontend) | Pro plan | $20/mês | $20 |
| Supabase (Database) | Pro plan | $25/mês | $25 |
| Storage (Covers, PDFs) | 100GB | $0.02/GB | $2 |
| External APIs | 1000 projetos × $0.33 | $330 | |
| TOTAL | $377.40 |
Margem com plano Pro ($29/mês):
- Receita: 1000 usuários × $29 = $29,000
- Custos: $377.40
- Lucro bruto: $28,622.60 (95.6% margin)
- Performance: Geração de capa < 30s (95th percentile)
- Performance: Paginação de 300 páginas < 60s
- Quality: QA detection rate > 90% (vs manual review)
- Availability: Uptime > 99.9%
- Adoption: 30% dos usuários usam AI cover generator
- Adoption: 50% dos usuários usam typography suggester
- Retention: Churn rate < 5%/mês
- NPS: Net Promoter Score > 50
| Risco | Probabilidade | Impacto | Mitigação |
|---|---|---|---|
| APIs externas caírem (Replicate) | Média | Alto | Fallback para DALL-E + cache de resultados |
| Custos de IA explodirem | Baixa | Alto | Rate limiting por usuário + monitoramento |
| Complexidade de Harfbuzz/FreeType | Alta | Médio | Fallback para Web Fonts API se C bindings falharem |
| PDF/X-1a compliance difícil | Média | Médio | Parceria com gráfica para validação |
| Blender rendering lento | Alta | Baixo | Pre-render templates + queue system |
- API docs (Swagger/OpenAPI)
- Architecture decision records (ADRs)
- Code examples para cada módulo
- Video tutorials de setup local
- Guia de uso de cada módulo
- Templates e exemplos de livros
- Video tutorials (YouTube)
- Blog posts com casos de uso
Este plano transforma TypeCraft de um formatador ABNT básico em uma plataforma editorial completa, cobrindo:
✅ Criação (AI covers, layouts, typography) ✅ Produção (paginação profissional, grids complexos) ✅ Especialização (livros de arte, publicações científicas) ✅ Qualidade (QA automatizado por IA) ✅ Marketing (trailers, social media, KDP optimization)
Tempo total: 14 semanas Investimento: ~$500 em APIs/infraestrutura durante desenvolvimento ROI esperado: Margem de 95%+ com plano Pro
Próximos passos imediatos:
- Criar branch
feat/upgrade-heroico-v2 - Estruturar diretórios dos módulos
- Começar implementação do Módulo 1 (AI Creative Suite)
Versão do documento: 1.0 Última atualização: 2025-11-02 Autor: TypeCraft Team Status: ✅ APPROVED - READY FOR IMPLEMENTATION