• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] TextSplat2

Level 19
Joined
Mar 18, 2012
Messages
1,716
You may be aware of TextSplat. It's a great alternative to texttag handles.

As THW user there were a few things I disliked in TextSplat:
  • Does not work with Bribes Table
  • Suspended/permanent TextSplats keep the periodic timer running.

I worked myself into TextSplat and changed the code, so it works
with our Table and also stops the timer if the textsplat is permanent.

At the moment it's not fully backwards compatible ( will do that ).

I was so free to increase the allocation speed of textsplats.

JASS:
//**
//*    Credits:
//*        - Vexorian   ( ARGB )
//*        - Nestharus  ( ErrorMessage  )
//*        - Bribe      ( Table )
//*        - PitzerMike ( original TextSplat system )
//*        - Deaod      ( TextSplat system of the second generation )
//**
//* Written by BPower
library TextSplat2 /*v2.0
*************************************************************************************
*
*   Creates objects of type "textsplat", which are strings displayed in a sequence of images.
*   In API textsplats have a remarkable resemblance to the texttag handle. 
*   Only the creation of a textsplat requires an extra argument ( a font ).
*
*************************************************************************************
*
*   */ requires /*
* 
*       */ ARGB       /*            wc3c.net/showthread.php?t=101858
*       */ Font       /*            hiveworkshop.com/forums/submissions-414/textsplat2-273717/
*       */ Table      /*            hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       */ Queue      /*            github.com/nestharus/JASS/blob/master/jass/Data%20Structures/Queue/script.j
*       */ optional ImageUtils   /* wc3c.net/showthread.php?t=107707
*       */ optional ImageTools   /* hiveworkshop.com/forums/submissions-414/imagetools-271099/
*       */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
*
************************************************************************************/
//**
//* Import instruction:
//* ===================
    //*  Copy the TextSplat2, Font and their requirements into your map.
//**
//*  API:
//*  ====
    //*  The API is similar to the texttag API with one exception.
    //* "CreateTextsplat(font)" expects a font as argument, while CreateTextTag() doesn't require any argument.
    //* Below the object orientated API is listed, as it offers some cool feature texttags don't have.
//! novjass ( Disables the compiler until the next endnovjass )
   
    struct textsplat
    //**
    //*  Methods:
    //*  ========
        static method create takes font f returns textsplat
        method destroy       takes nothing returns nothing

        method setText takes string s, real height, integer aligntype returns nothing
        //*  Available aligntype are TEXTSPLAT_TEXT_ALIGN_LEFT, TEXTSPLAT_TEXT_ALIGN_CENTER and TEXTSPLAT_TEXT_ALIGN_RIGHT
        //*
        //* Hint: You can bind images into a text ( if added to the font ) by using "|i" as identifier.
        //* Example:  "The price is 2000 |igold|i"
        //* Output: Textsplat will translate "|igold|i" into the gold coin image.
       
        method setVelocity  takes real xvel, real yvel, real zvel returns nothing
        method setColor     takes integer red, integer green, integer blue, integer alpha returns nothing
        method setPosition  takes real x, real y, real z return nothing
        method setPosUnit   takes unit u, real z returns nothing
        method setSuspended takes boolean flag returns nothing
        method setVisible   takes boolean flag returns nothing
    //**
    //*  Fields you may manipulate directly:
    //*  ===================================
        font    fontType
        real    age
        real    lifespan
        real    fadepoint
        boolean permanent
    //**
    //*  Fields you may read:
    //*  ====================
        readonly integer charCount
        //*  Position values.
        readonly real    x
        readonly real    y
        readonly real    z
        //*  Velocity values.
        readonly real    dX
        readonly real    dY
        readonly real    dZ
        //*  Width and height of a text are very useful.
        readonly real    width
        readonly real    height
        readonly boolean suspended
        readonly boolean visible
        readonly string  text
        readonly ARGB    color
//! endnovjass
//**
//*  User configuration:
//*  ===================
    globals
        //*  Set the timer accuracy.
        private constant real    ACCURACY           = 1./32.
        //*  Set the default color for textsplats. If unsure use 0xFFFFFFFF.
        private constant integer DEFAULT_COLOR      = 0xFFFFFFFF
        //*  Set the default image type for textsplats.
        private constant integer DEFAULT_IMAGE_TYPE = IMAGE_TYPE_SELECTION
        //*  Set how many chars can be parsed per function evaluation.
        //* Use lower values if you use many |cAARRGGBB strings in textsplats. Maximum working value is about 65.
        private constant    integer TOKENS_PER_CHUNK = 40
//**
//*  Globals you can read:
//*  =====================
    //* ( Do not change these global values )
        constant integer DEFAULT_IMAGE_SIZE          = 32
        constant integer TEXTSPLAT_TEXT_ALIGN_LEFT   = 0
        constant integer TEXTSPLAT_TEXT_ALIGN_CENTER = 1
        constant integer TEXTSPLAT_TEXT_ALIGN_RIGHT  = 2
        constant real    TEXT_SIZE_TO_IMAGE_SIZE     = 4.146479
    endglobals
