diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f6e020b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,804 @@ +# CLAUDE.md — dreport + +## Proje Özeti + +**dreport**, kullanıcıların fatura, irsaliye, rapor gibi belge şablonlarını görsel bir drag & drop editör ile tasarlayıp, JSON veri ile birleştirerek PDF çıktı almasını sağlayan bir belge tasarım aracıdır. + +Temel fark: Editördeki önizleme doğrudan Typst render çıktısıdır. Canvas üzerinde ayrı bir render engine çalışmaz — kullanıcı her zaman gerçek Typst çıktısını görür. Bu sayede "editörde gördüğüm ile PDF'te aldığım farklı" sorunu ortadan kalkar. + +--- + +## Teknoloji Kararları + +| Katman | Teknoloji | Gerekçe | +| ----------------- | ------------------------------------ | --------------------------------------------------------- | +| Frontend | Vue 3 (Composition API) + TypeScript | Kullanıcı tercihi | +| Editör Render | Typst WASM → SVG | Editör çıktısı = PDF çıktısı tutarlılığı | +| Typst WASM | `@myriaddreamin/typst.ts` | Tarayıcıda Typst derleme; SVG çıktı üretimi | +| Etkileşim Katmanı | SVG overlay (Vue bileşenleri) | Typst SVG üzerine seçim, sürükleme, yeniden boyutlandırma | +| Backend | Rust + Axum | Typst crate'lerini doğrudan kullanabilme; performans | +| PDF Render | `typst` Rust crate (server-side) | Nihai PDF üretimi sunucuda; font tutarlılığı garantisi | +| Veri Formatı | JSON (şablon tanımı + veri) | Evrensel, kolay serialize/deserialize | +| Paket Yönetimi | bun (frontend), cargo (backend) | — | + +--- + +## Mimari Genel Bakış + +``` +┌─────────────────────────────────────────────────────┐ +│ VUE FRONTEND │ +│ │ +│ ┌───────────────┐ ┌──────────────────────────┐ │ +│ │ Sol Panel │ │ Editör Canvas │ │ +│ │ - Bileşenler │ │ │ │ +│ │ - Schema Tree │ │ ┌────────────────────┐ │ │ +│ │ - Özellikler │ │ │ Typst WASM → SVG │ │ │ +│ │ │ │ │ (gerçek render) │ │ │ +│ │ │ │ └────────────────────┘ │ │ +│ │ │ │ ┌────────────────────┐ │ │ +│ │ │ │ │ SVG Overlay (Vue) │ │ │ +│ │ │ │ │ seçim / drag / resize│ │ │ +│ │ │ │ └────────────────────┘ │ │ +│ └───────────────┘ └──────────────────────────┘ │ +│ │ +│ Template JSON ←→ Typst Markup ←→ SVG Render │ +└──────────────────────────┬──────────────────────────┘ + │ POST /api/render + ▼ +┌─────────────────────────────────────────────────────┐ +│ RUST BACKEND (Axum) │ +│ │ +│ Template JSON + Data JSON → Typst Markup → PDF │ +│ (typst crate ile doğrudan derleme) │ +└─────────────────────────────────────────────────────┘ +``` + +--- + +## Editör Render Stratejisi: Typst WASM Full Render + +### Temel Prensip + +Editörde ayrı bir canvas render engine (fabric.js, konva.js vb.) KULLANILMAZ. Bunun yerine: + +1. Kullanıcının tasarımı bir **Template JSON** olarak tutulur. +2. Template JSON'dan **Typst markup** üretilir (frontend'de, saf fonksiyon). +3. Typst markup, **typst.ts WASM** modülü ile tarayıcıda derlenir → **SVG** çıktı üretilir. +4. SVG, editör alanında gösterilir. +5. SVG üzerine **Vue bileşenleriyle bir interaction overlay** yerleştirilir (seçim kutuları, drag handle'lar, resize köşeleri). +6. Kullanıcı bir elemanı sürüklediğinde → Template JSON güncellenir → Typst yeniden derlenir → SVG güncellenir. + +### Performans Stratejisi + +Typst incremental compilation destekler, ancak her fare hareketi için full render döngüsü ağır olabilir. Bu yüzden: + +- **Drag sırasında:** Overlay katmanında sadece CSS transform ile görsel geri bildirim ver (hafif, anlık). Typst derleme YAPMA. +- **Drag bittiğinde (mouseup/pointerup):** Template JSON'ı güncelle, Typst derle, SVG'yi yenile. +- **Debounce:** Özellik panelinden yapılan değişikliklerde (font boyutu, renk vs.) 150-300ms debounce ile derle. +- **Web Worker:** Typst WASM derlemeyi bir Web Worker içinde çalıştır. Ana thread'i ASLA bloklamayacak. + +### typst.ts Entegrasyonu + +```typescript +// Temel kullanım şeması +import { $typst } from "@myriaddreamin/typst.ts/dist/esm/contrib/snippet.mjs"; + +// Worker içinde: +async function compile(typstMarkup: string): Promise { + const svg = await $typst.svg({ mainContent: typstMarkup }); + return svg; +} +``` + +- WASM modülleri: `typst-ts-web-compiler` (~7.6 MB) + `typst-ts-renderer` (~350 KB) +- Fontlar: Projeye gömülü font seti gerekecek (~4.4 MB). Başlangıçta Noto Sans / Inter gibi bir set yeterli. +- İlk yükleme ağır olabilir — lazy loading ve cache stratejisi gerekli (Service Worker ile WASM cache'leme). + +--- + +## Veri Modeli + +### Layout Sistemi: Container-Based + +Eski model (her eleman absolute `place()`) yerine, CSS Flexbox mantığına benzeyen container-based layout kullanılır: + +- **Sayfa = kök container.** `page.margins` → kök container'ın `padding`'i olur. +- **Container'lar iç içe geçebilir.** `direction: "row" | "column"` ile yatay/dikey dizilim. +- **Elemanlar varsayılan olarak flow içindedir** — otomatik pozisyonlanır. +- **Opsiyonel absolute positioning:** Kullanıcı isterse bir elemanı `position: "absolute"` yapabilir. Bu durumda eleman parent container içinde absolute konumlanır (`place()` ile). + +Bu sayede: +- Tablo satırları artarsa alttaki elemanlar otomatik kayar. +- Aynı satıra iki kolon koymak için iç içe container yeterlidir. +- Absolute mod ile serbest pozisyonlama da mümkündür. + +### Boyut Sistemi (SizeValue) + +Her eleman ve container için `width` ve `height` şu tiplerden biri olabilir: + +| Tip | Açıklama | Typst karşılığı | +| ------- | ------------------------------------- | --------------- | +| `fixed` | Sabit boyut (mm) | `80mm` | +| `auto` | İçeriğe göre otomatik | `auto` | +| `fr` | Kalan alanı oransal doldur | `1fr`, `2fr` | + +Ek olarak `minWidth`, `maxWidth`, `minHeight`, `maxHeight` (mm) desteklenir. + +### Template JSON (Şablon Tanımı) + +```jsonc +{ + "id": "tpl_fatura_001", + "name": "Standart Fatura", + "page": { "width": 210, "height": 297 }, + "fonts": ["Noto Sans", "Noto Sans Mono"], + "root": { + "id": "root", + "type": "container", + "position": { "type": "flow" }, + "size": { "width": { "type": "auto" }, "height": { "type": "auto" } }, + "direction": "column", + "gap": 5, + "padding": { "top": 15, "right": 15, "bottom": 15, "left": 15 }, + "align": "stretch", + "justify": "start", + "style": {}, + "children": [ + { + "id": "c_header", + "type": "container", + "position": { "type": "flow" }, + "size": { "width": { "type": "fr", "value": 1 }, "height": { "type": "auto" } }, + "direction": "row", + "gap": 5, + "padding": { "top": 0, "right": 0, "bottom": 0, "left": 0 }, + "align": "start", + "justify": "start", + "style": {}, + "children": [ + { + "id": "el_firma", + "type": "text", + "position": { "type": "flow" }, + "size": { "width": { "type": "fr", "value": 1 }, "height": { "type": "auto" } }, + "style": { "fontSize": 14, "fontWeight": "bold" }, + "binding": { "type": "scalar", "path": "firma.unvan" } + }, + { + "id": "el_fatura_baslik", + "type": "static_text", + "position": { "type": "flow" }, + "size": { "width": { "type": "auto" }, "height": { "type": "auto" } }, + "style": { "fontSize": 12, "fontWeight": "bold", "align": "right" }, + "content": "FATURA" + } + ] + }, + { + "id": "el_cizgi", + "type": "line", + "position": { "type": "flow" }, + "size": { "width": { "type": "fr", "value": 1 }, "height": { "type": "auto" } }, + "style": { "strokeColor": "#000000", "strokeWidth": 0.5 } + } + ] + } +} +``` + +### Eleman Tipleri + +| Tip | Açıklama | Binding | +| ----------------- | ------------------------------------- | ---------------- | +| `container` | Düzen kutusu, çocuk elemanları barındırır | Yok | +| `static_text` | Sabit metin, veri bağlantısı yok | Yok | +| `text` | Dinamik metin, schema'dan veri çeker | Scalar | +| `repeating_table` | Array verisinden tekrarlayan tablo | Array | +| `line` | Yatay/dikey çizgi | Yok | +| `image` | Statik veya dinamik görsel | Opsiyonel scalar | +| `page_number` | Sayfa numarası (çok sayfalı belgeler) | Otomatik | + +### Container Özellikleri + +| Özellik | Tip | Açıklama | +| ----------- | ---------------------------------------- | --------------------------------- | +| `direction` | `"row"` \| `"column"` | Çocukları yatay mı dikey mi diz | +| `gap` | number (mm) | Çocuklar arası boşluk | +| `padding` | `{ top, right, bottom, left }` (mm) | İç boşluk | +| `align` | `"start"` \| `"center"` \| `"end"` \| `"stretch"` | Cross-axis hizalama | +| `justify` | `"start"` \| `"center"` \| `"end"` \| `"space-between"` | Main-axis dağılım | +| `style` | `{ backgroundColor, borderColor, borderWidth, borderRadius }` | Görsel stil | + +### Positioning Modları + +| Mod | Açıklama | Typst karşılığı | +| ---------- | ------------------------------------------- | ----------------------- | +| `flow` | Parent container'ın flow'una katıl (default)| `stack` / `box` içinde | +| `absolute` | Parent container içinde sabit konum | `place(dx, dy)` | + +### Fatura Örneği — Container Ağacı + +``` +Sayfa (kök container, column, padding: 15mm) +├── Header (container, row, gap: 5mm) +│ ├── Logo alanı (container, column, width: 60mm) +│ │ ├── image (logo) +│ │ └── text (firma ünvanı) +│ └── Fatura bilgi (container, column, width: fill, align: end) +│ ├── static_text ("FATURA") +│ ├── text (fatura no) +│ └── text (tarih) +├── line (ayırıcı çizgi) +├── repeating_table (kalemler) +└── Footer (container, row) + ├── boş alan (width: fill) + └── Toplamlar (container, column, width: 80mm) + ├── text (ara toplam) + ├── text (KDV) + └── text (genel toplam) +``` + +### Data JSON (Gerçek Veri) + +Render zamanında şablonla birleştirilen veri: + +```jsonc +{ + "firma": { + "unvan": "Acme Teknoloji A.Ş.", + "vergiNo": "1234567890", + "logo": "data:image/png;base64,...", + }, + "fatura": { + "no": "FTR-2026-001", + "tarih": "2026-03-29", + }, + "kalemler": [ + { + "siraNo": 1, + "adi": "Web Geliştirme Hizmeti", + "miktar": 1, + "birim": "Adet", + "birimFiyat": 15000, + "tutar": 15000, + }, + { + "siraNo": 2, + "adi": "SSL Sertifikası", + "miktar": 2, + "birim": "Adet", + "birimFiyat": 500, + "tutar": 1000, + }, + ], + "toplamlar": { + "araToplam": 16000, + "kdv": 2880, + "genelToplam": 18880, + }, +} +``` + +### JSON Schema (Veri Yapısı Tanımı) + +Editörün sol panelinde kullanıcıya sunulan, bağlanabilir alanların ağaç yapısı. Kullanıcı bu ağaçtan sürükleyerek elemanları bağlar. + +```jsonc +{ + "$id": "fatura-schema", + "type": "object", + "properties": { + "firma": { + "type": "object", + "properties": { + "unvan": { "type": "string", "title": "Firma Ünvanı" }, + "vergiNo": { "type": "string", "title": "Vergi No" }, + "logo": { "type": "string", "title": "Logo", "format": "image" }, + }, + }, + "fatura": { + "type": "object", + "properties": { + "no": { "type": "string", "title": "Fatura No" }, + "tarih": { "type": "string", "title": "Tarih", "format": "date" }, + }, + }, + "kalemler": { + "type": "array", + "title": "Fatura Kalemleri", + "items": { + "type": "object", + "properties": { + "siraNo": { "type": "integer", "title": "Sıra No" }, + "adi": { "type": "string", "title": "Ürün / Hizmet Adı" }, + "miktar": { "type": "number", "title": "Miktar" }, + "birim": { "type": "string", "title": "Birim" }, + "birimFiyat": { + "type": "number", + "title": "Birim Fiyat", + "format": "currency", + }, + "tutar": { "type": "number", "title": "Tutar", "format": "currency" }, + }, + }, + }, + "toplamlar": { + "type": "object", + "properties": { + "araToplam": { + "type": "number", + "title": "Ara Toplam", + "format": "currency", + }, + "kdv": { "type": "number", "title": "KDV", "format": "currency" }, + "genelToplam": { + "type": "number", + "title": "Genel Toplam", + "format": "currency", + }, + }, + }, + }, +} +``` + +--- + +## Binding Mekanizması + +### Scalar Binding + +Basit alan bağlama — bir eleman, JSON'daki tek bir değere bağlanır. + +- Editörde: Kullanıcı schema ağacından bir alanı sürükleyip text elemanına bırakır. +- Template JSON'da: `"binding": { "type": "scalar", "path": "firma.vergiNo" }` +- Typst çıktısında: `#data.firma.vergiNo` + +### Array Binding (Tekrarlayan Tablo) + +Array verisi için özel tablo bileşeni. Kullanıcı: + +1. Araç çubuğundan "Tekrarlayan Tablo" bileşenini sürükler. +2. `dataSource` olarak schema'daki bir array alanı seçer (ör: `kalemler`). +3. Sütun tanımlarında array'in alt alanlarını seçer (ör: `kalemler[].adi`). +4. Tablo stili (header rengi, zebra satırlar vs.) ayarlar. + +Typst çıktısında: + +```typst +#let kalemler = data.kalemler +#table( + columns: (8%, 40%, 12%, 10%, 15%, 15%), + align: (center, left, right, center, right, right), + fill: (_, row) => if row == 0 { rgb("#f0f0f0") } else if calc.odd(row) { rgb("#fafafa") } else { none }, + [*\#*], [*Ürün / Hizmet*], [*Miktar*], [*Birim*], [*Birim Fiyat*], [*Tutar*], + ..kalemler.map(k => ( + [#k.siraNo], + [#k.adi], + [#k.miktar], + [#k.birim], + [#format-currency(k.birimFiyat)], + [#format-currency(k.tutar)], + )).flatten() +) +``` + +### Format Fonksiyonları + +Schema'daki `format` alanına göre Typst helper fonksiyonları üretilir: + +- `currency` → para birimi formatlama (binlik ayracı, kuruş, ₺ sembolü) +- `date` → tarih formatlama (gün.ay.yıl) +- `percentage` → yüzde formatlama + +--- + +## Template JSON → Typst Markup Dönüşümü + +Bu dönüşüm hem frontend'de (WASM önizleme için) hem backend'de (PDF üretimi için) çalışır. Aynı saf fonksiyon her iki tarafta da kullanılmalıdır: + +- **Frontend:** TypeScript'te yazılır (`core/template-to-typst.ts`). +- **Backend:** Aynı mantık Rust'ta implemente edilir. + +Her iki implementasyon da birebir aynı Typst çıktısını üretmelidir. Tutarlılık testleri yazılmalıdır. + +### Dönüşüm Kuralları (Container-Based) + +1. **Sayfa ayarları** — kök container'ın padding'i = sayfa margin: + + ```typst + #set page(width: 210mm, height: 297mm, margin: (top: 15mm, right: 15mm, bottom: 15mm, left: 15mm)) + ``` + +2. **Veri enjeksiyonu:** + + ```typst + #let data = ( firma: ( unvan: "Acme A.Ş.", ... ), ... ) + ``` + +3. **Container (column) → `stack(dir: ttb)`:** + + ```typst + #stack(dir: ttb, spacing: 5mm, + [#text(size: 18pt, weight: "bold")[dreport]], + [#text(size: 11pt, fill: rgb("#666666"))[Alt başlık]], + ) + ``` + +4. **Container (row) → `stack(dir: ltr)`:** + + ```typst + #stack(dir: ltr, spacing: 5mm, + [#box(width: 1fr)[Sol kolon]], + [#box(width: 1fr)[Sağ kolon]], + ) + ``` + +5. **İç içe container → `box` + `stack`:** + + ```typst + #box(width: 1fr, inset: (top: 5mm, bottom: 5mm))[ + #stack(dir: ttb, spacing: 3mm, + [#text[Eleman 1]], + [#text[Eleman 2]], + ) + ] + ``` + +6. **Absolute eleman → `place()` (sadece absolute positioning seçilmişse):** + + ```typst + #place(top + left, dx: 130mm, dy: 30mm)[ + #text(size: 12pt, weight: "bold")[FATURA] + ] + ``` + +7. **Çizgi → `line()`:** + + ```typst + #line(length: 1fr, stroke: 0.5pt + rgb("#000000")) + ``` + +8. **Önizleme vs. nihai render:** + - Önizleme: `data` mock veri ile doldurulur. + - Nihai render: `data` gerçek JSON verisi ile doldurulur. + +--- + +## SVG Overlay — Etkileşim Katmanı + +Typst SVG'si read-only bir render çıktısıdır — tıklama veya sürükleme algılamaz. SVG'nin üstüne Vue bileşenleriyle bir etkileşim katmanı (overlay) koyulur. + +### Overlay Nasıl Çalışır (Container Layout) + +1. Overlay, template JSON'un ağaç yapısını yansıtır (recursive `ElementHandle` bileşenleri). +2. Kök overlay, sayfa padding'ini CSS padding olarak uygular. +3. Flow elemanlar `position: relative` ile doğal akışta durur. +4. Absolute elemanlar `position: absolute` ile parent container içinde konumlanır. +5. Tıklama ile seçim → mavi kenarlık (container ise mor kenarlık) + resize handle'lar. +6. Absolute elemanlar sürüklenebilir — drag sırasında CSS transform, bırakınca Typst re-render. +7. Flow elemanlar sürüklenemez — sıra değişikliği drag-to-reorder ile yapılır (ilerideki fazda). + +### Overlay ↔ SVG Koordinat Eşleştirme + +- Overlay container'ı, sayfa CSS variable'ları ile margin'leri eşler. +- Zoom yapıldığında hem SVG hem overlay aynı oranda scale edilir. +- Koordinat dönüşümü: `px = mm * (containerWidthPx / pageWidthMm) * zoomLevel` + +--- + +## Proje Yapısı (Monorepo) + +``` +dreport/ +├── CLAUDE.md +├── README.md +├── frontend/ # Vue 3 + TypeScript +│ ├── package.json +│ ├── vite.config.ts +│ ├── tsconfig.json +│ ├── public/ +│ │ └── fonts/ # Typst WASM için gömülü fontlar +│ ├── src/ +│ │ ├── main.ts +│ │ ├── App.vue +│ │ ├── stores/ # Pinia +│ │ │ ├── template.ts # Template JSON state +│ │ │ ├── schema.ts # JSON Schema state +│ │ │ └── editor.ts # Editör UI state (seçili eleman, zoom vs.) +│ │ ├── components/ +│ │ │ ├── editor/ +│ │ │ │ ├── EditorCanvas.vue # Ana editör alanı (SVG + overlay container) +│ │ │ │ ├── TypstSvgLayer.vue # Typst SVG render katmanı +│ │ │ │ ├── InteractionOverlay.vue # Etkileşim katmanı (tüm handle'ların parent'ı) +│ │ │ │ ├── ElementHandle.vue # Tekil eleman seçim/drag/resize handle +│ │ │ │ ├── SnapGuides.vue # Hizalama çizgileri +│ │ │ │ └── RulerBar.vue # Cetvel +│ │ │ ├── panels/ +│ │ │ │ ├── ToolboxPanel.vue # Sol: bileşen araç kutusu +│ │ │ │ ├── SchemaTreePanel.vue # Sol: JSON schema ağacı (drag source) +│ │ │ │ └── PropertiesPanel.vue # Sağ: seçili elemanın özellikleri +│ │ │ ├── properties/ +│ │ │ │ ├── TextProperties.vue +│ │ │ │ ├── TableProperties.vue +│ │ │ │ ├── ImageProperties.vue +│ │ │ │ └── StyleProperties.vue +│ │ │ └── common/ +│ │ │ ├── ColorPicker.vue +│ │ │ ├── FontSelector.vue +│ │ │ └── UnitInput.vue # mm/pt girişi +│ │ ├── composables/ +│ │ │ ├── useTypstCompiler.ts # Typst WASM yönetimi (Web Worker iletişimi) +│ │ │ ├── useDragDrop.ts # Sürükle-bırak mantığı +│ │ │ ├── useElementSelection.ts # Eleman seçimi +│ │ │ ├── useSnapGuides.ts # Mıknatıslı hizalama +│ │ │ ├── useUndoRedo.ts # Geri al / yinele +│ │ │ └── useZoomPan.ts # Zoom ve kaydırma +│ │ ├── workers/ +│ │ │ └── typst.worker.ts # Typst WASM Web Worker +│ │ ├── core/ +│ │ │ ├── template-to-typst.ts # Template JSON → Typst markup dönüşümü +│ │ │ ├── schema-parser.ts # JSON Schema → ağaç yapısı (panel için) +│ │ │ ├── mock-data-generator.ts # Schema'dan örnek veri üretme +│ │ │ └── types.ts # Ortak TypeScript tip tanımları +│ │ └── styles/ +│ │ └── editor.css +│ └── tests/ +│ ├── template-to-typst.test.ts +│ └── schema-parser.test.ts +│ +├── backend/ # Rust + Axum +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── main.rs +│ │ ├── routes/ +│ │ │ ├── mod.rs +│ │ │ ├── render.rs # POST /api/render → PDF +│ │ │ └── health.rs # GET /api/health +│ │ ├── typst_engine/ +│ │ │ ├── mod.rs +│ │ │ ├── compiler.rs # typst crate wrapper (World impl) +│ │ │ ├── template_to_typst.rs # Template JSON → Typst markup (Rust) +│ │ │ └── fonts.rs # Font yönetimi ve yükleme +│ │ └── models/ +│ │ ├── mod.rs +│ │ ├── template.rs # Template JSON serde modelleri +│ │ └── schema.rs # JSON Schema modelleri +│ ├── fonts/ # Gömülü font dosyaları +│ │ ├── NotoSans-Regular.ttf +│ │ ├── NotoSans-Bold.ttf +│ │ ├── NotoSans-Italic.ttf +│ │ └── NotoSansMono-Regular.ttf +│ └── tests/ +│ ├── render_test.rs +│ └── template_to_typst_test.rs +│ +└── shared/ # Ortak şema tanımları + └── schemas/ + ├── fatura.schema.json + └── irsaliye.schema.json +``` + +--- + +## API Endpoints + +İlk aşamada minimal API: + +### `POST /api/render` + +Template JSON + Data JSON alır, PDF döner. + +**Request:** + +```json +{ + "template": {}, + "data": {} +} +``` + +**Response:** `Content-Type: application/pdf` — binary PDF + +### `GET /api/health` + +Sunucu sağlık kontrolü. + +--- + +## Editör UI/UX Davranışları + +### Eleman Ekleme + +- **Araç kutusundan sürükle-bırak:** Container, statik metin, çizgi, görsel, tekrarlayan tablo. +- **Container'a bırakma:** Eleman hedef container'ın flow'una eklenir. Drop pozisyonuna göre sıra belirlenir. +- **Schema ağacından sürükle-bırak:** Scalar alan container'a bırakılınca `text` elemanı oluşur, binding ayarlanır. +- **Schema'dan array alanı sürükle-bırak:** `repeating_table` oluşur, sütunları ayarlama diyaloğu açılır. + +### Eleman Seçimi ve Manipülasyon + +- Tıklama ile seçim → mavi kenarlık (container ise mor kenarlık) + resize handle'lar. +- **Flow elemanlar:** Sürüklenemez (pozisyon otomatik). Sıra değişikliği drag-to-reorder ile. +- **Absolute elemanlar:** Drag ile taşınır. Sürükleme sırasında CSS transform, bırakınca Typst re-render. +- **Container seçimi:** Tıklayınca sağ panelde direction, gap, align, padding ayarları. +- **Positioning modu değişikliği:** Sağ panelde flow ↔ absolute geçişi. +- Delete/Backspace ile silme. +- Shift+tıklama ile çoklu seçim. + +### Undo/Redo + +- Template JSON üzerinde immutable snapshot stack. +- Ctrl+Z / Ctrl+Shift+Z. + +### Zoom ve Pan + +- Ctrl+scroll ile zoom. +- Space+drag veya orta fare tuşu ile pan. +- Zoom aralığı: %25 – %400. + +--- + +## Geliştirme Öncelikleri (Roadmap) + +### Faz 1: Temel Altyapı ✓ + +- [x] Proje iskeleti kurulumu (Vue + Vite + Pinia, Axum boilerplate) +- [x] Typst WASM entegrasyonu — Web Worker'da Typst markup → SVG +- [x] Template JSON → Typst markup dönüşümü (static_text, text, line) +- [x] Container-based layout sistemi (tree yapı, flow + absolute positioning) +- [x] EditorCanvas: Typst SVG + recursive overlay + seçim +- [x] Absolute elemanlar için drag ile taşıma +- [x] Resize handle'lar +- [x] Backend iskeleti (Axum, health endpoint, render placeholder) +- [x] Font dosyaları (Noto Sans ailesi) + +### Faz 2: Editör Temelleri + +- [ ] `text` (dinamik binding'li) eleman tipi (Typst dönüşümü var, UI eksik) +- [ ] Schema tree paneli — JSON schema'dan ağaç oluşturma +- [ ] Schema'dan drag ile binding oluşturma +- [ ] Properties paneli — seçili elemanın stillerini düzenleme (font, renk, boyut, hizalama) +- [ ] Container properties paneli — direction, gap, padding, align ayarları +- [ ] Mock data generator — schema'dan örnek veri üretip önizlemede kullanma +- [ ] Undo/redo +- [ ] Toolbox paneli — eleman/container ekleme + +### Faz 3: Tablo ve Array Binding + +- [ ] `repeating_table` bileşeni ve Typst markup üretimi +- [ ] Sütun tanımlama UI'ı (alan seçimi, genişlik, hizalama) +- [ ] Array field'larına binding +- [ ] Tablo stili ayarları (header, zebra, border) +- [ ] Format fonksiyonları (currency, date) + +### Faz 4: PDF Render Backend + +- [ ] Axum server setup + `POST /api/render` +- [ ] Rust'ta template-to-typst dönüşümü (TypeScript versiyonuyla tutarlı) +- [ ] `typst` crate ile `World` trait implementasyonu +- [ ] PDF derleme ve response olarak dönme +- [ ] Font embed (frontend ile aynı font seti) +- [ ] Frontend'den "PDF İndir" butonu + +### Faz 5: Polish + +- [ ] Snap guides ve hizalama +- [ ] Zoom / pan +- [ ] `line`, `rect` eleman tipleri +- [ ] `image` eleman tipi (statik + dinamik) +- [ ] Sayfa numarası +- [ ] Çoklu sayfa desteği +- [ ] Template kaydetme / yükleme (JSON dosyası export/import) + +--- + +## Önemli Teknik Notlar + +### Typst WASM Font Stratejisi + +Typst tarayıcıda çalışırken sistem fontlarına erişemez. Font dosyaları (`*.ttf` / `*.otf`) projeye dahil edilmeli ve WASM'a yüklenmelidir. Başlangıçta minimal bir set: + +- Noto Sans (Regular, Bold, Italic, Bold Italic) — genel metin +- Noto Sans Mono (Regular) — tablo sayıları, monospace ihtiyaçları +- Toplam ~4-5 MB + +**Kritik:** Backend'de (Rust) ve frontend'de (WASM) birebir aynı font dosyaları kullanılmalıdır. Farklı font = farklı metrik = render uyumsuzluğu. + +### Koordinat Sistemi + +- Tüm pozisyonlar **milimetre (mm)** cinsindendir. +- Template JSON'daki değerler mm, Typst'e `Xmm` olarak yazılır. +- Editör canvas'ta mm → px dönüşümü: `px = mm * (containerWidthPx / pageWidthMm) * zoomLevel` +- Referans: A4 = 210mm × 297mm. + +### Typst Özel Karakter Escape + +Template JSON → Typst dönüşümünde kullanıcı verisindeki özel karakterler escape edilmelidir: + +- `#`, `$`, `@`, `*`, `_`, `<`, `>`, `\` Typst'te özel anlam taşır. +- Kullanıcı verisi `[...]` content block'a sarılarak büyük ölçüde güvenli hale gelir. +- İçerideki `[`, `]` karakterleri ise `\[`, `\]` olarak escape edilmelidir. + +### Hata Yönetimi + +- Typst derleme hatası olursa → editörde kırmızı banner ile hata mesajı göster. +- Derleme başarısız olduğunda son başarılı SVG'yi koru, kullanıcının çalışmasını bozma. +- Web Worker crash olursa → yeniden başlat, state'i koru. + +### Eleman Sırası (Z-Order) + +- Template JSON'daki `elements` dizisinin sırası = çizim sırası (sonraki üstte). +- Kullanıcı "Öne Getir" / "Arkaya Gönder" yapabilmeli → dizi sırası değişir. + +--- + +## Rust Backend — Typst World Implementasyonu + +Typst crate ile PDF üretmek için `World` trait'i implement etmek gerekir. Bu trait, Typst'e dosya sistemi, fontlar ve zaman bilgisi sağlar. + +```rust +use typst::World; + +struct DreportWorld { + /// Ana .typ dosyasının içeriği (dinamik üretilen markup) + main_source: String, + /// Yüklenmiş font dosyaları + fonts: Vec, + /// Data JSON (json dosyası olarak erişilebilir) + data_json: String, +} + +impl World for DreportWorld { + // file(), font(), main(), source(), ... implementasyonları +} +``` + +Derleme akışı: + +1. HTTP request gelir (template + data JSON). +2. Template JSON → Typst markup string üretilir. +3. Data JSON, sanal dosya sistemi üzerinden `data.json` olarak erişilebilir yapılır. +4. `DreportWorld` oluşturulur. +5. `typst::compile(&world)` → `Document` elde edilir. +6. `typst_pdf::pdf(&document, ...)` → PDF bytes. +7. Response olarak döndürülür. + +--- + +## Kod Stili ve Konvansiyonlar + +### Frontend (TypeScript / Vue) + +- Composition API + ` diff --git a/frontend/src/components/panels/ToolboxPanel.vue b/frontend/src/components/panels/ToolboxPanel.vue index a645698..a703c21 100644 --- a/frontend/src/components/panels/ToolboxPanel.vue +++ b/frontend/src/components/panels/ToolboxPanel.vue @@ -1,9 +1,12 @@