RichText 的lua实现

RichText 的lua实现

展示图

修改完善自大神的文章

https://blog.csdn.net/qq446569365/article/details/46812197

  1. 最近公司需求要一种可以实现彩色文字、图片显示、网络图片下载等功能的邮件系统,so,第一反应就是富文本(RichText),cocos的富文本感觉还没有实现完美,在百度上找了半天,找到了前辈某位大大的作品,感觉他的思路还可以,但是由于时代过于悠久,我还是自己重构了一些代码。下面贴出代码
local _M = class("RichTextEx", function(...)
        return ccui.RichText:create(...)
end)

local flag_enum = 
{
    END = 0,
    ITALICS =  math.pow(2,0),          --/*!< italic text */
    BOLD =  math.pow(2,1),             --/*!< bold text */
    UNDERLINE =  math.pow(2,2),        --/*!< underline */
    STRIKETHROUGH =  math.pow(2,3),    --/*!< strikethrough */
    URL =  math.pow(2,4),              --/*!< url of anchor */
    OUTLINE =  math.pow(2,5),          --/*!< outline effect */
    SHADOW =  math.pow(2,6),           --/*!< shadow effect */
    GLOW =  math.pow(2,7)              --/*!< glow effect */
}

--/
local str_sub   = string.sub
local str_rep   = string.rep
local str_byte  = string.byte
local str_gsub  = string.gsub
local str_find  = string.find

local str_trim  = function(input)
    input = str_gsub(input, "^[ \t\n\r]+", "")
    return str_gsub(input, "[ \t\n\r]+$", "")
end

local C_AND     = str_byte("&")
local P_BEG     = str_byte("<")
local P_END     = str_byte(">")
local SHARP     = str_byte("#")
local ULINE     = str_byte("_")
local C_LN      = str_byte("\n")
local C_TAB     = str_byte("\t")
local C_RST     = str_byte("!")
local C_INC     = str_byte("+")
local C_DEC     = str_byte("-")
local C_MUL     = str_byte("*")
local C_DIV     = str_byte("/")

local function c3b_to_c4b(c3b)
    return { r = c3b.r, g = c3b.g,  b = c3b.b, a = 255 }
end

--------------------------------------------------------------------------------
-- #RRGGBB/#RGB to c3b
local function c3b_parse(s)
    if not s then
        return nil
    end
    local r, g, b = 0, 0, 0
    if #s == 4 then
        r, g, b =   tonumber(str_rep(str_sub(s, 2, 2), 2), 16),
                    tonumber(str_rep(str_sub(s, 3, 3), 2), 16),
                    tonumber(str_rep(str_sub(s, 4, 4), 2), 16)
    elseif #s == 7 then
        r, g, b =   tonumber(str_sub(s, 2, 3), 16),
                    tonumber(str_sub(s, 4, 5), 16),
                    tonumber(str_sub(s, 6, 7), 16)
    end
    return cc.c3b(r, g, b)
end

--------------------------------------------------------------------------------

local function ccsize_parse( s )
    if not s then
        return nil
    end
    local w,h = 0,0
    local p1 = str_find(s, "*")
    w = tonumber(str_sub(s, 1, p1 - 1))
    h = tonumber(str_sub(s, p1 + 1))
    return cc.size(w,h)
end

--------------------------------------------------------------------------------
-- local _FIX = {
--  ["&lt;"] = "<",
--  ["&gt;"] = ">",
-- }
-- local function str_fix(s)
--  for k, v in pairs(_FIX) do
--      s = str_gsub(s, k, v)
--  end
--  return s
-- end

--/
function _M:ctor(
    color, 
    opacity, 
    text, 
    fontName, 
    fontSize, 
    flags, 
    url, 
    outlineColor, 
    outlineSize, 
    shadowColor, 
    shadowOffset,
    shadowBlurRadius,
    glowColor
)
    self._text          = text or ""
    self._fontSizeDef   = fontSize or display.DEFAULT_TTF_FONT_SIZE
    self._textColorDef  = color or display.COLOR_WHITE
    self._fontSize      = self._fontSizeDef
    self._textColor     = self._textColorDef
    self._elements      = {}
    self._textFont      = fontName or "fzzyjt.TTF"
    self._flag          = flag_enum[flags] or 0
    self._url           = url or ""
    self._outlinecolor  = outlineColor or display.COLOR_WHITE
    self._outlinesize   = outlineSize or 2
    self._shadowcolor   = shadowColor or display.COLOR_BLACK
    self._shadowoffset  = shadowOffset or cc.size(2,-2)
    self._shadowblurradius  = shadowBlurRadius or 0
    self._glowColor     = glowColor or display.COLOR_WHITE