//========================================================================
//*  Textsplat2 system code. Make changes carefully.
//========================================================================   
//**
//*  Terrain offset z:
//*  =================
    //*  Native SetImageConstantHeight() expects an absolute z value.
    globals
        private constant location LOC = Location(0, 0)
    endglobals
    private function GetLocZ takes real x, real y returns real
        call MoveLocation(LOC, x, y)
        return GetLocationZ(LOC)
    endfunction
//**
//*  Struct Char:
//*  ============
    //*  The data struture is a queue. You can replace the module
    //* with any data structure module which supports
    //* create(), destroy(), enqueue() and clear()
    private struct Char extends array
        implement Queue
        //*  Struct members.
        image   img
        integer imageType
        string  path
        real    sizeX
        real    sizeY
        real    x
        real    y
        real    z
        boolean show
        boolean colorize//*  Non char images are not colorized.
        integer alpha
        integer red
        integer blue
        integer green
       
        private method draw takes nothing returns nothing
            if (img != null) then
                call ReleaseImage(img)
            endif
            static if LIBRARY_ImageTools then
                set img = NewImage(path, sizeX, sizeY, x, y, 0, imageType)
            elseif LIBRARY_ImageUtils then
                set img = NewImage(path, sizeX, sizeY, 0, x, y, 0, 0, 0, 0, imageType)
            endif
            call SetImageConstantHeight(img, true, z + GetLocZ(x, y))
            call SetImageColor(img, red, green, blue, alpha)
            call SetImageRenderAlways(img, show)           
        endmethod
       
        method setColor takes ARGB value returns nothing
            set alpha = value.alpha
            set red   = value.red
            set green = value.green
            set blue  = value.blue
            call SetImageColor(img, red, green, blue, alpha)
        endmethod
       
        private method new takes string strPath, real sX, real sY, real posX, real posY, real posZ returns thistype
            local ARGB val = DEFAULT_COLOR
            set this = enqueue()//*  New node.
            set path = strPath
            set sizeX = sX
            set sizeY = sY
            set x = posX
            set y = posY
            set z = posZ
            set show = true
            set imageType = DEFAULT_IMAGE_TYPE
            set alpha = val.alpha
            set red = val.red
            set green = val.green
            set blue = val.blue
            call draw()
            return this
        endmethod
       
        method createChar takes font ft, string symbol, real x, real y, real z, real size returns thistype
            return new(ft.getChar(symbol).path, size*TEXT_SIZE_TO_IMAGE_SIZE, size*TEXT_SIZE_TO_IMAGE_SIZE, x, y, z)
        endmethod
       
        method createImage takes font ft, string img, real x, real y, real z, real size returns thistype
            return new(ft.getImage(img).path, size*TEXT_SIZE_TO_IMAGE_SIZE, size*TEXT_SIZE_TO_IMAGE_SIZE, x, y, z)
        endmethod
    endstruct
   
    globals
        //*  Globals to transfer variable values between functions, which use
        //* either function.evaluate or ForForce to not hit the OOP limit.
        private integer strLength
        private integer currentLength 
        private integer tokenAmount
        private real    maxLineWidth
        private integer currentLine
        private boolean defaultColor
        private ARGB    currentColor
        private real    sourceX
        private real    sourceY
        //*
        private string array textChars
        private real   array lineWidth
    endglobals
//**
//*  Hex to Decimal:
//*  ===============
    globals
        private Table Hex2Dec
        private Table IsHex  
    endglobals
   
    private function IsHexadecimal takes integer i returns boolean
        local integer l = i + 8
        loop
            exitwhen (i >= l)
            if not IsHex.has(StringHash(textChars[i])) then
                return false
            endif
            set i = i + 1
        endloop
        return true
    endfunction
//**
//*  Perpare source string:
//*  ======================
    private function SplitString takes string text returns nothing
        local integer i = 0
        loop
            exitwhen (i == strLength)
            set textChars[i] = SubString(text, i, i + 1)
            set i = i + 1
        endloop
        set textChars[i] = null//*  Will indicate the end of the text.
    endfunction
