commit 9508f0faa4c7d01b97bbd550e9a4f97ff3417983
Author: Ondřej Váně <onry.vane@gmail.com>
Date:   Tue Apr 14 22:49:20 2020 +0200

    Re #7820: Vykreslení rámečku do výřezu dokumentu
    
    - implementována service pro vykreslení rámečku do obrázku
    - vykreslování pomocí html5 tagu canvas
    - nutno dopočítat posun oproti původnímu obrázku (velikost bude posílána z BE)

diff --git a/fe/fulltextsearch/src/Utils.ts b/fe/fulltextsearch/src/Utils.ts
index 91c554a..969811c 100644
--- a/fe/fulltextsearch/src/Utils.ts
+++ b/fe/fulltextsearch/src/Utils.ts
@@ -1,6 +1,28 @@
+import {Position} from './app/model/crates/Position';
+
 export default class Utils {
 
   static removeFileSuffix(file: File): string {
     return file.name.substring(0, file.name.indexOf('.'));
   }
+
+  static isIterable(obj): boolean {
+    // checks for null and undefined
+    if (obj == null) {
+      return false;
+    }
+    return typeof obj[Symbol.iterator] === 'function';
+  }
+
+  static getAbsolutePositionOfElement(element: HTMLElement): Position {
+    let top = 0, left = 0;
+
+    do {
+      top += element.offsetTop  || 0;
+      left += element.offsetLeft || 0;
+      element = <HTMLElement> element.offsetParent;
+    } while (element);
+
+    return new Position(top, left);
+  }
 }
diff --git a/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.html b/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.html
index 34fd272..9c54d37 100644
--- a/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.html
+++ b/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.html
@@ -8,7 +8,12 @@
             {{row.documentName}}
           </div>
           <div class="row image-div">
-            <img class="image" src="data:image/jpg;base64,{{row.imageCut}}" />
+            <img id="{{row.documentName + 'Img'}}"
+                 class="image" src="data:image/jpg;base64,{{row.imageCut}}"
+                 (load)="draw(row,row.documentName + 'Img' , row.documentName + 'Canvas' )"/>
+            <canvas id="{{row.documentName + 'Canvas'}}" style="position: absolute;" title="TEST">
+              Your browser does not support the HTML5 canvas tag.
+            </canvas>
           </div>
         </div>
       </td>
diff --git a/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.ts b/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.ts
index ca46a65..7303f2e 100644
--- a/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/search/search-paginator/search-paginator.component.ts
@@ -3,6 +3,9 @@ import { MatPaginator } from '@angular/material/paginator';
 import { MatTableDataSource } from '@angular/material/table';
 import { TextRegion } from '../../../../model/parts/TextRegion';
 import { SearchResponse } from '../../../../model/SearchResponse';
