mirror of
https://github.com/duhanbalci/dreport.git
synced 2026-07-02 02:49:16 +00:00
805 lines
32 KiB
Markdown
805 lines
32 KiB
Markdown
# 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<string> {
|
||
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<typst::text::Font>,
|
||
/// 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 + `<script setup>` kullan, Options API KULLANMA.
|
||
- Pinia store'lar `defineStore` ile.
|
||
- Tip güvenliği: `strict: true` tsconfig'de. `any` kullanma, gerekirse `unknown` + type guard.
|
||
- Composable isimlendirme: `useXxx` pattern.
|
||
- Bileşen isimleri: PascalCase, en az iki kelime (ör: `EditorCanvas`, `SchemaTreePanel`).
|
||
- CSS: Scoped styles veya CSS modules. Global CSS minimum.
|
||
|
||
### Backend (Rust)
|
||
|
||
- Axum handler'lar async.
|
||
- Serde ile JSON serialize/deserialize (`#[derive(Serialize, Deserialize)]`).
|
||
- Hata yönetimi: `thiserror` ile typed errors, handler'larda `anyhow` kabul edilebilir.
|
||
- Typst crate dependency: `typst`, `typst-pdf`.
|
||
- Clippy uyarıları temiz tutulacak.
|
||
|
||
### Genel
|
||
|
||
- Commit mesajları: conventional commits (`feat:`, `fix:`, `refactor:`, `docs:` vs.).
|
||
- Türkçe yorum yazılabilir, kod ve değişken isimleri İngilizce.
|
||
- Template JSON field isimleri İngilizce (ör: `position`, `size`, `binding`).
|
||
- UI etiketleri ve kullanıcıya gösterilen metinler Türkçe.
|
||
|
||
---
|
||
|
||
## Kısıtlamalar ve Bilinçli Tercihler
|
||
|
||
1. **Veritabanı yok (ilk aşama).** Template'ler JSON dosyası olarak import/export edilir.
|
||
2. **Kullanıcı auth yok.** Tek kullanıcılı yerel kullanım senaryosu.
|
||
3. **Sadece PDF çıktı.** Typst'in SVG/PNG/HTML çıktıları ileride eklenebilir.
|
||
4. **Tekrarlayan bölge (repeating region) yok — sadece tekrarlayan tablo.** Array binding yalnızca tablo bileşeni ile yapılır. Serbest form repeating region ilerideki fazlarda değerlendirilir.
|
||
5. **WYSIWYG garantisi Typst üzerinden.** Editörde kendi render engine'imiz yok — Typst ne üretiyorsa kullanıcı onu görür.
|
||
6. **Canvas kütüphanesi (fabric.js / konva.js) kullanılmıyor.** Etkileşim katmanı saf Vue bileşenleri + pointer event'ler ile yapılır. Render zaten Typst SVG'sidir.
|