//**
//*  String parsing:
//*  ===============
    globals
        private constant integer TOKEN_TYPE_INVALID  = 0
        private constant integer TOKEN_TYPE_NORMAL   = 1
        private constant integer TOKEN_TYPE_NEWLINE  = 2
        private constant integer TOKEN_TYPE_COLOR    = 3
        private constant integer TOKEN_TYPE_COLOREND = 4
        private constant integer TOKEN_TYPE_IMAGE    = 5
    endglobals
   
    private struct TextToken extends array
        static thistype current
        integer tokenType
        string  value
        ARGB    color
        boolean isDefaultColor
        integer line
        real    posX
        real    posY
    endstruct
   
    private function TokenizeText takes nothing returns nothing
        local integer   i     = currentLength
        local TextToken base  = TextToken.current
        local TextToken token = base
        local string    char
        loop
            exitwhen (integer(token) - integer(base) >= TOKENS_PER_CHUNK) or (i >= strLength)
            set char = textChars[i]
           
            //*  Check for \n or \r\n or |n
            if (char == "\n") or (char == "\r" and textChars[i + 1] == "\n") or (char=="|" and textChars[i + 1]=="n") then
                set token.tokenType = TOKEN_TYPE_NEWLINE
                if (char != "\n") then
                    set i = i + 1
                endif
            //*  Check for color start and color code.
            elseif (char=="|") and (textChars[i + 1] == "c") and (strLength - i >= 10) and (IsHexadecimal(i + 2)) then
                set token.tokenType = TOKEN_TYPE_COLOR
                set token.color = ARGB.create(Hex2Dec[StringHash(textChars[i + 2] + textChars[i + 3])], Hex2Dec[StringHash(textChars[i + 4] + textChars[i + 5])], Hex2Dec[StringHash(textChars[i + 6] + textChars[i + 7])], Hex2Dec[StringHash(textChars[i + 8] + textChars[i + 9])])
                set i = i + 9
            //*  Check for color end
            elseif (char=="|") and (textChars[i + 1] == "r") then
                set token.tokenType = TOKEN_TYPE_COLOREND
                set i = i + 1
            //*  Check for image start, path and end.
            elseif (char == "|") and (textChars[i + 1] == "i") then
                set token.tokenType = TOKEN_TYPE_IMAGE
                set token.value = ""
                set i = i + 2
                loop
                    exitwhen (textChars[i] == "|" and textChars[i + 1] == "i") or (i >= strLength)
                    set token.value = token.value + textChars[i]
                    set i = i + 1
                endloop
                set i = i + 1
            //*  Otherwise it is a normal ascii char.
            else
                set token.tokenType = TOKEN_TYPE_NORMAL
                set token.value = char
            endif
            set token = token + 1
            set i = i + 1
        endloop
        set TextToken.current = token
        set currentLength = i
    endfunction
   
    private function LayoutText takes ARGB backgroundColor, font textFont, real lineHeight returns nothing
        local TextToken base = TextToken.current
        local TextToken token = base
        local real width
        loop
            exitwhen (integer(token) - integer(base) >= TOKENS_PER_CHUNK) or (integer(token) >= tokenAmount)
           
            if (token.tokenType == TOKEN_TYPE_NEWLINE) then
                if (lineWidth[currentLine] > maxLineWidth) or (currentLine == 0) then
                    set maxLineWidth = lineWidth[currentLine]
                endif
                set currentLine = currentLine + 1
                set lineWidth[currentLine] = 0
            elseif (token.tokenType == TOKEN_TYPE_COLOR) then
                set currentColor = token.color
                set defaultColor = false
            elseif (token.tokenType == TOKEN_TYPE_COLOREND) then
                set currentColor = backgroundColor
                set defaultColor = true
            elseif (token.tokenType == TOKEN_TYPE_IMAGE) or (token.tokenType == TOKEN_TYPE_NORMAL) then
                set token.color          = currentColor
                set token.isDefaultColor = defaultColor
                set token.line           = currentLine
                if (token.tokenType == TOKEN_TYPE_IMAGE) then
                    set width = (textFont.getImage(token.value).width*TEXT_SIZE_TO_IMAGE_SIZE*lineHeight/DEFAULT_IMAGE_SIZE)
                else
                    set width = (textFont.getChar(token.value).width*TEXT_SIZE_TO_IMAGE_SIZE*lineHeight/DEFAULT_IMAGE_SIZE)
                endif
                set token.posY = currentLine*lineHeight*TEXT_SIZE_TO_IMAGE_SIZE
                set token.posX = lineWidth[currentLine]
               
                set lineWidth[currentLine] = lineWidth[currentLine] + width
            endif
           
            set token = token + 1
        endloop
       
        set TextToken.current = token
    endfunction
   
    private function DisplayText takes real lineHeight, real bonus, real z, boolean visible, font fontType, Char chars returns nothing
        local TextToken token = TextToken.current
        local real x
        local real y
        local Char char
       // call BJDebugMsg(R2S(bonus))
        loop
            exitwhen (integer(token) - integer(TextToken.current) >= TOKENS_PER_CHUNK) or (integer(token) >= tokenAmount)
            if (token.tokenType == TOKEN_TYPE_IMAGE) or (token.tokenType == TOKEN_TYPE_NORMAL) then
                set x = sourceX + token.posX + (bonus*(maxLineWidth - lineWidth[token.line]))
                set y = sourceY - token.posY
                if (token.tokenType == TOKEN_TYPE_IMAGE) then
                    set char = chars.createImage(fontType, token.value, x, y, z, lineHeight)
                else
                    set char = chars.createChar(fontType, token.value, x, y, z, lineHeight)
                endif
                set char.colorize = token.isDefaultColor
                call char.setColor(token.color)
                set char.show = visible
                call SetImageRenderAlways(char.img, visible)
                set currentLength = currentLength + 1
            endif
            set token = token + 1
        endloop
        set TextToken.current = token
    endfunction
