18 KiB
PDF Editor - Database Schema & Data Flow
Database Table: annotations
Location
- Migration File:
database/migrations/2025_09_29_104240_create_annotations_table.php - Updated Migration:
database/migrations/2025_10_16_131634_update_annotations_shape_type_enum.php
Table Structure
CREATE TABLE annotations (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
pdf_file VARCHAR(255),
page_number INT,
shape_type ENUM('rect', 'circle', 'triangle', 'spool', 'weld', 'shop_weld', 'leader_line', 'text'),
x_coordinate DECIMAL(10,2),
y_coordinate DECIMAL(10,2),
width DECIMAL(10,2),
height DECIMAL(10,2),
shape_id VARCHAR(100),
spool_no VARCHAR(100),
joint_type TEXT,
classification VARCHAR(50),
created_by BIGINT,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
Field Descriptions
| Field | Type | Description | Used For |
|---|---|---|---|
id |
BIGINT | Primary key | Unique identifier |
pdf_file |
VARCHAR(255) | PDF file path | Linking annotations to specific PDFs |
page_number |
INT | Page number | Multi-page PDF support |
shape_type |
ENUM | Type of annotation | rect, circle, triangle, spool, weld, shop_weld, leader_line, text |
x_coordinate |
DECIMAL(10,2) | X position | Canvas coordinate (for shapes: top-left, for lines: start point) |
y_coordinate |
DECIMAL(10,2) | Y position | Canvas coordinate |
width |
DECIMAL(10,2) | Width/Delta X | Shape width or line X distance |
height |
DECIMAL(10,2) | Height/Delta Y | Shape height or line Y distance |
shape_id |
VARCHAR(100) | Unique shape ID | Cross-referencing (leader lines to shapes, QR codes) |
spool_no |
VARCHAR(100) | Spool number | Business data for welding |
joint_type |
TEXT | Joint type or text content | Business data for welding OR free text content |
classification |
VARCHAR(50) | Annotation category | 'shape_text', 'free_text', etc. |
created_by |
BIGINT | User ID | Audit trail |
created_at |
TIMESTAMP | Creation time | Audit trail |
updated_at |
TIMESTAMP | Update time | Audit trail |
Special Field Usage
Leader Lines
shape_type: 'leader_line'x_coordinate,y_coordinate: Start point (click point)width: Delta X (x2 - x1)height: Delta Y (y2 - y1)- Additional metadata stored in backend:
connectedShapeId: The shape this line connects tooriginalStartPoint: Original click coordinates for reconnection
Text Annotations
shape_type: 'text'joint_type: Contains the actual text contentclassification:- 'shape_text': Text inside shapes (red, bold, 28px)
- 'free_text': Standalone notes (black, normal, 16px)
Shapes (rect, circle, triangle, etc.)
- Standard coordinate and dimension fields
spool_no: Business identifier - populated from Spool Number dropdown- Rectangle shapes: Display spool_no as text inside shape
- Source: MTO (Material Take-Off) data or manually generated
- Dropdown ID:
#spool-select
joint_type: Joint/Weld classification - populated from Weld Number dropdown- Triangle/Circle shapes: Display joint_type (weld number) as text inside shape
- Source: MTO welders data or manually entered
- Dropdown ID:
#weld-select
shape_id: Used for QR code generation and leader line connection
Shape Type → Data Mapping:
- Rectangle → Uses
spool_nofor text content - Triangle → Uses
joint_type(weld number) for text content - Circle → Uses
joint_type(weld number) for text content
Data Population System
Spool Number (spool_no field)
What is it?
Spool number is a unique identifier for a spool in piping/welding systems. In the PDF Editor, this value is used to label rectangle shapes.
How it's populated:
-
From MTO Data (Material Take-Off):
// Function: populateMtoForms() // Populates dropdown from mtoData.spools array spoolSelect.innerHTML = '<option value="">Select Spool</option>'; mtoData.spools.forEach(spool => { option.value = spool.spool_number; // e.g., "SP001" option.textContent = spool.spool_number; }); -
Auto-Generate:
- Button: "Generate" next to Spool Number dropdown
- API Call:
GET /api/mto/spools/generate-number - Returns: New unique spool number (e.g., "SP123")
-
Manual Selection:
- User selects from dropdown:
<select id="spool-select"> - Value is stored when shape is created
- User selects from dropdown:
When it's used:
- Rectangle shapes: Display spool_no inside the shape as red text
- Shape creation: Value is read from dropdown and assigned to
shape.spoolNo - Database save: Stored in
annotations.spool_nocolumn
Code Flow:
// 1. User selects spool from dropdown
<select id="spool-select">
<option value="SP001">SP001</option>
<option value="SP002">SP002</option>
</select>
// 2. User draws rectangle with leader line
const spoolSelect = document.getElementById('spool-select');
const spoolNo = spoolSelect.value; // "SP001"
// 3. Shape is created with spool number
shape.set('spoolNo', spoolNo);
// 4. Text is added inside rectangle
textContent = (spoolNo && spoolNo.trim() !== '') ? spoolNo : 'N/A';
// Result: "SP001" displayed in red inside rectangle
// 5. Save to database
annotation = {
spool_no: obj.spoolNo, // "SP001"
shape_type: 'rect',
...
};
Weld/Joint Number (joint_type field)
What is it?
Weld/Joint number is a unique identifier for a weld joint in piping systems. In the PDF Editor, this value is used to label triangle and circle shapes.
How it's populated:
-
From MTO Welders Data:
// Function: populateMtoForms() // Populates dropdown from mtoData.welders array weldSelect.innerHTML = '<option value="">Select Weld</option>'; mtoData.welders.forEach(welder => { option.value = welder.welder_code; // e.g., "W001" option.textContent = welder.welder_code; }); -
Auto-Generate:
- Button: "Generate" next to Weld Number dropdown
- API Call:
GET /api/mto/joints/generate-number?spool_no={spoolNo} - Returns: New joint number based on spool (e.g., "J-SP001-001")
-
Manual Selection:
- User selects from dropdown:
<select id="weld-select"> - Value is stored when shape is created
- User selects from dropdown:
When it's used:
- Triangle shapes: Display joint_type inside the shape as red text
- Circle shapes: Display joint_type inside the shape as red text
- Shape creation: Value is read from dropdown and assigned to
shape.weldNo - Database save: Stored in
annotations.joint_typecolumn
Code Flow:
// 1. User selects weld from dropdown
<select id="weld-select">
<option value="W001">W001</option>
<option value="W002">W002</option>
</select>
// 2. User draws triangle with leader line
const weldSelect = document.getElementById('weld-select');
const weldNo = weldSelect.value; // "W001"
// 3. Shape is created with weld number
shape.set('weldNo', weldNo);
// 4. Text is added inside triangle
textContent = (weldNo && weldNo.trim() !== '') ? weldNo : 'N/A';
// Result: "W001" displayed in red inside triangle
// 5. Save to database
annotation = {
joint_type: obj.weldNo, // "W001"
shape_type: 'triangle',
...
};
Current Status Issue
Problem: spool_no field is currently empty in database
Possible Reasons:
- MTO Data Not Loaded:
mtoData.spoolsarray is empty or not fetched - Dropdown Not Selected: User is creating shapes without selecting spool/weld number
- Sample Data: System falls back to sample data (SP001, SP002, SP003) if MTO data is missing
- API Integration: MTO API endpoints might not be returning data
How to Fix:
- Check MTO data loading in browser console
- Ensure dropdown has options before creating shapes
- Select spool/weld number from dropdown before drawing
- Verify API endpoints are working:
/api/mto/spools/generate-number/api/mto/joints/generate-number
Testing:
// Open browser console on PDF Editor page
console.log('MTO Data:', mtoData);
console.log('Spools:', mtoData.spools);
console.log('Welders:', mtoData.welders);
// Check dropdown values
const spoolSelect = document.getElementById('spool-select');
console.log('Spool options:', Array.from(spoolSelect.options).map(o => o.value));
API Endpoints
1. Save/Sync Annotations
Endpoint: POST /api/pdf-editor/annotations/sync
Controller: app/Http/Controllers/Api/AnnotationController.php → syncAnnotations()
Request Body:
{
"pdf_file": "path/to/file.pdf",
"page_number": 1,
"annotations": [
{
"shape_id": "shape-1729612345678",
"spool_no": "SP-001",
"joint_type": "Butt Weld",
"classification": "",
"page_number": 1,
"x_coordinate": 100,
"y_coordinate": 200,
"width": 140,
"height": 70,
"shape_type": "rect",
"pdf_file_path": "path/to/file.pdf",
"created_by": 1
},
{
"shape_id": "leader-1729612345680",
"shape_type": "leader_line",
"x_coordinate": 150,
"y_coordinate": 180,
"width": 0,
"height": 50,
"connectedShapeId": "shape-1729612345678",
"originalStartPoint": {"x": 150, "y": 180}
}
]
}
Response:
{
"success": true,
"message": "Annotations synced successfully",
"data": {
"deleted": 2,
"created": 5
}
}
Logic:
- Deletes all existing annotations for the specific PDF page
- Inserts all new annotations from the request
- Transaction-based for data integrity
2. Load Annotations
Endpoint: GET /api/pdf-editor/annotations?pdf_file={path}&page_number={num}
Controller: app/Http/Controllers/Api/AnnotationController.php → index()
Response:
{
"success": true,
"data": [
{
"id": 123,
"pdf_file": "path/to/file.pdf",
"page_number": 1,
"shape_type": "triangle",
"x_coordinate": 300,
"y_coordinate": 400,
"width": 140,
"height": 140,
"shape_id": "shape-1729612345690",
"spool_no": "SP-002",
"joint_type": "Fillet",
"classification": "",
"created_by": 1,
"created_at": "2025-10-22 10:30:00",
"updated_at": "2025-10-22 10:30:00"
}
]
}
3. Generate QR Code
Endpoint: GET /api/pdf-editor/qr-code/{shapeId}
Controller: app/Http/Controllers/Api/AnnotationController.php → generateQRCode()
Response: PNG image data (base64 or binary)
Used For: Generating QR codes for shape annotations
Data Flow Diagram
┌─────────────────────────────────────────────────────────────┐
│ PDF EDITOR FRONTEND │
│ (pdf-editor.blade.php) │
└─────────────────────────────────────────────────────────────┘
│
│ User draws shapes,
│ adds text, creates
│ leader lines
▼
┌─────────────────────────────────────────────────────────────┐
│ FABRIC.JS CANVAS │
│ - Shapes (rect, circle, triangle) │
│ - Leader Lines (connecting click point to shape edge) │
│ - Text Objects (free text notes + shape text) │
│ - QR Codes (linked to shapes) │
└─────────────────────────────────────────────────────────────┘
│
│ Save button clicked
▼
┌─────────────────────────────────────────────────────────────┐
│ saveCurrentPageAnnotations() │
│ - Iterates through all canvas objects │
│ - Skips QR codes (not saved) │
│ - Converts shapes, lines, text to annotation format │
│ - Prepares JSON payload │
└─────────────────────────────────────────────────────────────┘
│
│ POST /api/pdf-editor/annotations/sync
▼
┌─────────────────────────────────────────────────────────────┐
│ API: AnnotationController@syncAnnotations │
│ 1. Validates request data │
│ 2. Starts database transaction │
│ 3. Deletes old annotations for this page │
│ 4. Inserts new annotations │
│ 5. Commits transaction │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATABASE TABLE │
│ `annotations` │
│ - All annotations stored with coordinates, types, metadata │
└─────────────────────────────────────────────────────────────┘
│
│ Page reload or PDF selection
│ GET /api/pdf-editor/annotations
▼
┌─────────────────────────────────────────────────────────────┐
│ API: AnnotationController@index │
│ - Fetches annotations for specified PDF + page │
│ - Returns JSON array │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ loadExistingAnnotations() │
│ - Receives annotation data from API │
│ - Calls loadAnnotationToCanvas() for each │
│ - Recreates shapes, lines, text on canvas │
│ - Calls reconnectLeaderLines() to re-establish connections │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ FABRIC.JS CANVAS │
│ - All annotations restored │
│ - Leader lines connected to shapes │
│ - QR codes regenerated via API │
└─────────────────────────────────────────────────────────────┘
Key Implementation Details
Triangle Leader Line Connection
- Challenge: Triangles have different geometry than rectangles/circles
- Solution: Custom
calculateEdgePoint()function insetupConnectedMovement()- Calculates triangle vertices and edges
- Determines closest edge/vertex based on original click position
- Connects leader line to appropriate edge point
- Handles rotation, scaling, and movement events
Unique Shape IDs
- Generated using
Date.now()to ensure uniqueness across page reloads - Format:
shape-{timestamp},leader-{timestamp},text-{timestamp} - Prevents database conflicts when saving new annotations
Text Handling
- Shape Text: Red, bold, 28px, positioned inside shapes, non-selectable
- Free Text: Black, normal, 16px, standalone notes, selectable
- Both saved to same table with different
classificationvalues
Leader Line Storage
- Start point stored as
x_coordinate,y_coordinate - End point calculated using
widthandheightdeltas - Metadata (
connectedShapeId,originalStartPoint) preserved for reconnection
Validation Rules
// app/Http/Controllers/Api/AnnotationController.php
'annotations' => 'required|array',
'annotations.*.shape_type' => 'required|in:rect,circle,triangle,spool,weld,shop_weld,leader_line,text',
'annotations.*.x_coordinate' => 'required|numeric',
'annotations.*.y_coordinate' => 'required|numeric',
'annotations.*.width' => 'nullable|numeric',
'annotations.*.height' => 'nullable|numeric',
'annotations.*.shape_id' => 'required|string',
'annotations.*.page_number' => 'required|integer',
Future Enhancements
- Add annotation history/versioning
- Implement collaborative editing with user tracking
- Add annotation comments/notes
- Support for more shape types
- Advanced leader line routing (avoid overlaps)