; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: Text drawing library.
;
;  Target OS: Any
;
;  Dependencies: StrLib
;
;  Notes: This file contains OS independent part of the library and includes respective
;         OS dependent files.
;_________________________________________________________________________________________
module "Text library"


; dtfXXX means "draw text flag"

dtfHAlignMask = $3
dtfVAlignMask = $c


; horizontal align
dtfAlignLeft    = 0
dtfAlignRight   = 1
dtfAlignCenter  = 2
dtfAlignJustify = 3


; vertical align
dtfAlignTop    = 0 shl 2
dtfAlignBottom = 1 shl 2
dtfAlignMiddle = 2 shl 2


; Text layout
dtfCRLF         = $0100
dtfWordWrap     = $0200
dtfTableTabs    = $0400
dtfSingleLine   = $0800



struct TTextExtent
  .width  dd ?
  .height dd ?
  .OffsX  dd ?
  .OffsY  dd ?
ends

struct TCharAttr
  .font       dd ?
  .color      dd ?
  .background dd ?
  .flags      dd ?      ; for free use of the client.
ends


struct TChunkFormat
  .length dd ?
  .font   dd ?
  .color  dd ?
  .flags  dd ?
ends


struct TChunkColor
  .length dd ?
  .color  dd ?
  .bkgnd  dd ?
  .flags  dd ?
ends



struct _TTextChunk
  .y      dd ?
  .width  dd ?
  .len    dd ?
  .ptr    dd ?
ends



struct TTextChar
  .code   dd ?           ; unicode of the character.
  .width  dw ?           ; width increment in pixels.
  .height dw ?           ; height increment in pixels - if <> 0 this char must be written on new line.
ends





interface DrawString, .pImage, .pString, .len, .x, .y, .font, .color


interface TextDecompose, .hString, .font


interface DrawDecomposedString, .pImage, .pArray, .x, .y, .font, .color


interface GetTextBounds, .pString, .len, .font


; Draws a rectangle of TText object with monospaced font.
;
; .pImage        - where to draw.
; .pText         - pointer to TText structure.
;
; .pColors       - pointer to TArray of TChunkColor to be used for drawing.
; .pFont         - font to be used. Must be monospaced.
; .wChar, .hChar - width and height of the font characters.
;
; .xOrg, .yOrg   - the upper left corner in pixels of the whole text.
; .xFrom, .yFrom - the upper left corner in characters from where to draw.
; .width, height - the width and height in characters of the rectangle to draw.

interface DrawFormattedMonoText, .pImage, .pText, .pColors, .pFont, .wChar, .hChar, .xOrg, .yOrg, .xFrom, .yFrom, .width, .height





proc AdjustCountUtf8
begin
        push    esi
        lea     esi, [esi+ebx]
        stdcall ScanBackUtf8
        mov     ebx, esi
        pop     esi
        sub     ebx, esi
        return
endp



proc JustifyLine, .CharArray, .from, .to, .current_width, .align_width
begin
        pushad

        dec     [.to]   ; don't change the last character width.

        mov     edi, [.CharArray]
        mov     ecx, [.to]
        sub     ecx, [.from]
        jle     .finish

        mov     edx, [.align_width]
        sub     edx, [.current_width]
        jle     .finish

        mov     esi, edx

        mov     ebx, [.from]

.align_loop:
        inc     ebx
        cmp     ebx, [.to]
        jae     .finish

        cmp     word [edi+TArray.array+8*ebx+TTextChar.width], 0
        je      .skip_this

.sub:
        test    esi, esi
        js      .negative

        sub     esi, ecx
        inc     word [edi+TArray.array+8*ebx+TTextChar.width]
        jmp     .sub

.negative:
        add     esi, edx
        jmp     .align_loop

.skip_this:
        dec     ecx
        jnz     .align_loop

.finish:
        popad
        return
endp




proc DrawTextBox, .pImage, .text, .x, .y, .width, .height, .extra_line, .flags, .font, .color

.ascender   dd ?
.descender  dd ?

.txt_height dd ?

.last_word  dd ?
.lw_width   dd ?

.no_just    dd ?

begin

        pushad

        stdcall TextDecompose, [.text], [.font]
        mov     edi, eax
        mov     [.ascender], edx
        mov     [.descender], ecx

        add     [.y], edx

        xor     ecx, ecx
        mov     [.txt_height], ecx

        cmp     [edi+TArray.count], ecx
        je      .finish

.main_loop:                             ; start of new line
        xor     ebx, ebx                ; current X coordinate.
        or      [.last_word], -1        ; end of the word -1 means there is no word from the beginning of the line.

        mov     eax, [.txt_height]
        cmp     eax, [.height]
        jb      .format_it

        mov     eax, [.extra_line]
        sub     [.txt_height], eax

        mov     [edi+TArray.count], ecx
        jmp     .draw_all2


.format_it:
        stdcall InsertArrayItems, edi, ecx, 1
        mov     edi, edx
        mov     [eax+TTextChar.code], $20
        mov     dword [eax+TTextChar.width], ebx        ; Set to zero TTextChar.height as well!

        mov     esi, ecx       ; start of the line!
        inc     ecx

.slice_loop:

; Is it end of a word?
        cmp     [edi+TArray.array+8*ecx+TTextChar.code], $20
        ja      .white_ok

        test    ecx, ecx
        jz      .white_ok

        cmp     [edi+TArray.array+8*ecx-8+TTextChar.code], $20  ; previous character
        jbe     .white_ok

        mov     [.last_word], ecx        ; edx is a pointer to the end of the last word.
        mov     [.lw_width], ebx