//**
//*  Struct textsplat:
//*  =================
    struct textsplat
        private static constant timer TMR = CreateTimer()
        //*  Linked list data structure.
        private static integer array next
        private static integer array prev
        private static boolean array inList
    //**
    //*  Members:
    //*  ========
        private Char    chars
        private integer ref
        //*  Fields you may read.
        readonly integer charCount
        readonly real    x
        readonly real    y
        readonly real    z
        readonly real    dX
        readonly real    dY
        readonly real    dZ
        readonly real    width
        readonly real    height
        readonly boolean suspended
        readonly boolean visible
        readonly string  text
        readonly ARGB    color
                 //*  Fields you may manipulate directly.
                 font    fontType
                 real    age
                 real    lifespan
                 real    fadepoint
                 boolean permanent
       
        private method clear takes nothing returns nothing
            local Char char = chars.first
            loop
                exitwhen (0 == char)
                if (char.img != null) then
                    call ReleaseImage(char.img)
                    set char.img = null
                endif
                set char = char.next
            endloop
            call chars.clear()
            set charCount = 0
        endmethod
       
        private method remove takes nothing returns nothing
            if (inList[this]) then
                set inList[this] = false
                set next[prev[this]] = next[this]
                set prev[next[this]] = prev[this]
                if (0 == next[0]) then
                    call PauseTimer(TMR)
                endif
            endif 
        endmethod
       
        method destroy takes nothing returns nothing
            call clear()
            call remove()    
            if (ref <= 0) then
                call chars.destroy()
                call deallocate()
            endif
        endmethod
       
        method lock takes nothing returns nothing
            set ref = ref + 1
        endmethod
       
        method unlock takes nothing returns nothing
            set ref = ref - 1
            if (ref <= 0) then
                call destroy()
            endif
        endmethod
       
        private static method onPeriodic takes nothing returns nothing
            local thistype this = next[0]
            local Char     char
            local boolean  hasZ
            local real     val
            loop
                exitwhen (0 == this)
                set char = chars.first
                //*  Update position.
                set x    = x + dX
                set y    = y + dY
                set z    = z + dZ
                set hasZ = (0. != dZ)
                //*  Update alpha channel.
                set val  = 1.
                if not (permanent) then
                    set age = age + ACCURACY
                    if (age >= lifespan) then
                        call destroy()
                        set char = 0//*  Does not enter the loop.
                    elseif (age > fadepoint) then
                        set val = 1. - (age - fadepoint)/(lifespan - fadepoint)
                    endif
                endif
                loop
                    exitwhen (0 == char)
                    set char.x = char.x + dX
                    set char.y = char.y + dY
                    //*  Unlike units, image handles may move out of WorldBounds without producing issues.
                    call SetImagePosition(char.img, char.x, char.y, 0.)
                    if (hasZ) then
                        set char.z = char.z + dZ
                        call SetImageConstantHeight(char.img, true, char.z + GetLocZ(char.x, char.y))
                    endif
                    call SetImageColor(char.img, char.red, char.green, char.blue, R2I(char.alpha*val))
                    set char = char.next
                endloop
                set this = next[this]
            endloop
        endmethod
       
        private method enqueue takes nothing returns nothing
            if not (inList[this]) then
                set inList[this] = true
                set next[this] = 0
                set prev[this] = prev[0]
                set next[prev[0]] = this
                set prev[0] = this
                if (0 == prev[this]) then
                    call TimerStart(TMR, ACCURACY, true, function thistype.onPeriodic)
                endif
            endif
        endmethod
       
        method setText takes string s, real h, integer aligntype returns nothing
            set strLength = StringLength(s)
            set currentLine = 0
            set currentColor = color
            set defaultColor = true
            set maxLineWidth = 0
            call clear()//*  First clean the previous text.
            call SplitString(s)//*  Split string into single chars.
            //*  Tokenize.
            set lineWidth[0] = 0
            set currentLength = 0
            set TextToken.current = 0
            loop
                exitwhen (currentLength >= strLength)
                call ForForce(bj_FORCE_PLAYER[0], function TokenizeText)
            endloop
            //*  Layout.
            set tokenAmount = TextToken.current
            set TextToken.current = 0
            loop
                exitwhen integer(TextToken.current) >= tokenAmount
                call LayoutText.evaluate(color, fontType, h)
            endloop
            if (currentLine == 0) or (lineWidth[currentLine] > maxLineWidth) then
                set maxLineWidth = lineWidth[currentLine]
            endif
            //*  Display.
            set sourceX = x
            set sourceY = y + currentLine*h*TEXT_SIZE_TO_IMAGE_SIZE
            set currentLength = 0
            set TextToken.current = 0
            loop
                exitwhen integer(TextToken.current) >= tokenAmount
                call DisplayText.evaluate(h, aligntype*.5, z, visible, fontType, chars)
            endloop
           
            set charCount = currentLength
            set text = s
            set width = maxLineWidth
            set height = (currentLine + 1)*h*TEXT_SIZE_TO_IMAGE_SIZE
        endmethod
       
        method setVelocity takes real xvel, real yvel, real zvel returns nothing
            set dX = xvel*ACCURACY
            set dY = yvel*ACCURACY
            set dZ = zvel*ACCURACY
        endmethod
       
        method setColor takes integer red, integer green, integer blue, integer alpha returns nothing
            local Char char = chars.first
            local integer val = alpha
            if (age > fadepoint) and (lifespan != fadepoint) and not (permanent) then
                set val = R2I((1 - (age - fadepoint)/(lifespan - fadepoint))*alpha)
            endif
            set color = ARGB.create(alpha, red, green, blue)
            loop
                exitwhen (0 == char)
                if (char.colorize) then
                    set char.red   = red
                    set char.green = green
                    set char.blue  = blue
                    set char.alpha = alpha
                    call SetImageColor(char.img, red, green, blue, val)
                endif
                set char = char.next
            endloop
        endmethod
       
        method setPosition takes real posX, real posY, real posZ returns nothing
            local real newX = posX - x
            local real newY = posY - y
            local real newZ = posZ - z
            local Char char = chars.first
            loop
                exitwhen (0 == char)
                set char.x = char.x + newX
                set char.y = char.y + newY
                set char.z = char.z + newZ
                call SetImagePosition(char.img, char.x, char.y, 0)
                call SetImageConstantHeight(char.img, true, char.z + GetLocZ(char.x, char.y))
                set char = char.next
            endloop
            set x = posX
            set y = posY
            set z = posZ
        endmethod
       
        method setPosUnit takes unit u, real z returns nothing
            call setPosition(GetUnitX(u), GetUnitY(u), z)
        endmethod
       
        method setSuspended takes boolean flag returns nothing
            set suspended = flag
            if (flag) then
                call remove()
            else
                call enqueue()
            endif
        endmethod
       
        method setVisible takes boolean flag returns nothing
            local Char char = chars.first
            set visible = flag
            loop
                exitwhen (0 == char)
                set char.show = flag
                call SetImageRenderAlways(char.img, flag)
                set char = char.next
            endloop
        endmethod
       
        static method create takes font f returns thistype
            local thistype this = thistype.allocate()
            set fontType  = f
            set color     = DEFAULT_COLOR
            set visible   = true
            set permanent = true
            set suspended = false
            set chars     = Char.create()//*  Queue.
            //*  Reset position and fading members.
            set dX        = 0.
            set dY        = 0.
            set dZ        = 0.
            set x         = 0.
            set y         = 0.
            set z         = 0.
            set age       = 0.
            set fadepoint = 0.
            set lifespan  = 0.
            set width     = 0.
            set height    = 0.
            set ref       = 0
            //*  Add to iterating textsplats.
            call enqueue()
            return this
        endmethod
    endstruct
   
    function CreateTextSplat takes font fontType returns textsplat
        return textsplat.create(fontType)
    endfunction
    function DestroyTextSplat takes textsplat t returns nothing
        call t.destroy()
    endfunction
    function SetTextSplatFont takes textsplat t, font new returns nothing
        set t.fontType = new
    endfunction
    function SetTextSplatAge takes textsplat t, real value returns nothing
        set t.age = value
    endfunction
    function SetTextSplatColor takes textsplat t, integer red, integer green, integer blue, integer alpha returns nothing
        call t.setColor(red, green, blue, alpha)
    endfunction
    function SetTextSplatFadepoint takes textsplat t, real value returns nothing
        set t.fadepoint = value
    endfunction
    function SetTextSplatLifespan takes textsplat t, real value returns nothing
        set t.lifespan = value
    endfunction
    function SetTextSplatPermanent takes textsplat t, boolean flag returns nothing
        set t.permanent = flag
    endfunction
    function SetTextSplatPos takes textsplat t, real x, real y, real heightOffset returns nothing
        call t.setPosition(x, y, heightOffset)
    endfunction
    function SetTextSplatPosUnit takes textsplat t, unit whichUnit, real heightOffset returns nothing
        call t.setPosUnit(whichUnit, heightOffset)
    endfunction
    function SetTextSplatSuspended takes textsplat t, boolean flag returns nothing
        call t.setSuspended(flag)
    endfunction
    function SetTextSplatText takes textsplat t, string s, real height returns nothing
        call t.setText(s, height, TEXTSPLAT_TEXT_ALIGN_CENTER)
    endfunction
    function SetTextSplatVelocity takes textsplat t, real xvel, real yvel returns nothing
        call t.setVelocity(xvel, yvel, 0)
    endfunction
    function SetTextSplatVisibility takes textsplat t, boolean flag returns nothing
        call t.setVisible(flag)
    endfunction