end

--/
-- 多行模式,要设置 ignoreContentAdaptWithSize(false) 和设置 setContentSize()
function _M:setMultiLineMode(b)
    self:ignoreContentAdaptWithSize(not b)
    return self
end

--/
function _M.defaultCb(text, sender)
    local BLINK         = "blink "
    local BLINK_        = "blink_"
    local ROTATE        = "rotate "
    local ROTATE_       = "rotate_"
    local SCALE         = "scale "
    local SCALE_        = "scale_"
    local COPY          = "copy "
    local COPY_         = "copy_"
    local NEWLINE       = "NEWLINE"
    local INDENT        = "INDENT"

    if str_sub(text, 1, #BLINK) == BLINK or str_sub(text, 1, #BLINK_) == BLINK_ then
        local lbl = ccui.Text:create(sender:htmlDecode(str_sub(text, #BLINK + 1)), sender._textFont, sender._fontSize)
        lbl:setTextColor(c3b_to_c4b(sender._textColor))
        lbl:runAction(cc.RepeatForever:create(cc.Blink:create(10, 10)))
        return lbl
    elseif str_sub(text, 1, #ROTATE) == ROTATE or str_sub(text, 1, #ROTATE_) == ROTATE_ then
        local lbl = ccui.Text:create(sender:htmlDecode(str_sub(text, #ROTATE + 1)), sender._textFont, sender._fontSize)
        lbl:setTextColor(c3b_to_c4b(sender._textColor))
        lbl:runAction(cc.RepeatForever:create(cc.RotateBy:create(0.1, 5)))
        return lbl
    elseif str_sub(text, 1, #SCALE) == SCALE or str_sub(text, 1, #SCALE_) == SCALE_ then
        local lbl = ccui.Text:create(sender:htmlDecode(str_sub(text, #SCALE + 1)), sender._textFont, sender._fontSize)
        lbl:setTextColor(c3b_to_c4b(sender._textColor))
        lbl:runAction(cc.RepeatForever:create(cc.Sequence:create(cc.ScaleTo:create(1.0, 0.1), cc.ScaleTo:create(1.0, 1.0))))
        return lbl
    elseif str_sub(text, 1, #NEWLINE) == NEWLINE then
        local obj = ccui.RichElementNewLine:create(0, cc.c3b(11, 11, 11), 255)
        sender:pushBackElement(obj)
        sender._elements[#sender._elements + 1] = obj
    elseif str_sub(text, 1, #INDENT) == INDENT then
        local obj = cc.Node:create()
        obj:setContentSize(cc.size(sender._fontSize * 2, 1))
        return obj
    elseif str_sub(text, 1, #COPY) == COPY or str_sub(text, 1, #COPY_) == COPY_ then
        local lbl = ccui.Text:create(sender:htmlDecode(str_sub(text, #COPY + 1)), sender._textFont, sender._fontSize)
        lbl:setTextColor(c3b_to_c4b(sender._textColor))
        return lbl
    end

    return nil
end

--/
-- TODO: 对 http:// 开头的路径进行动态网络下载
function _M:defaultImgCb(text)
    local arr = string.split(text, " ")
    local w, h = 0, 0
    if #arr > 1 then
        local p1 = str_find(arr[1], "*")

        w = tonumber(str_sub(arr[1], 1, p1 - 1))
        h = tonumber(str_sub(arr[1], p1 + 1))

        text = str_trim(arr[2])
    else
        text = str_trim(arr[1])
    end

    local spf, img = cc.SpriteFrameCache:getInstance():getSpriteFrame(text), nil
    if spf then
        img = ccui.ImageView:create(text, ccui.TextureResType.plistType)
    elseif cc.FileUtils:getInstance():isFileExist(text) then
        img = ccui.ImageView:create(text, ccui.TextureResType.localType)
    elseif string.find(text, "http") then
        img = ccui.ImageView:create()
        local function callback(urlMd5)
            local function delay()
                local fileName = device.writablePath..urlMd5..".png"
                if cc.FileUtils:getInstance():isFileExist(fileName) then
                    if fileName then
                        img:loadTexture(fileName)
                    end
                end
            end
            scheduler.performWithDelayGlobal(delay, 0.1)
        end
        NetSprite.new(text, callback, 100, 100, true):addTo(self)
    end

    if img and w and h and w > 0 and h > 0 then
        img:ignoreContentAdaptWithSize(false) -- cc.Sprite can't do this, so we use ccui.ImageView
        img:setContentSize(cc.size(w, h))
    end

    return img
end

--/
function _M:addCustomNode(node)
    if node then
        local anc = node:getAnchorPoint()
        if anc.x ~= 0.0 or anc.y ~= 0.0 then
            local tmp = node
            local siz = node:getContentSize()
            node = cc.Node:create()
            node:setContentSize(siz)
            node:addChild(tmp)
            tmp:setPosition(cc.p(siz.width * anc.x, siz.height * anc.y))
        end
        local obj = ccui.RichElementCustomNode:create(0, cc.c3b(255,255,255), 255, node)
        self:pushBackElement(obj)
        self._elements[#self._elements + 1] = obj
    end
end

--/
-- 可以在 callback 里添加各种自定义<XXXXX XXX>语法控制
function _M:setText(text, callback)
    assert(text)
    text = self.htmlDecode(text)
    self._text = text
    self._callback = callback

    self._fontSize  = self._fontSizeDef
    self._textColor = self._textColorDef

    -- clear
    for _, lbl in pairs(self._elements) do
        self:removeElement(lbl)
    end
    self._elements = {}

    local p, i, b, c = 1, 1, false
    local str, len, chr, obj = "", #text

    while i <= len do
        c = str_byte(text, i)
        if c == P_BEG then  -- <
            if (not b) and (i > p) then
                str = str_sub(text, p, i - 1)
                obj = ccui.RichElementText:create(
                    0,
                    self._textColor,
                    255,
                    self:htmlDecode(str),
                    self._textFont,
                    self._fontSize,
                    self._flag,
                    self._url,
                    self._outlinecolor,
                    self._outlinesize,
                    self._shadowcolor,
                    self._shadowoffset,
                    self._shadowblurradius,
                    self._glowColor
                )
                self:pushBackElement(obj)
                self._elements[#self._elements + 1] = obj
            end

            b = true; p = i + 1; i = p

            while i <= len do
                if str_byte(text, i) == P_END then  -- >
                    b = false
                    if i > p then
                        str = str_trim(str_sub(text, p, i - 1))
                        chr = str_byte(str, 1)
                        if chr == SHARP and (#str == 4 or #str == 7) and tonumber(str_sub(str, 2), 16) then -- textColor
                            self._textColor = c3b_parse(str)
                        elseif chr == C_RST and #str == 1 then  -- reset
                            self._textColor = self._textColorDef
                            self._fontSize  = self._fontSizeDef
                            self._textFont  = ""
                            self._flag = 0
                        elseif (chr == C_INC or chr == C_DEC or chr == C_MUL or chr == C_DIV)
                                and tonumber(str_sub(str, 2)) then
                            local v = tonumber(str_sub(str, 2)) or 0
                            if chr == C_INC then
                                self._fontSize = self._fontSize + v
                            elseif chr == C_DEC then
                                self._fontSize = self._fontSize - v
                            elseif chr == C_MUL then
                                self._fontSize = self._fontSize * v
                            elseif v ~= 0 then
                                self._fontSize = self._fontSize / v
                            end
                        elseif tonumber(str) then   -- fontSize
                            self._fontSize = tonumber(str)
                        elseif str_sub(str, 1, 5) == "font " or str_sub(str, 1, 5) == "font_" then
                            self._textFont = str_trim(str_sub(str, 6, i - 1))
                        elseif str_sub(str, 1, 5) == "flag " or str_sub(str, 1, 5) == "flag_" then
                            local strTemp = str_trim(str_sub(str, 6, i - 1))
                            local arr = string.split(strTemp, " ")
                            self._flag = flag_enum[arr[1]] or 0
                            if self._flag == flag_enum["URL"] then
                                self._url               = arr[2] or self._url
                            elseif self._flag == flag_enum["OUTLINE"] then
                                self._outlinecolor      = c3b_parse(arr[2]) or self._outlinecolor
                                self._outlinesize       = tonumber(arr[3]) or self._outlinesize
                            elseif self._flag == flag_enum["SHADOW"] then
                                self._shadowcolor       = c3b_parse(arr[2]) or self._shadowcolor
                                self._shadowoffset      = ccsize_parse(arr[3]) or self._shadowoffset
                                self._shadowblurradius  = tonumber(arr[4]) or self._shadowblurradius
                            elseif self._flag == flag_enum["GLOW"] then
                                self._glowColor         = c3b_parse(arr[2]) or self._glowColor
                            end
                        elseif str_sub(str, 1, 4) == "img " or str_sub(str, 1, 4) == "img_" then
                            local strTemp = str_trim(str_sub(str, 5, i - 1))
                            local arr = string.split(strTemp, " ")
                            local w, h = 50, 50
                            if #arr > 1 then
                                local p1 = str_find(arr[1], "*")

                                w = tonumber(str_sub(arr[1], 1, p1 - 1))
                                h = tonumber(str_sub(arr[1], p1 + 1))

                                strTemp = str_trim(arr[2])
                            else
                                strTemp = str_trim(arr[1])
                            end
                            local _obj = ccui.RichElementImage:create(0,cc.c3b(11, 11, 11),255,strTemp,self._url)
                            _obj:setWidth(w)
                            _obj:setHeight(h)
                            self:pushBackElement(_obj)
                            self._elements[#self._elements + 1] = _obj
                        elseif str_sub(str, 1, 7) == "imgurl " or str_sub(str, 1, 7) == "imgurl_" then
                            self:addCustomNode(self:defaultImgCb(str_trim(str_sub(str, 8, i - 1))))
                        else
                            if self._callback then
                                self:addCustomNode(self._callback(str, self))
                            end
                            self:addCustomNode(self.defaultCb(str, self))
                        end
                    end

                    break
                end
                i = i + 1
            end

            p = i + 1
        elseif c == C_LN or c == C_TAB then
            if (not b) and (i > p) then
                str = str_sub(text, p, i - 1)
                obj = ccui.RichElementText:create(
                    0,
                    self._textColor,
                    255,
                    self:htmlDecode(str),
                    self._textFont,
                    self._fontSize,
                    self._flag,
                    self._url,
                    self._outlinecolor,
                    self._outlinesize,
                    self._shadowcolor,
                    self._shadowoffset,
                    self._shadowblurradius,
                    self._glowColor
                )
                self:pushBackElement(obj)
                self._elements[#self._elements + 1] = obj
            end

            if c == C_LN then
                -- obj:setContentSize(cc.size(self:getContentSize().width, 1))
                obj = ccui.RichElementNewLine:create(0, cc.c3b(11, 11, 11), 255)
                self:pushBackElement(obj)
                self._elements[#self._elements + 1] = obj
            else
                obj = cc.Node:create()
                obj:setContentSize(cc.size(self._fontSize * 2, 1))
                self:addCustomNode(obj)
            end


            p = i + 1
        end

        i = i + 1
    end

    if (not b) and (p <= len) then
        str = str_sub(text, p)
        obj = ccui.RichElementText:create(
            0,
            self._textColor,
            255,
            self:htmlDecode(str),
            self._textFont,
            self._fontSize,
            self._flag,
            self._url,
            self._outlinecolor,
            self._outlinesize,
            self._shadowcolor,
            self._shadowoffset,
            self._shadowblurradius,
            self._glowColor
        )
        self:pushBackElement(obj)
        self._elements[#self._elements + 1] = obj
    end

    return self
end
function _M:setDefaultFont(font)
    self._textFont = font
end


--[[--

将特殊字符转为 HTML 转义符

~~~ lua

print(RichTextEx.htmlEncode("<ABC>"))
-- 输出 &lt;ABC&gt;

~~~

@param string input 输入字符串

@return string 转换结果


本来想直接把触控的function里边的算法扳过来,发现不对,也不知道哪个二货写的算法。也许是我看的哪个function版本太低了
对于<>  编码成 &lt; &gt; 这个很正常,然后他又把&gt;的&给编码成&amp;
<ABC> 期望的编码结果  "&lt;ABC&gt;"  最终让他给编成了 "&amp;lt;ABC&amp;gt;" 解析时候就出错了。。。。
]]

function _M.htmlEncode(self,input)
    if not input then input = self end
    input = string.gsub(input,"&", "&amp;") 
    input = string.gsub(input,"\"", "&quot;")
    input = string.gsub(input,"'", "&#039;")
    input = string.gsub(input,"<", "&lt;")
    input = string.gsub(input,">", "&gt;")
    return input
end

--[[--

将 HTML 转义符还原为特殊字符,功能与 string.htmlEncode() 正好相反

~~~ lua

print(RichTextEx.htmlDecode("&lt;ABC&gt;"))
-- 输出 <ABC>

~~~

@param string input 输入字符串

@return string 转换结果

]]
function _M.htmlDecode(self,input)
    if not input then input = self end
    input = string.gsub(input,"&gt;",">")
    input = string.gsub(input,"&lt;","<")
    input = string.gsub(input,"&#039;","'")
    input = string.gsub(input,"&quot;","\"")
    input = string.gsub(input,"&amp;","&")
    return input
end
function _M:create(...)
    local richTextEx = _M.new(...)
    return richTextEx
end

--/

return _M
  1. 下面来说说如何使用:
-------------------------------------------------
    RichTextEx.lua
    Created by xqp on 18-08-10.
    Fixed by xqp on 18-08-12.
-------------------------------------------------
一个简单的富文本 Label,用法

    local txt = RichTextEx:create() -- 或 RichTextEx:create(26, cc.c3b(10, 10, 10))
    txt:setText("<img_30*30 laba.png><flag SHADOW #000 2*-2 5><#F00>系统邮件:<flag END><NEWLINE><INDENT><#FFF>获得了奖励<imgurl_50*50 http://imgs.91y.com/head/head0_0.png><#FF0>10000<#FFF>金币。<#F0F><blink 点击领取!>哈哈")
    -- 多行模式要同时设置 ignoreContentAdaptWithSize(false) 和 contentSize
    txt:setMultiLineMode(true)  -- 这行其实就是 ignoreContentAdaptWithSize(false)
    txt:setContentSize(200, 400)
    addChild(txt)

    如果字符串是由用户输入的话,建议调用RichTextEx.htmlEncode("<ABC>")将用户输入内容编码一下,以避免用户输入关键字符导致无法预知的错误
    在生成字符串之前会自动调用RichTextEx.htmlDecode,如果你自定义了字符串创建,请记得调用这个,以解码

基本选项是
    <#F00> = <#FF0000>  = 文字颜色
    <32>                = 字体大小
    <font Arial>        = 文字字体 支持TTF
    <img filename>      = 图片(filename 可以是已经在 SpriteFrameCache 里的 key,或磁盘文件)
    <img_32*32 fname>   = 指定大小的图片
    <+2> <-2> <*2> </2> = 当前字体大小 +-*/
    <!>                 = 颜色、字体和字体大小恢复默认
    <NEWLINE> <INDENT>  = 换行 和 缩进

    <flag_END>                                                  = 设置结束
    <flag_ITALICS>                                              = 斜体
    <flag_BOLD>                                                 = 加粗
    <flag_UNDERLINE>                                            = 下划线
    <flag_STRIKETHROUGH>                                        = 删除
    <flag_URL http://imgs.91y.com/head/head0_0.png>             = 网址点击效果,但是貌似没用
    <flag_OUTLINE #FFF 2>                                       = 描边 (color, size) 
    <flag_SHADOW #FFF 2*-2 3>                                   = 阴影 (color, offset, radius)
    <flag_GLOW #FFF>                                            = 发光 (color)

示例选项是 (在 RichTextEx.defaultCb 中提供)
    <blink 文字>      = (动画)闪烁那些文字
    <rotate 文字>     = (动画)旋转那些文字
    <scale 文字>      = (动画)缩放那些文字
    (但如果你做了 setText(t, callback) 除非你在 callback 主动调用 defaultCb,否则以上选项会被忽略)   

    <imgurl_w*h http://path/image> 从网络下载图片
    ...

同时支持自定义特殊语法,加入 callback 回调就可,如

    txt:setText("XXXXX <aaaa haha> <bbbb> <CCCC> xxx", function(text, sender) -- 第二个参数 sender 可选
        -- 对每一个自定义的 <***> 都会调用此 callback
        -- text 就等于 *** (不含<>)
        -- 简单的返回一个 Node 的子实例就可,如
        -- 如果接收第二个参数 sender,就可获取当前文字大小、颜色: sender._fontSize、sender._textColor

        if string.sub(text, 1, 4) == "aaaa" then
            return ccui.Text:create("aaa111" .. string.sub(text, 6)), "", 32)
            --这里如果为了代码的健壮性最好加入self:htmlDecode
            --return ccui.Text:create(self:htmlDecode("aaa111" .. string.sub(text, 6))), "", 32)
        elseif text == "bbbb" then
            -- 用当前文字大小和颜色
            local lbl = ccui.Text:create("bbb111", "", sender._fontSize)
            lbl:setTextColor(sender._textColor)
            return lbl
        elseif string.sub(text, 1, 4) == "CCCC" then
            local img = ccui.ImageView:create(....)
            img:setScale(...)
            img:runAction(...)
            return img
        end
    end)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值