.white_ok:
        test    [.flags], dtfCRLF
        jz      .crlf_ok

        mov     [.no_just], 1

        cmp     [edi+TArray.array+8*ecx+TTextChar.code], $0d
        je      .new_line

        cmp     [edi+TArray.array+8*ecx+TTextChar.code], $0a
        je      .new_line

.crlf_ok:
        mov     [.no_just], 0

        test    [.flags], dtfTableTabs
        jz      .tabs_ok

; implement here check for tab and tabs expanding.

.tabs_ok:

; check the current line width

        movzx   eax, word [edi+TArray.array+8*ecx+TTextChar.width]
        add     eax, ebx

        cmp     eax, [.width]
        ja      .wider

        mov     ebx, eax

        inc     ecx
        cmp     ecx, [edi+TArray.count]
        jb      .slice_loop

        mov     [.no_just], 1
        call    .RowAlign
        jmp     .draw_all


.wider:
        test    [.flags], dtfSingleLine
        jnz     .del_remaining

        mov     eax, ecx
        sub     eax, esi
        cmp     eax, 1
        jbe     .finish         ; Even single char can not be displayed on this [.width]

; next line
.new_line:

        test    [.flags], dtfWordWrap
        jz      .word_ok

; word wrap
        cmp     [.last_word], 0
        jl      .word_ok       ; there is no previous word, so split on the current pos.

        mov     ecx, [.last_word]
        mov     ebx, [.lw_width]

.word_ok:
        call    .RowAlign

        mov     eax, [edi+TArray.lparam]                                ; contains the line height computed by DecomposeText
        add     eax, [.extra_line]

        add     [.txt_height], eax
        mov     word [edi+TArray.array+8*ecx-8+TTextChar.height], ax

        test    [.flags], dtfCRLF
        jz      .skip_ws_loop

        mov     eax, [edi+TArray.array+8*ecx+TTextChar.code]
        cmp     eax, $0d
        je      .skip_cr

        cmp     eax, $0a
        jne     .skip_ws_loop

.skip_cr:
        mov     word [edi+TArray.array+8*ecx+TTextChar.width], 0     ; zero width, skip it.
        inc     ecx
        cmp     ecx, [edi+TArray.count]
        je      .draw_all

        xor     eax, $0d xor $0a
        cmp     [edi+TArray.array+8*ecx+TTextChar.code], eax
        jne     .main_loop

        mov     word [edi+TArray.array+8*ecx+TTextChar.width], 0     ; zero width, skip it.

        inc     ecx
        cmp     ecx, [edi+TArray.count]
        jae     .draw_all
        jmp     .main_loop

.del_remaining:

        mov     eax, [edi+TArray.count]
        sub     eax, ecx
        stdcall DeleteArrayItems, edi, ecx, eax
        jmp     .draw_all


.skip_ws_loop:
        cmp     [edi+TArray.array+8*ecx+TTextChar.code], $20
        ja      .main_loop

        mov     word [edi+TArray.array+8*ecx+TTextChar.width], 0     ; zero width, skip it.

        inc     ecx
        cmp     ecx, [edi+TArray.count]
        jb      .skip_ws_loop


.draw_all:
; compute the vertical align.

        mov     eax, [edi+TArray.lparam]
        add     [.txt_height], eax

.draw_all2:
        mov     ecx, [.height]
        sub     ecx, [.txt_height]

        mov     eax, [.flags]
        and     eax, dtfVAlignMask

        cmp     eax, dtfAlignTop
        je      .drawit

        add     [.y], ecx

        cmp     eax, dtfAlignBottom
        je      .drawit

        sar     ecx, 1
        sub     [.y], ecx

.drawit:
        stdcall DrawDecomposedString, [.pImage], edi, [.x], [.y], [.font], [.color]


.finish:
        stdcall FreeMem, edi
        popad
        return




.RowAlign:
        mov     eax, [.flags]
        and     eax, dtfHAlignMask

        cmp     eax, dtfAlignLeft
        je      .align_ok               ; no need for extra space

        cmp     eax, dtfAlignJustify
        jne     .align_center_right

        cmp     [.no_just], 1
        je      .align_ok

        stdcall JustifyLine, edi, esi, ecx, ebx, [.width]
        jmp     .align_ok

.align_center_right:

        mov     edx, [.width]
        sub     edx, ebx
        cmp     eax, dtfAlignRight
        je      .set_align

        sar     edx, 1

.set_align:
        mov     word [edi+TArray.array+8*esi+TTextChar.width], dx

.align_ok:
        retn

endp





proc DrawStringFormatted, .pImage, .pString, .len, .x, .y, .pFormat
begin
        pushad

        mov     esi, [.pString]
        mov     edi, [.pFormat]
        mov     ecx, [.len]
        test    ecx, ecx
        jns     .loop

        stdcall StrLen, esi
        mov     ecx, eax

.loop:
        mov     eax, [edi+TChunkFormat.length]
        test    eax, eax
        jns     @f

        stdcall StrLen, esi
@@:
        stdcall DrawString, [.pImage], esi, eax, [.x], [.y], [edi+TChunkFormat.font], [edi+TChunkFormat.color]

        add     [.x], eax

        mov     eax, [edi+TChunkFormat.length]
        test    eax, eax
        js      .finish

        add     edi, sizeof.TChunkFormat
        add     esi, eax
        sub     ecx, eax
        jae     .loop

.finish:
        popad
        return
endp








include "%TargetOS%/text.asm"




endmodule