//**
//*  Hex to Dec:
//*  ===========
    //! textmacro Hex2DecUpper_Macro takes L
        set Hex2Dec[StringHash("$L$0")]=0x$L$0
        set Hex2Dec[StringHash("$L$1")]=0x$L$1
        set Hex2Dec[StringHash("$L$2")]=0x$L$2
        set Hex2Dec[StringHash("$L$3")]=0x$L$3
        set Hex2Dec[StringHash("$L$4")]=0x$L$4
        set Hex2Dec[StringHash("$L$5")]=0x$L$5
        set Hex2Dec[StringHash("$L$6")]=0x$L$6
        set Hex2Dec[StringHash("$L$7")]=0x$L$7
        set Hex2Dec[StringHash("$L$8")]=0x$L$8
        set Hex2Dec[StringHash("$L$9")]=0x$L$9
        set Hex2Dec[StringHash("$L$A")]=0x$L$A
        set Hex2Dec[StringHash("$L$B")]=0x$L$B
        set Hex2Dec[StringHash("$L$C")]=0x$L$C
        set Hex2Dec[StringHash("$L$D")]=0x$L$D
        set Hex2Dec[StringHash("$L$E")]=0x$L$E
        set Hex2Dec[StringHash("$L$F")]=0x$L$F
    //! endtextmacro
   
    private module InitHex2Dec
        private static method onInit takes nothing returns nothing
            set Hex2Dec = Table.create()
            set IsHex   = Table.create()
   
            //! runtextmacro Hex2DecUpper_Macro("0")
            //! runtextmacro Hex2DecUpper_Macro("1")
            //! runtextmacro Hex2DecUpper_Macro("2")
            //! runtextmacro Hex2DecUpper_Macro("3")
            //! runtextmacro Hex2DecUpper_Macro("4")
            //! runtextmacro Hex2DecUpper_Macro("5")
            //! runtextmacro Hex2DecUpper_Macro("6")
            //! runtextmacro Hex2DecUpper_Macro("7")
            //! runtextmacro Hex2DecUpper_Macro("8")
            //! runtextmacro Hex2DecUpper_Macro("9")
            //! runtextmacro Hex2DecUpper_Macro("A")
            //! runtextmacro Hex2DecUpper_Macro("B")
            //! runtextmacro Hex2DecUpper_Macro("C")
            //! runtextmacro Hex2DecUpper_Macro("D")
            //! runtextmacro Hex2DecUpper_Macro("E")
            //! runtextmacro Hex2DecUpper_Macro("F")
            set IsHex[StringHash("0")]=1
            set IsHex[StringHash("1")]=1
            set IsHex[StringHash("2")]=1
            set IsHex[StringHash("3")]=1
            set IsHex[StringHash("4")]=1
            set IsHex[StringHash("5")]=1
            set IsHex[StringHash("6")]=1
            set IsHex[StringHash("7")]=1
            set IsHex[StringHash("8")]=1
            set IsHex[StringHash("9")]=1
            set IsHex[StringHash("A")]=1
            set IsHex[StringHash("B")]=1
            set IsHex[StringHash("C")]=1
            set IsHex[StringHash("D")]=1
            set IsHex[StringHash("E")]=1
            set IsHex[StringHash("F")]=1
        endmethod
    endmodule
   
    private struct Inits extends array
        implement InitHex2Dec
    endstruct
   