+import Utils from '../../../../../Utils';
+import {DrawerService} from '../../../../services/drawer/drawer.service';
+import {LoggerService} from '../../../../services/logger/logger.service';
 
 @Component({
   selector: 'app-search-paginator',
@@ -17,9 +20,15 @@ export class SearchPaginatorComponent implements OnChanges {
   /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
   displayedColumns = ['image'];
 
+  constructor(private LOGGER: LoggerService, private drawer: DrawerService) {}
+
   ngOnChanges(changes: SimpleChanges): void {
     this.tableDataSource = new MatTableDataSource<TextRegion>(this.inputData.textRegions);
     this.tableDataSource.paginator = this.paginator;
   }
 
+  draw(result: TextRegion, imageId: string, canvasId: string) {
+    this.LOGGER.debug('Drawing method');
+    this.drawer.drawResult(result, imageId, canvasId);
+  }
 }
diff --git a/fe/fulltextsearch/src/app/model/crates/Position.ts b/fe/fulltextsearch/src/app/model/crates/Position.ts
new file mode 100644
index 0000000..7c017c6
--- /dev/null
+++ b/fe/fulltextsearch/src/app/model/crates/Position.ts
@@ -0,0 +1,9 @@
+export class Position {
+  public top: number;
+  public left: number;
+
+  constructor(top: number, left: number) {
+    this.top = top;
+    this.left = left;
+  }
+}
diff --git a/fe/fulltextsearch/src/app/model/parts/DocumentSize.ts b/fe/fulltextsearch/src/app/model/parts/DocumentSize.ts
new file mode 100644
index 0000000..4d14044
--- /dev/null
+++ b/fe/fulltextsearch/src/app/model/parts/DocumentSize.ts
@@ -0,0 +1,9 @@
+export class DocumentSize {
+  public height: number;
+  public width: number;
+
+  constructor(height: number, width: number) {
+    this.height = height;
+    this.width = width;
+  }
+}
diff --git a/fe/fulltextsearch/src/app/model/parts/TextRegion.ts b/fe/fulltextsearch/src/app/model/parts/TextRegion.ts
index 70db417..53c4dd3 100644
--- a/fe/fulltextsearch/src/app/model/parts/TextRegion.ts
+++ b/fe/fulltextsearch/src/app/model/parts/TextRegion.ts
@@ -1,5 +1,6 @@
 import { Coords } from './Coords';
 import { TextLine } from './TextLine';
+import {DocumentSize} from './DocumentSize';
 
 export class TextRegion {
   public imageCut: string;
@@ -7,4 +8,5 @@ export class TextRegion {
   public regionText: string;
   public textLines: TextLine[];
   public documentName: string;
+  public documentSize: DocumentSize;
 }
diff --git a/fe/fulltextsearch/src/app/services/drawer/drawer.service.spec.ts b/fe/fulltextsearch/src/app/services/drawer/drawer.service.spec.ts
new file mode 100644
index 0000000..f428135
--- /dev/null
+++ b/fe/fulltextsearch/src/app/services/drawer/drawer.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { DrawerService } from './drawer.service';
+
+describe('DrawerService', () => {
+  let service: DrawerService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(DrawerService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/fe/fulltextsearch/src/app/services/drawer/drawer.service.ts b/fe/fulltextsearch/src/app/services/drawer/drawer.service.ts
new file mode 100644
index 0000000..a0868c4
--- /dev/null
+++ b/fe/fulltextsearch/src/app/services/drawer/drawer.service.ts
@@ -0,0 +1,86 @@
+import { Injectable } from '@angular/core';
+import {LoggerService} from '../logger/logger.service';
+import {TextRegion} from '../../model/parts/TextRegion';
+import {Point} from '../../model/parts/Point';
+import Utils from '../../../Utils';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DrawerService {
+
+  constructor(private LOGGER: LoggerService) { }
+
+  public drawResult(textRegion: TextRegion, imageId: string, canvasId: string) {
+    const canvasContext = this.initCanvas(imageId, canvasId);
+
+    if (textRegion !== null) {
+      if (Utils.isIterable(textRegion.textLines) && textRegion.textLines.length !== 0) {
+        for (const textLine of textRegion.textLines) {
+          if (Utils.isIterable(textLine.textWords) && textLine.textWords.length !== 0) {
+            for (const textWord of textLine.textWords) {
+              if (textWord !== null) {
+                // print words
+                this.draw(textWord.wordCoords.points, canvasContext);
+                this.LOGGER.info('printing words');
+              }
+            }
+            return;
+          } else {
+            // print lines
+            this.draw(textLine.lineCoords.points, canvasContext);
+            this.LOGGER.info('Print lines');
+          }
+        }
+        return;
+      } else {
+        // all region
+        this.draw(textRegion.regionCoords.points, canvasContext);
+        this.LOGGER.info('Print region');
+      }
+    } else {
+      // all document
+      // TODO : obarvit celý výřez
+      this.LOGGER.warn('Print all document');
+    }
+  }
+
+  private initCanvas(imageId: string, canvasId: string) {
+    const img = document.getElementById(imageId);
+    const canvasElement = document.getElementById(canvasId);
+    const canvas = <HTMLCanvasElement> document.getElementById(canvasId);
+
+    const absolutePosition = Utils.getAbsolutePositionOfElement(document.getElementById(imageId));
+
+    canvasElement.style.display = 'block';
+    canvasElement.style.left = absolutePosition.left + 'px';
+    canvasElement.style.top = absolutePosition.top + 'px';
+
+    const ctx = canvas.getContext('2d');
+    ctx.canvas.width = img.offsetWidth;
+    ctx.canvas.height = img.offsetHeight;
+
+    return ctx;
+  }
+
+  private draw(points: Point[], context: CanvasRenderingContext2D) {
+    let firstPoint = true;
+    context.beginPath();
+
+    for (const point of points) {
+      if (firstPoint) {
+        context.moveTo(point.x / 1.676, point.y / 1.676);
+        firstPoint = false;
+      } else {
+        context.lineTo(point.x / 1.676, point.y / 1.676);
+      }
+    }
+    context.closePath();
+
+    context.lineWidth = 2;
+    context.strokeStyle = '#ff253b';
+    context.fillStyle = 'hsla(14, 100%, 53%, 0.3)';
+    context.fill();
+    context.stroke();
+  }
+}
