mirror of
https://github.com/duhanbalci/dreport.git
synced 2026-07-01 18:39:16 +00:00
724 lines
29 KiB
Markdown
724 lines
29 KiB
Markdown
# CLAUDE.md — dreport
|
||
|
||
## Proje Ozeti
|
||
|
||
**dreport**, kullanicilarin fatura, irsaliye, rapor gibi belge sablonlarini gorsel bir drag & drop editor ile tasarlayip, JSON veri ile birlestirerek PDF cikti almasini saglayan bir belge tasarim aracidir.
|
||
|
||
Temel fark: Editorde ayri bir canvas render engine (fabric.js, konva.js vb.) KULLANILMAZ. Bunun yerine custom bir layout engine (taffy + cosmic-text) kullanilir. Ayni layout engine hem editorde onizleme (HTML div'ler) hem backend'de PDF uretimi (krilla) icin calisir. Bu sayede "editorde gordugum ile PDF'te aldigim farkli" sorunu ortadan kalkar.
|
||
|
||
---
|
||
|
||
## Teknoloji Kararlari
|
||
|
||
| Katman | Teknoloji | Gerekce |
|
||
| ----------------- | ------------------------------------ | ---------------------------------------------------------------- |
|
||
| Frontend | Vue 3 (Composition API) + TypeScript | Kullanici tercihi |
|
||
| Layout Engine | taffy (flexbox) + cosmic-text | Template JSON → hesaplanmis pozisyonlar; hem WASM hem native |
|
||
| Editor Render | HTML div'ler (LayoutRenderer.vue) | Layout engine sonuclarina gore CSS ile render |
|
||
| Etkilesim Katmani | DOM overlay (Vue bilesenleri) | Layout sonuclari uzerine secim, surekleme, yeniden boyutlandirma |
|
||
| Backend | Rust + Axum | Layout engine'i dogrudan kullanabilme; performans |
|
||
| PDF Render | krilla (server-side) | LayoutResult → PDF; font tutarliligi garantisi |
|
||
| Veri Formati | JSON (sablon tanimi + veri) | Evrensel, kolay serialize/deserialize |
|
||
| Paket Yonetimi | bun (frontend), cargo (backend) | — |
|
||
|
||
---
|
||
|
||
## Mimari Genel Bakis
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ VUE FRONTEND │
|
||
│ │
|
||
│ ┌───────────────┐ ┌───────────────────────────┐ │
|
||
│ │ Sol Panel │ │ Editor Canvas │ │
|
||
│ │ - Bilesenler │ │ │ │
|
||
│ │ - Schema Tree │ │ ┌─────────────────────┐ │ │
|
||
│ │ - Ozellikler │ │ │ LayoutRenderer.vue │ │ │
|
||
│ │ │ │ │ (HTML div render) │ │ │
|
||
│ │ │ │ └─────────────────────┘ │ │
|
||
│ │ │ │ ┌─────────────────────┐ │ │
|
||
│ │ │ │ │ InteractionOverlay │ │ │
|
||
│ │ │ │ │ secim / drag / resize│ │ │
|
||
│ │ │ │ └─────────────────────┘ │ │
|
||
│ └───────────────┘ └───────────────────────────┘ │
|
||
│ │
|
||
│ Template JSON → layout-engine WASM → LayoutResult │
|
||
│ → LayoutRenderer (HTML) + Overlay │
|
||
└──────────────────────────┬───────────────────────────┘
|
||
│ POST /api/render
|
||
▼
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ RUST BACKEND (Axum) │
|
||
│ │
|
||
│ Template JSON + Data JSON │
|
||
│ → compute_layout() (taffy + cosmic-text) │
|
||
│ → render_pdf() (krilla) │
|
||
│ → PDF bytes │
|
||
└──────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Render Pipeline
|
||
|
||
### Temel Prensip
|
||
|
||
Typst KULLANILMAZ. Bunun yerine custom layout engine:
|
||
|
||
1. Kullanicinin tasarimi bir **Template JSON** olarak tutulur.
|
||
2. Template JSON + Data JSON, **layout-engine** WASM modulu ile tarayicide islenir → **LayoutResult** uretilir.
|
||
3. LayoutResult, her elemanin mutlak pozisyonunu (x, y, width, height mm cinsinden) icerir.
|
||
4. **LayoutRenderer.vue** bu sonuclari HTML div'ler olarak render eder.
|
||
5. **InteractionOverlay.vue** ayni pozisyon bilgisiyle secim, drag, resize handle'lar koyar.
|
||
6. Kullanici bir elemani suruklediqinde → Template JSON guncellenir → layout-engine yeniden calisir → HTML guncellenir.
|
||
|
||
### Performans Stratejisi
|
||
|
||
- **Drag sirasinda:** Overlay katmaninda sadece CSS transform ile gorsel geri bildirim ver. Layout engine calistirma.
|
||
- **Drag bittiginde (mouseup/pointerup):** Template JSON'i guncelle, layout engine calistir, HTML'i yenile.
|
||
- **Debounce:** Ozellik panelinden yapilan degisikliklerde 150-300ms debounce ile hesapla.
|
||
- **Web Worker:** Layout engine WASM'i bir Web Worker icinde calistir. Ana thread'i ASLA bloklamayacak.
|
||
|
||
### Layout Engine (layout-engine crate)
|
||
|
||
```rust
|
||
// Temel kullanim
|
||
use dreport_layout::{compute_layout, LayoutResult};
|
||
|
||
let layout: LayoutResult = compute_layout(&template, &data, &fonts);
|
||
// layout.pages[0].elements → her elemanin x, y, width, height (mm)
|
||
```
|
||
|
||
WASM tarafinda (frontend):
|
||
|
||
```typescript
|
||
// layout.worker.ts icinde
|
||
import init, { computeLayout, loadFonts } from "dreport-layout-wasm";
|
||
|
||
await init();
|
||
await loadFonts(fontBytes);
|
||
const layoutJson = computeLayout(templateJson, dataJson);
|
||
```
|
||
|
||
- WASM modulu: ~1-2 MB (vs eski Typst 8MB)
|
||
- Fontlar: Ayni Noto Sans seti (~4-5 MB), font olcum icin gerekli.
|
||
|
||
---
|
||
|
||
## Veri Modeli
|
||
|
||
### Layout Sistemi: Container-Based
|
||
|
||
CSS Flexbox mantigina benzeyen container-based layout:
|
||
|
||
- **Sayfa = kok container.** `page.margins` → kok container'in `padding`'i olur.
|
||
- **Container'lar ic ice gecebilir.** `direction: "row" | "column"` ile yatay/dikey dizilim.
|
||
- **Elemanlar varsayilan olarak flow icindedir** — otomatik pozisyonlanir.
|
||
- **Opsiyonel absolute positioning:** Kullanici isterse bir elemani `position: "absolute"` yapabilir.
|
||
|
||
Bu sayede:
|
||
|
||
- Tablo satirlari artarsa alttaki elemanlar otomatik kayar.
|
||
- Ayni satira iki kolon koymak icin ic ice container yeterlidir.
|
||
- Absolute mod ile serbest pozisyonlama da mumkundur.
|
||
|
||
### Boyut Sistemi (SizeValue)
|
||
|
||
Her eleman ve container icin `width` ve `height` su tiplerden biri olabilir:
|
||
|
||
| Tip | Aciklama | Taffy karsiligi |
|
||
| ------- | -------------------------- | ----------------------------- |
|
||
| `fixed` | Sabit boyut (mm) | `Dimension::Length(pt)` |
|
||
| `auto` | Iceriqe gore otomatik | `Dimension::Auto` |
|
||
| `fr` | Kalan alani oransal doldur | `flex_grow: n, flex_basis: 0` |
|
||
|
||
Ek olarak `minWidth`, `maxWidth`, `minHeight`, `maxHeight` (mm) desteklenir.
|
||
|
||
### Template JSON (Sablon Tanimi)
|
||
|
||
```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 | Aciklama | Binding |
|
||
| ----------------- | ----------------------------------------- | ---------------- |
|
||
| `container` | Duzen kutusu, cocuk elemanlari barindirir | Yok |
|
||
| `static_text` | Sabit metin, veri baglantisi yok | Yok |
|
||
| `text` | Dinamik metin, schema'dan veri ceker | Scalar |
|
||
| `repeating_table` | Array verisinden tekrarlayan tablo | Array |
|
||
| `line` | Yatay/dikey cizgi | Yok |
|
||
| `image` | Statik veya dinamik gorsel | Opsiyonel scalar |
|
||
| `page_number` | Sayfa numarasi (cok sayfali belgeler) | Otomatik |
|
||
|
||
### Container Ozellikleri
|
||
|
||
| Ozellik | Tip | Aciklama |
|
||
| ----------- | ------------------------------------------------------------- | ------------------------------- |
|
||
| `direction` | `"row"` \| `"column"` | Cocuklari yatay mi dikey mi diz |
|
||
| `gap` | number (mm) | Cocuklar arasi bosluk |
|
||
| `padding` | `{ top, right, bottom, left }` (mm) | Ic bosluk |
|
||
| `align` | `"start"` \| `"center"` \| `"end"` \| `"stretch"` | Cross-axis hizalama |
|
||
| `justify` | `"start"` \| `"center"` \| `"end"` \| `"space-between"` | Main-axis dagilim |
|
||
| `style` | `{ backgroundColor, borderColor, borderWidth, borderRadius }` | Gorsel stil |
|
||
|
||
### Positioning Modlari
|
||
|
||
| Mod | Aciklama | Taffy karsiligi |
|
||
| ---------- | -------------------------------------------- | ------------------------------------- |
|
||
| `flow` | Parent container'in flow'una katil (default) | `Position::Relative` |
|
||
| `absolute` | Parent container icinde sabit konum | `Position::Absolute, inset: top/left` |
|
||
|
||
### Fatura Ornegi — Container Agaci
|
||
|
||
```
|
||
Sayfa (kok container, column, padding: 15mm)
|
||
├── Header (container, row, gap: 5mm)
|
||
│ ├── Logo alani (container, column, width: 60mm)
|
||
│ │ ├── image (logo)
|
||
│ │ └── text (firma unvani)
|
||
│ └── Fatura bilgi (container, column, width: fill, align: end)
|
||
│ ├── static_text ("FATURA")
|
||
│ ├── text (fatura no)
|
||
│ └── text (tarih)
|
||
├── line (ayirici cizgi)
|
||
├── repeating_table (kalemler)
|
||
└── Footer (container, row)
|
||
├── bos alan (width: fill)
|
||
└── Toplamlar (container, column, width: 80mm)
|
||
├── text (ara toplam)
|
||
├── text (KDV)
|
||
└── text (genel toplam)
|
||
```
|
||
|
||
### Data JSON (Gercek Veri)
|
||
|
||
Render zamaninda sablonla birlestirilen veri:
|
||
|
||
```jsonc
|
||
{
|
||
"firma": {
|
||
"unvan": "Acme Teknoloji A.S.",
|
||
"vergiNo": "1234567890",
|
||
"logo": "data:image/png;base64,...",
|
||
},
|
||
"fatura": {
|
||
"no": "FTR-2026-001",
|
||
"tarih": "2026-03-29",
|
||
},
|
||
"kalemler": [
|
||
{
|
||
"siraNo": 1,
|
||
"adi": "Web Gelistirme Hizmeti",
|
||
"miktar": 1,
|
||
"birim": "Adet",
|
||
"birimFiyat": 15000,
|
||
"tutar": 15000,
|
||
},
|
||
{
|
||
"siraNo": 2,
|
||
"adi": "SSL Sertifikasi",
|
||
"miktar": 2,
|
||
"birim": "Adet",
|
||
"birimFiyat": 500,
|
||
"tutar": 1000,
|
||
},
|
||
],
|
||
"toplamlar": {
|
||
"araToplam": 16000,
|
||
"kdv": 2880,
|
||
"genelToplam": 18880,
|
||
},
|
||
}
|
||
```
|
||
|
||
### JSON Schema (Veri Yapisi Tanimi)
|
||
|
||
Editorun sol panelinde kullaniciya sunulan, baglanabilir alanlarin agac yapisi. Kullanici bu agactan surukleyerek elemanlari baglar.
|
||
|
||
```jsonc
|
||
{
|
||
"$id": "fatura-schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"firma": {
|
||
"type": "object",
|
||
"properties": {
|
||
"unvan": { "type": "string", "title": "Firma Unvani" },
|
||
"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": "Sira No" },
|
||
"adi": { "type": "string", "title": "Urun / Hizmet Adi" },
|
||
"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 Mekanizmasi
|
||
|
||
### Scalar Binding
|
||
|
||
Basit alan baglama — bir eleman, JSON'daki tek bir degere baglanir.
|
||
|
||
- Editorde: Kullanici schema agacindan bir alani surukleyip text elemanina birakir.
|
||
- Template JSON'da: `"binding": { "type": "scalar", "path": "firma.vergiNo" }`
|
||
- Layout engine'de: `data_resolve.rs` JSON path'i cozumler, text icerigini uretir.
|
||
|
||
### Array Binding (Tekrarlayan Tablo)
|
||
|
||
Array verisi icin ozel tablo bileseni. Kullanici:
|
||
|
||
1. Arac kutusundan "Tekrarlayan Tablo" bilesenini surukler.
|
||
2. `dataSource` olarak schema'daki bir array alani secer (or: `kalemler`).
|
||
3. Sutun tanimlarinda array'in alt alanlarini secer (or: `kalemler[].adi`).
|
||
4. Tablo stili (header rengi, zebra satirlar vs.) ayarlar.
|
||
|
||
Layout engine'de: `table_layout.rs` repeating_table'i satir/sutun container agacina acar, taffy ile layout hesaplar.
|
||
|
||
### Format Fonksiyonlari
|
||
|
||
Schema'daki `format` alanina gore formatlama yapilir:
|
||
|
||
- `currency` → para birimi formatlama (binlik ayiraci, kurus, ₺ sembolu)
|
||
- `date` → tarih formatlama (gun.ay.yil)
|
||
- `percentage` → yuzde formatlama
|
||
|
||
---
|
||
|
||
## Layout Engine Detaylari
|
||
|
||
### LayoutResult (engine ciktisi)
|
||
|
||
```rust
|
||
pub struct LayoutResult {
|
||
pub pages: Vec<PageLayout>,
|
||
}
|
||
|
||
pub struct PageLayout {
|
||
pub width_mm: f64,
|
||
pub height_mm: f64,
|
||
pub elements: Vec<ElementLayout>,
|
||
}
|
||
|
||
pub struct ElementLayout {
|
||
pub id: String,
|
||
pub x_mm: f64, // Sayfa sol ustten mutlak pozisyon
|
||
pub y_mm: f64,
|
||
pub width_mm: f64,
|
||
pub height_mm: f64,
|
||
pub element_type: String,
|
||
pub content: Option<ResolvedContent>,
|
||
pub style: ResolvedStyle,
|
||
}
|
||
```
|
||
|
||
### Taffy Mapping
|
||
|
||
| dreport | taffy |
|
||
| ----------------------------------------- | -------------------------------------------- |
|
||
| `container(direction: row)` | `FlexDirection::Row` |
|
||
| `container(direction: column)` | `FlexDirection::Column` |
|
||
| `gap` | `gap: Size { width, height }` |
|
||
| `padding` | `padding: Rect { top, right, bottom, left }` |
|
||
| `align: start/center/end/stretch` | `align_items` |
|
||
| `justify: start/center/end/space-between` | `justify_content` |
|
||
| `SizeValue::Fixed(mm)` | `Dimension::Length(pt)` |
|
||
| `SizeValue::Auto` | `Dimension::Auto` |
|
||
| `SizeValue::Fr(n)` | `flex_grow: n, flex_basis: 0` |
|
||
| `PositionMode::Absolute` | `Position::Absolute, inset: top/left` |
|
||
|
||
Text leaf node'lari → taffy `MeasureFunc` callback'i ile cosmic-text'ten olcum alir.
|
||
|
||
### PDF Render (Backend)
|
||
|
||
```rust
|
||
// render.rs akisi
|
||
let layout = dreport_layout::compute_layout(&template, &data, &fonts);
|
||
let pdf_bytes = dreport_layout::pdf_render::render_pdf(&layout, &fonts)?;
|
||
// → Response olarak dondurulur
|
||
```
|
||
|
||
krilla kutuphanesi ile her ElementLayout'u PDF sayfasina cizer: text, line, rect, image, table.
|
||
|
||
---
|
||
|
||
## Interaction Overlay
|
||
|
||
Layout engine pozisyon bilgisi uretiyor — bu pozisyonlar hem gorsel render (LayoutRenderer.vue) hem etkilesim katmani (InteractionOverlay.vue) tarafindan kullanilir.
|
||
|
||
### Overlay Nasil Calisir
|
||
|
||
1. LayoutResult'taki her eleman icin pozisyon ve boyut bilgisi (mm) alinir.
|
||
2. mm → px donusumu yapilir: `px = mm * scale * zoomLevel`
|
||
3. Her eleman icin CSS `position: absolute` + `left/top/width/height` ile handle yerlestirilir.
|
||
4. Tikla ile secim → mavi kenarlik (container ise mor kenarlik) + resize handle'lar.
|
||
5. Absolute elemanlar suruklenebilir — drag sirasinda CSS transform, birakinca layout engine re-compute.
|
||
6. Flow elemanlar suruklenemez — sira degisikligi drag-to-reorder ile yapilir.
|
||
|
||
### En Buyuk Kazanim
|
||
|
||
Eski mimari: Typst SVG (opak) + Vue overlay (pozisyon sync'i sorunlu)
|
||
Yeni mimari: Layout engine pozisyon verir → DOM div'ler hem gorsel hem etkilesim katmani. Ayri SVG/overlay senkronizasyonu sorunu yok.
|
||
|
||
---
|
||
|
||
## Proje Yapisi (Monorepo)
|
||
|
||
```
|
||
dreport/
|
||
├── CLAUDE.md
|
||
├── Cargo.toml # Workspace: core, backend, layout-engine
|
||
├── justfile
|
||
├── frontend/ # Vue 3 + TypeScript
|
||
│ ├── package.json
|
||
│ ├── vite.config.ts
|
||
│ ├── tsconfig.json
|
||
│ ├── public/
|
||
│ │ └── fonts/ # Layout engine WASM icin gomulu fontlar
|
||
│ ├── src/
|
||
│ │ ├── main.ts
|
||
│ │ ├── App.vue
|
||
│ │ ├── stores/ # Pinia
|
||
│ │ │ ├── template.ts # Template JSON state
|
||
│ │ │ ├── schema.ts # JSON Schema state
|
||
│ │ │ └── editor.ts # Editor UI state (secili eleman, zoom vs.)
|
||
│ │ ├── components/
|
||
│ │ │ ├── editor/
|
||
│ │ │ │ ├── EditorCanvas.vue # Ana editor alani (LayoutRenderer + overlay)
|
||
│ │ │ │ ├── LayoutRenderer.vue # LayoutResult → HTML div render
|
||
│ │ │ │ ├── InteractionOverlay.vue # Etkilesim katmani (secim/drag/resize)
|
||
│ │ │ │ ├── ElementHandle.vue # Tekil eleman secim/drag/resize handle
|
||
│ │ │ │ ├── SnapGuides.vue # Hizalama cizgileri
|
||
│ │ │ │ └── RulerBar.vue # Cetvel
|
||
│ │ │ ├── panels/
|
||
│ │ │ │ ├── ToolboxPanel.vue # Sol: bilesen arac kutusu
|
||
│ │ │ │ ├── SchemaTreePanel.vue # Sol: JSON schema agaci (drag source)
|
||
│ │ │ │ └── PropertiesPanel.vue # Sag: secili elemanin ozellikleri
|
||
│ │ │ ├── properties/
|
||
│ │ │ │ ├── TextProperties.vue
|
||
│ │ │ │ ├── TableProperties.vue
|
||
│ │ │ │ ├── ImageProperties.vue
|
||
│ │ │ │ └── StyleProperties.vue
|
||
│ │ │ └── common/
|
||
│ │ │ ├── ColorPicker.vue
|
||
│ │ │ ├── FontSelector.vue
|
||
│ │ │ └── UnitInput.vue # mm/pt girisi
|
||
│ │ ├── composables/
|
||
│ │ │ ├── useLayoutEngine.ts # Layout engine WASM yonetimi (Web Worker iletisimi)
|
||
│ │ │ ├── useDragDrop.ts # Surukle-birak mantigi
|
||
│ │ │ ├── useElementSelection.ts # Eleman secimi
|
||
│ │ │ ├── useSnapGuides.ts # Miknatisli hizalama
|
||
│ │ │ ├── useUndoRedo.ts # Geri al / yinele
|
||
│ │ │ └── useZoomPan.ts # Zoom ve kaydirma
|
||
│ │ ├── workers/
|
||
│ │ │ └── layout.worker.ts # Layout engine WASM Web Worker
|
||
│ │ ├── core/
|
||
│ │ │ ├── types.ts # Ortak TypeScript tip tanimlari
|
||
│ │ │ ├── layout-types.ts # LayoutResult TypeScript tipleri
|
||
│ │ │ ├── schema-parser.ts # JSON Schema → agac yapisi (panel icin)
|
||
│ │ │ └── mock-data-generator.ts # Schema'dan ornek veri uretme
|
||
│ │ └── styles/
|
||
│ │ └── editor.css
|
||
│ └── tests/
|
||
│ └── schema-parser.test.ts
|
||
│
|
||
├── core/ # Ortak Rust modelleri
|
||
│ ├── Cargo.toml
|
||
│ └── src/
|
||
│ ├── lib.rs
|
||
│ └── models.rs # Template JSON serde modelleri (tum crate'ler kullanir)
|
||
│
|
||
├── layout-engine/ # Custom layout engine (taffy + cosmic-text)
|
||
│ ├── Cargo.toml
|
||
│ └── src/
|
||
│ ├── lib.rs # Public API: compute_layout()
|
||
│ ├── tree.rs # Template → taffy node tree
|
||
│ ├── sizing.rs # SizeValue → taffy Style mapping
|
||
│ ├── text_measure.rs # cosmic-text ile text olcum
|
||
│ ├── table_layout.rs # RepeatingTable → container agacina expand
|
||
│ ├── data_resolve.rs # Binding'leri cozumle (gercek text content uret)
|
||
│ ├── page_break.rs # Cok sayfali belgeler icin icerik bolme
|
||
│ ├── pdf_render.rs # LayoutResult → PDF (krilla, sadece native)
|
||
│ ├── wasm_api.rs # wasm_bindgen exports (loadFonts, computeLayout)
|
||
│ └── font.rs # Font yukleme (WASM fetch vs native file read)
|
||
│
|
||
├── backend/ # Rust + Axum
|
||
│ ├── Cargo.toml
|
||
│ ├── src/
|
||
│ │ ├── main.rs
|
||
│ │ ├── routes/
|
||
│ │ │ ├── mod.rs
|
||
│ │ │ ├── render.rs # POST /api/render → PDF (layout-engine kullanir)
|
||
│ │ │ └── health.rs # GET /api/health
|
||
│ │ └── models/
|
||
│ │ ├── mod.rs
|
||
│ │ ├── template.rs # Template JSON serde modelleri
|
||
│ │ └── schema.rs # JSON Schema modelleri
|
||
│ ├── fonts/ # Gomulu font dosyalari
|
||
│ │ ├── NotoSans-Regular.ttf
|
||
│ │ ├── NotoSans-Bold.ttf
|
||
│ │ ├── NotoSans-Italic.ttf
|
||
│ │ └── NotoSansMono-Regular.ttf
|
||
│ └── tests/
|
||
│ └── render_test.rs
|
||
│
|
||
└── shared/ # Ortak sema tanimlari
|
||
└── schemas/
|
||
├── fatura.schema.json
|
||
└── irsaliye.schema.json
|
||
```
|
||
|
||
---
|
||
|
||
## API Endpoints
|
||
|
||
### `POST /api/render`
|
||
|
||
Template JSON + Data JSON alir, PDF doner.
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"template": {},
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
**Response:** `Content-Type: application/pdf` — binary PDF
|
||
|
||
**Akis:**
|
||
|
||
1. Template + Data JSON parse edilir.
|
||
2. `compute_layout(template, data, fonts)` → `LayoutResult`
|
||
3. `render_pdf(layout_result, fonts)` → PDF bytes
|
||
4. Response olarak dondurulur.
|
||
|
||
### `GET /api/health`
|
||
|
||
Sunucu saglik kontrolu.
|
||
|
||
---
|
||
|
||
## Editor UI/UX Davranislari
|
||
|
||
### Eleman Ekleme
|
||
|
||
- **Arac kutusundan surukle-birak:** Container, statik metin, cizgi, gorsel, tekrarlayan tablo.
|
||
- **Container'a birakma:** Eleman hedef container'in flow'una eklenir. Drop pozisyonuna gore sira belirlenir.
|
||
- **Schema agacindan surukle-birak:** Scalar alan container'a birakilinca `text` elemani olusur, binding ayarlanir.
|
||
- **Schema'dan array alani surukle-birak:** `repeating_table` olusur, sutunlari ayarlama diyalogu acilir.
|
||
|
||
### Eleman Secimi ve Manipulasyon
|
||
|
||
- Tiklama ile secim → mavi kenarlik (container ise mor kenarlik) + resize handle'lar.
|
||
- **Flow elemanlar:** Suruklenemez (pozisyon otomatik). Sira degisikligi drag-to-reorder ile.
|
||
- **Absolute elemanlar:** Drag ile tasinir. Surukleme sirasinda CSS transform, birakinca layout engine re-compute.
|
||
- **Container secimi:** Tiklayinca sag panelde direction, gap, align, padding ayarlari.
|
||
- **Positioning modu degisikligi:** Sag panelde flow ↔ absolute gecisi.
|
||
- Delete/Backspace ile silme.
|
||
- Shift+tiklama ile coklu secim.
|
||
|
||
### Undo/Redo
|
||
|
||
- Template JSON uzerinde immutable snapshot stack.
|
||
- Ctrl+Z / Ctrl+Shift+Z.
|
||
|
||
### Zoom ve Pan
|
||
|
||
- Ctrl+scroll ile zoom.
|
||
- Space+drag veya orta fare tusu ile pan.
|
||
- Zoom araligi: %25 – %400.
|
||
|
||
---
|
||
|
||
## Roadmap
|
||
|
||
- [ ] Tablo stili ayarlari (header, zebra, border)
|
||
- [ ] Format fonksiyonlari (currency, date)
|
||
- [ ] `image` eleman tipi (statik + dinamik)
|
||
|
||
---
|
||
|
||
## Onemli Teknik Notlar
|
||
|
||
### Font Stratejisi
|
||
|
||
Layout engine (hem WASM hem native) cosmic-text ile text olcum yapar — bu nedenle font dosyalarina ihtiyac duyar. Font dosyalari projeye dahil edilmeli ve hem WASM'a hem backend'e yuklenmelidir. Baslangicta minimal bir set:
|
||
|
||
- Noto Sans (Regular, Bold, Italic, Bold Italic) — genel metin
|
||
- Noto Sans Mono (Regular) — tablo sayilari, monospace ihtiyaclari
|
||
- Toplam ~4-5 MB
|
||
|
||
**Kritik:** Backend'de (native Rust) ve frontend'de (WASM) birebir ayni font dosyalari kullanilmalidir. Farkli font = farkli metrik = layout uyumsuzlugu.
|
||
|
||
### Koordinat Sistemi
|
||
|
||
- Tum pozisyonlar **milimetre (mm)** cinsindendir.
|
||
- Template JSON'daki degerler mm, taffy'ye point'e cevrilerek verilir.
|
||
- LayoutResult'taki degerler mm cinsindendir.
|
||
- Editor canvas'ta mm → px donusumu: `px = mm * scale * zoomLevel`
|
||
- Referans: A4 = 210mm × 297mm.
|
||
|
||
### Hata Yonetimi
|
||
|
||
- Layout engine hatasi olursa → editorde kirmizi banner ile hata mesaji goster.
|
||
- Hesaplama basarisiz oldugunda son basarili LayoutResult'i koru, kullanicinin calsimasini bozma.
|
||
- Web Worker crash olursa → yeniden baslat, state'i koru.
|
||
|
||
### Eleman Sirasi (Z-Order)
|
||
|
||
- Template JSON'daki `children` dizisinin sirasi = cizim sirasi (sonraki ustte).
|
||
- Kullanici "One Getir" / "Arkaya Gonder" yapabilmeli → dizi sirasi degisir.
|
||
|
||
---
|
||
|
||
## Kod Stili ve Konvansiyonlar
|
||
|
||
### Frontend (TypeScript / Vue)
|
||
|
||
- Composition API + `<script setup>` kullan, Options API KULLANMA.
|
||
- Pinia store'lar `defineStore` ile.
|
||
- Tip guvenligi: `strict: true` tsconfig'de. `any` kullanma, gerekirse `unknown` + type guard.
|
||
- Composable isimlendirme: `useXxx` pattern.
|
||
- Bilesen isimleri: PascalCase, en az iki kelime (or: `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 yonetimi: `thiserror` ile typed errors, handler'larda `anyhow` kabul edilebilir.
|
||
- Layout engine dependency: `dreport-layout` crate.
|
||
- Clippy uyarilari temiz tutulacak.
|
||
|
||
### Genel
|
||
|
||
- Commit mesajlari: conventional commits (`feat:`, `fix:`, `refactor:`, `docs:` vs.).
|
||
- Turkce yorum yazilabilir, kod ve degisken isimleri Ingilizce.
|
||
- Template JSON field isimleri Ingilizce (or: `position`, `size`, `binding`).
|
||
- UI etiketleri ve kullaniciya gosterilen metinler Turkce.
|
||
|
||
---
|
||
|
||
## Kisitlamalar ve Bilincli Tercihler
|
||
|
||
1. **Veritabani yok (ilk asama).** Template'ler JSON dosyasi olarak import/export edilir.
|
||
2. **Kullanici auth yok.** Tek kullanicili yerel kullanim senaryosu.
|
||
3. **Sadece PDF cikti.** Ileride PNG/SVG eklenebilir.
|
||
4. **Tekrarlayan bolge (repeating region) yok — sadece tekrarlayan tablo.** Array binding yalnizca tablo bileseni ile yapilir. Serbest form repeating region ilerideki fazlarda degerlendirilir.
|
||
5. **WYSIWYG garantisi layout engine uzerinden.** Ayni layout engine (taffy + cosmic-text) hem editorde hem PDF'te kullanilir. Editor HTML div render, PDF krilla render — ama pozisyonlar ayni engine'den gelir.
|
||
6. **Canvas kutuphanesi (fabric.js / konva.js) kullanilmiyor.** Etkilesim katmani saf Vue bilesenleri + pointer event'ler ile yapilir. Render LayoutRenderer.vue ile HTML div'lerdir.
|