endlibrary

library_once TextSplat uses TextSplat2
endlibrary

Font:
JASS:
//**
//*    Credits:
//*        - Nestharus  ( Ascii, ErrorMessage )
//*        - Bribe      ( Ascii, Table )
//*        - PitzerMike ( original TextSplat system )
//*        - Deaod      ( TextSplat system of the second generation, Font )
//**
//* Written by BPower
library Font/*v1.0
*************************************************************************************
*
*   Creates custom fonts in Warcraft III.
*   Fonts are used to translate strings into a sequence of images.
*
*   A custom fonts may contain every character of the Ascii chart,
*   as well as file paths to textures inside your map.
*
*   Has full backwards compatibility to Deaods Font library.
*
*************************************************************************************
*         
*   Creating custom fonts:
*   ======================
*       Refer to wc3c.net/showthread.php?t=87798
*       You can change the file paths to your needs, but don't forget to copy
*       the width of each individual chars from the .j file that the program creates.
*
*************************************************************************************
*
*   */ requires /*
* 
*       */ Ascii /*                 hiveworkshop.com/forums/jass-resources-412/snippet-ascii-190746/
*       */ Table /*                 hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
*
************************************************************************************
*
*   Import instruction:
*   ===================
*       Copy & paste library Font into your map.
*       Make sure your map also has library Table and library Ascii.
*       Otherwise copy those two required libraries aswell.
*
*   API:
*   ====
*   struct Font extends array   
*  
*       static method create takes Font parentFont returns Font
*          - A parent font is accessed, if a char or file is not available in this font.
*
*       method addChar takes string char, real widthInPixel, string filePath returns nothing
*          - adds a new char to the font. The char must be part of the Ascii chart.
*      
*       method getChar takes string char returns FontChar
*           - FontChar fields are "width" and "path".
*
*       method addImage takes string tag, real widthInPixel, string filePath returns nothing
*           - adds an image to the font. Images are fully compatible with parents.
*           - to access an image you have to use myFont.getImage(tag)
*           - tags are case-insensitive ( myTag is equal to MYtAg )
*
*       method getImage takes string tag returns FontImage
*           - pass in the tag defined in addImage() to access a FontImage
*           - FontImage fields are "width" and "path"
*/
    globals
        //*  Struct font has an instance limit of 31.
        //* Only if you need more than 31 fonts in your map set MORE to true.
        private constant boolean MORE = false
    endglobals
   
    //**
    //*  Naming convention:
    //*  ==================
    //*  Due to backwards compatibility to Deaods Font library
    //* I didn't use medial capitalization.
    //* Personally I prefer struct names using PascalCase.
    private struct fontchar extends array
        private static integer alloc = 0
        string path
        real   width
       
        static method create takes string strPath, real strWidth returns thistype
            local thistype this = thistype.alloc + 1
            set thistype.alloc  = integer(this)
            set path            = strPath
            set width           = strWidth
            return this
        endmethod
    endstruct
   
    private struct fontimage extends array
        private static integer alloc = 0
        string path
        real   width
       
        static method create takes string strPath, real strWidth returns thistype
            local thistype this = thistype.alloc + 1
            set thistype.alloc  = integer(this)
            set path            = strPath
            set width           = strWidth
            return this
        endmethod
    endstruct
   
    static if MORE then
        private module InitFontTable
            private static method onInit takes nothing returns nothing
                set thistype.chars = Table.create()
            endmethod
        endmodule
    endif
   
    struct font extends array
        private static integer alloc = 0
        static if MORE then
            private static Table chars
            implement optional InitFontTable
        else
            private static integer array chars//* (this - 1)*256
        endif
       
        private thistype parent
        private Table    imgs

        method getChar takes string char returns fontchar
            local integer fc = chars[(this - 1)*256 + Char2Ascii(char)]
            if (fc != 0) then
                return fc
            elseif (0 != parent) then
                return parent.getChar(char)
            endif
           
            static if LIBRARY_ErrorMessage then
                debug call ThrowWarning(true, "Font", "getChar", "char", this, "Char [" + char + "] is not available in this font!")
            endif
            return 0
        endmethod
       
        method addChar takes string char, real width, string path returns nothing
            local integer index = (this - 1)*256 + Char2Ascii(char)
            local fontchar fc   = chars[index]
            //*
            static if LIBRARY_ErrorMessage then
                debug call ThrowError((index - ((this - 1)*256) <= 0), "Font", "addChar", "ascii", this, "Char [" + char + "] is not part of the Ascii chart!")
            endif
           
            if (fc == 0) then
                set chars[index] = fontchar.create(path, width)
            else
                set fc.path  = path
                set fc.width = width
            endif
        endmethod
       
        method getImage takes string file returns fontimage
            local integer fi = imgs[StringHash(file)]
            if (0 != fi) then
                return fi
            elseif (0 != parent) then
                return parent.getImage(file)
            endif
           
            static if LIBRARY_ErrorMessage then
                debug call ThrowWarning(true, "Font", "getImage", "file", this, "File [" + file + "] is not available in this font!")
            endif
            return 0
        endmethod
       
        //*  To works with both Tables I didn't use t.exists(index) nor t.has(index).
        //* Luckily hashtables return 0 for not existant entries.
        method addImage takes string file, real width, string path returns nothing
            local integer index = StringHash(file)
            local fontimage img
            if (imgs[index] == 0) then
                set imgs[index] = fontimage.create(path, width)
            else
                set img       = imgs[index]
                set img.path  = path
                set img.width = width
            endif
        endmethod
       
        static method create takes thistype parentFont returns thistype
            local thistype this = thistype.alloc + 1
            static if LIBRARY_ErrorMessage and not MORE then
                debug call ThrowError((this == 32), "Font", "create", "thistype", this, "Overflow. Go to library Font and set boolean MORE to true!")
            endif
            set thistype.alloc = integer(this)
            set parent         = parentFont
            set imgs           = Table.create()
            return this
        endmethod
       
        //*  Backwards compatibility to Deaods Font.
        method operator [] takes string char returns fontchar
            return getChar(char)
        endmethod
       
    endstruct
   
endlibrary

Towards custom fonts:
This could be a useful link
Also this link
 

Attachments

  • TextSplat2.w3x
    100.2 KB · Views: 180
Last edited by a moderator:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I normally don't submitt stuff unwrought. I will improve the documentation asap

Edit: Updated documentation of Font and TextSpalt2.

There are still mistakes in removing/adding nodes to the list. I'll fix it soon.

Also I will replace the iteration over a table with a list module.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
JASS:
        private static method onInit takes nothing returns nothing
            local integer i = 0
            set rn[8191] = 0
            loop
                set rn[i] = i + 1
                exitwhen i == 8190
                set i = i + 1
            endloop
        endmethod

Please don't do that. Nestharus shared a lot of wisdom with us, but this was one of his other ideas. From the looks of the other TextSplat library, it shouldn't have any compatibility issues with NewTable as StringTable syntax is fully supported. The one and only thing not compatible between Vexorian's script and mine is that the .flush(int) method in integer Tables has to be changed to .remove(int).
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
private static method onInit takes nothing returns nothing
local integer i = 0
set rn[8191] = 0
loop
set rn = i + 1
exitwhen i == 8190
set i = i + 1
endloop
endmethod

I will remove the alloc module completly as I want to implement a stack data structure instead.

I will point out detailed, what are the difference to the previous TextSplat,
then we can discuss if this is garbage or I should continue working on it.

library Font:
Has full backwards compatibility.

I. Ascii
New Font library uses Ascii from Bribe/Nes, which is better organized in code, faster and covers all chars from the Ascii chart.
The old Font library has Ascii included inside the code and is incomplete. The lookup is via Table and not via array.

II. Allocate
Fonts, fontchars and fontimages are not destroyed nor recycled.
New Font uses an increasing integer to allocate struct instances.
The old Font library uses vJass standart allocate method.
Fonts possible allocation limit is raised now from 31 to 8190.

III. Overall

The new Font has a bigger char map, performs faster, uses less
memory and has a lower handle count ( - 3 trigger arrays )
You can have now 8190 instead of 31 Fonts if you wish so.

library TextSplat:
Has full backwards compatibility. ( I have to check this again )

I. Stops timer
textsplat = suspended will now remove the textsplat from the periodic list.
This may also stop the timer, if all textspalts are suspended.

II. Performance
It's better than before.

I fixed an issue within the periodic callback function, but I don't remember what it was.


I still have to write the documentation of TextSplat2
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Update:

1. ) Uses now a queue data structure, which will boost the speed when iterating over a textsplat.

2. ) Documentation updated

3.) Optimized all functions.

4.) Now uses a module initializer.

5.) Takes now terrain z into account to set an image height.


I want to add a function which extracts a textsplat to a Table instance.
Basically it makes a copy of the image sequence and stores them in a Table.
Something like: ( start is the first index of the stack, posX/Y/Z are the coordinates )
The returned value is the max index of the stack.
function ExtractTextSplat takes textsplat t, Table t, integer start, real posX, real posY, real posZ returns integer

This is useful, if you wish to write a permanent text. ( It frees textsplat instances and char instances )
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I've added a lock() and unlock() method which adds a reference counter to instances.
On allocation a textsplat has a reference count of 0.
lock() increases the counter by 1.
unlock() decreases the counter by 1 and calls destroy if 0 is hit.

textsplat.destroy() may only destroys a splat with 0 reference counts.
for references > 0 it only clears it and removes it from the periodic list.

For destroying it completly an equal or higher amount of .unlock() to
the number of .lock() must be called for this textsplat instance.

I will go over the scode again and change the documentation style.
I couldn't find anything wrong during testing and I'm using this successfully for own resources.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Try using the Memory Analysis tool in Spells section under Alloc instead of Lock. It'll be a bit more in-depth too ; ). It'll delete the memory if it's destroyed with things still referencing it, but it'll throw an error and let you know of the dangling references.

Good code cleans up after itself imo ^_^.




You won't need API support or anything. Just need to supply a shared memory address. The user can then do with it what they will. MemoryAnalysis should be an optional requirement for debugging purposes only ; ).
 
Level 13
Joined
Nov 7, 2014
Messages
571
//* Hint: You can bind images into a text ( if added to the font ) by using "|i" as identifier.
//* Example: "The price is 2000 |igold|i"
//* Output: Textsplat will translate "|igold|i" into the gold coin image.

This is not yet implemented, right?

I've added a lock() and unlock() method which adds a reference counter to instances.
On allocation a textsplat has a reference count of 0.
lock() increases the counter by 1.
unlock() decreases the counter by 1 and calls destroy if 0 is hit.

textsplat.destroy() may only destroys a splat with 0 reference counts.
for references > 0 it only clears it and removes it from the periodic list.

For destroying it completly an equal or higher amount of .unlock() to
the number of .lock() must be called for this textsplat instance.

Why is this necessary? In what use case?

Sigh... tried to create a new font but I don't have PS, and gimp's tga is not liked by FontConv,
I guess I'll be sticking to texttags then ;P
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
This is not yet implemented, right?
Of course it is.
For exampe:

Here I add the file path of the image to the font. "symbol" is the identifier. 32 is the image width.
call TREBUCHET_MS.addImage("symbol", 32, "UI\\Glues\\Loading\\Backgrounds\\Campaigns\\OrcSymbol.blp")

Passing this string as like:
call SetTextSplatText(splat, "For the horde |isymbol|i", 10)

results in "For the Horde " + image

If Textsplat finds a |i it will search for the next |i takes the string in between and checks
if that font or a parent font of the font supports that string file path.
In the example above it will detect "symbol" as pointer to an image path and looks up the correct path
in the font.

Why is this necessary? In what use case?
By default and equal to texttags, textsplat auto destroys splats which age is bigger than their lifespan.

But sometimes you save a textsplat to an array and eventually destroy it before it fades out.
Now how to determine if the textsplat has already faded?
The array just holds an integer which points to the instance.
If that instance is already destroyed and you call instance.destroy you have a double free.
If that instance is already destroyed and a new textsplat got that recycled index, you'll
destroy the new one instead.

lock() and unlock() prevent both cases.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Of course it is.

Ah, I was just looking at the setText method and didn't see any kind of "|i" thingies, and I opened the ScrollingText map which wasn't using this functionality,
but now I opened the TextSplat2 demo map and the "human" image is right there... =)

Now how to determine if the textsplat has already faded?

It's age >= lifespan? But when that happens it get's destroyed... and the array's textplat could now be pointing to some non related textsplat...
Maybe a permanent textsplat wouldn't necessary mean non-fading?


PS: Another thing that might be good to have is kerning, but I don't how to extract that from a font file... =)
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
maybe an example would be good, both coded and imaged, so people can see what wonders can be created with it? When I opened the Wc3c one, it had like 6 images in there, and this has 0, which is shame imo(not everyone is going to download 200 KB map to see few characters rendered on screen)

struct textsplat in documentation(missing capitalization on T).

If I understand it correctly, in Font you have to actually call addImage and addChar for every char I want to create? couldn't there be some combined add for that, since they both take same arguments?
 
Top