Release 3713 will not build html5

I cannot build an html5 version with release 2024.3713. I have tried several different apps, always get the same error (and a blank screen after the Solar2d logo). Works fine on 3211 and 3212

can you create a new blank project from corona simulator and try to build as HTML5 just to make sure that the error is caused by your code or by simulator itself

I built three of the sample projects, simple ones, and they worked. But none – 0 – of my projects that have worked for years on every build until 3713 work with 3713 html builds, and fail the same way.

None of the examples seem to use composer, all use only main.lua.

I just ran a bunch of programs I have sitting around. I did manage to trigger this on one of them, otherwise okay. I’m now struggling to pin down the troublesome one again, annoyingly… any chance you could send a tiny example that reproduces it?

We’re merging this stuff, which includes lots of graphics stuff, so WebGL hiccups aren’t totally unexpected. (Would also like to know about any non-web weirdness too, of course.)

Build 3712 is everything up to that point, so fine to use.

Sure. I put 3 projects on my server, I will send you the links in a message (I don’t need to share all that code with the world). One is a 10-year-old project, one a very simple retro arcade game, one a (programming) simple economics game. All build in 3712 and not in 3713.

None are new, current projects.

1 Like

I definitely see the issue, and consistently.

I realized one or two of my programs was triggering the error, but the browser stopped nagging after the first incident, thus the “struggling to pin down” thing.

The one in question (but not others) actually worked fine in Safari, but not in Opera GX. More strangely, it had some extra scripts (was just a copy-paste to make the project) and only has an issue then; if I strip it down to just a main.lua it was fine.

My theory was that somehow a “long” initialization was involved: in my case the slight extra size; in your case maybe that and / or the fonts, which I see have special attention in the web loading code. But some other large ones just work. I saw the error in the Angle project code and it suggested the renderer somehow gets inconsistent, so I had some theory it was tied to the new changes, but haven’t seen anything that bears that out.

I was discussing this with @Siu and (with my project) he also found some weirdness with build.settings, but nothing definitive. The debugging techniques are all pretty unfamiliar.

1 Like

Fonts make some sense – I do load special fonts in all my projects, would be a bit of work to remove them. I removed all references to non-system fonts in one project, made no difference. But they might still be contained in the build.

I tried comparing the build.settings, and nothing obvious I could see, but will try a few more things.

My current project did not work in Safari, Arc, Brave, or Chrome.

1 Like

I advice you to implement bitemap fonts with that special fonts.
I have some of my HTML5 games using bitmap fonts. I’ve made some improvements to the Ponyfont code because I found a few bugs in it. If you choose this option, I can send you my version of the Ponyfont code.

Additionally, you can use this Bitmap Font Generator software (freeware and open source) to generate your bitmap fonts. You can download it here:
https://www.angelcode.com/products/bmfont/

If you decide to go with this option, I can also share the configuration I use in the software.

PD: Sorry, I made a mistake in my response. That reply was intended for @dmarques42

1 Like

I will try, but it will take a while, there is a lot to replace in my projects. Last time I tried bitmap fonts they were all fuzzy, and I did not like the result (also slower). Maybe it is better now. The angelcode you recommended is for Windows, I have a mac. But I can try substituting some for one project.

I guess I want to try ponyfont, but it looks like I have to replace every display.newText in my app, is that right? That is a lot of re-coding.

Yes, I’m afraid you’ll have to replace each text in your code. But the replacement consists of:

Never create a text including the ‘parent’ parameter in the options of display.newText. Instead, do something like this:

if platform == "html5" then
     myGroup:insert(myText.raw)
else
     myGroup:insert(myText)
end

Other considerations:

To create your bitmap font, first review your entire code and identify the largest font size you use. Then, in the settings of the bitmap font generator software, set the font size to double the maximum size used in your app.

The configuration I use in bitmapfont64.exe (the version I downloaded from https://www.angelcode.com/products/bmfont/) to export the bitmap font is as follows:

In Font Settings:

  • Charset: Unicode
  • Match char height: Checked
  • Render from TrueType outline: Do NOT check.
  • ClearType: Checked (improves rendering quality)
  • Font Smoothing: Checked (enhances text appearance)
  • Super Sampling: Checked - choose level: 2 (use this when text quality is a priority, such as in HTML5 where texts can appear blurry)

In Export Options:

  • Paddings: 2 on each side
  • Spacing: 0 for all
  • Equalize the cell height: Checked (this ensures the text is properly centered along the ‘y’ axis).
  • Width and Height: Width and height of the bitmap font’s image sheet
  • Bit Depth: Select 32 bits
  • Pack chars in multiple channels: Checked (enabled)
    …

Last but not least, I’ve attached the code for ponyfont.lua below with some changes I implemented to fix several bugs:

-- ponyfont 0.3

-- A bitmap font loader/render for CoronaSDK

local M = {}
local cache = {} -- cache for loaded fonts


-- If you use UTF-8 characters, you will need to add this plugin to your
-- build settings
-- https://docs.coronalabs.com/plugin/utf8/index.html

local utf8 -- or require("lua-utf8") via luarocks 

-- Solar2D's utf8 plugin does not have html5 support
if platform == "html5" then
    local floor = math.floor
    utf8 = {}
    function utf8.codes(str)
        local pos = 1  -- starting position in the string
        str = tostring(str) -- convert to a string (can be a number)
        local _len = #str  -- string length
        return function()
            if pos <= _len then
                local code
                local byte = string.byte(str, pos)
                
                if byte < 0x80 then
                    code = byte
                    pos = pos + 1
                elseif byte < 0xE0 then
                    code = (byte % 0x40) * 0x40 + string.byte(str, pos + 1) % 0x80
                    pos = pos + 2
                elseif byte < 0xF0 then
                    code = (byte % 0x20) * 0x1000 + (string.byte(str, pos + 1) % 0x80) * 0x40 + string.byte(str, pos + 2) % 0x80
                    pos = pos + 3
                else
                    code = (byte % 0x10) * 0x40000 + (string.byte(str, pos + 1) % 0x80) * 0x1000 + (string.byte(str, pos + 2) % 0x80) * 0x40 + string.byte(str, pos + 3) % 0x80
                    pos = pos + 4
                end
                
                return pos - 1, code
            end
        end
    end -- end of 'utf8.codes()'
    
    utf8.char = function(...)
        local function encode(code)
            if type(code) ~= "number" then
                code = tonumber(code)
                if not code then
                    --error("Expected a number, got " .. type(code) .. " (" .. tostring(code) .. ")")
                end
            end
            if code <= 0x7F then
                return string.char(code)
            elseif code <= 0x7FF then
                return string.char(
                    0xC0 + floor(code / 0x40),
                    0x80 + (code % 0x40)
                )
            elseif code <= 0xFFFF then
                return string.char(
                    0xE0 + floor(code / 0x1000),
                    0x80 + (floor(code / 0x40) % 0x40),
                    0x80 + (code % 0x40)
                )
            elseif code <= 0x10FFFF then
                return string.char(
                    0xF0 + floor(code / 0x40000),
                    0x80 + (floor(code / 0x1000) % 0x40),
                    0x80 + (floor(code / 0x40) % 0x40),
                    0x80 + (code % 0x40)
                )
            else
                --error("Unicode code point out of range: " .. tostring(code))
            end
        end
        
        local chars = {}
        for _, code in ipairs({...}) do
            if type(code) ~= "number" then
                code = tonumber(code)
                if not code then
                    --error("Expected a number, got " .. type(code) .. " (" .. tostring(code) .. ")")
                end
            end
            table.insert(chars, encode(code))
        end
        return table.concat(chars)
    end
else
    utf8 = require("plugin.utf8") -- or require("lua-utf8") via luarocks 
end
-- property update events by Jonathan Beebe
-- https://coronalabs.com/blog/2012/05/01/tutorial-property-callbacks/

local function addPropertyUpdate(obj)
  local t = {}
  t.raw = obj

  local mt = {
    __index = function(_,k)
      if k == "raw" then
        return rawget(t, "raw")
      end

      -- pass method and property requests to the display object
      if type(obj[k]) == 'function' then
        return function(...) arg[1] = obj; obj[k](unpack(arg)) end
      else
        return obj[k]
      end
    end,

    __newindex = function(tb,k,v)
      -- dispatch event before property update
      local event = {
        name = "propertyUpdate",
        target=tb,
        key=k,
        value=v
      }
      obj:dispatchEvent(event)

      -- update the property on the display object
      obj[k] = v
    end
  }
  setmetatable(t, mt)
  return t
end

local function parseFilename(filename)
  return string.match(filename,"(.-)([^\\/]-%.?([^%.\\/]*))$")
end

local function extract(s, p)
  return string.match(s, p), string.gsub(s, p, '', 1)
end

function M.newText(options)
    options = options or {}

    -- Modified .fnt loading code from bmf.lua
    -- Contacted author and he released under CC0

    local function loadFont(name)
      -- load the fnt
      local path, filename, ext = parseFilename(name)
      local font = { info = {}, spritesheets = {}, sprites = {}, chars = {}, kernings = {} }
      local contents = io.lines(system.pathForFile(path .. filename, system.ResourceDirectory))
      for line in contents do
        local t = {}
        local tag
        tag, line = extract(line, '^%s*([%a_]+)%s*')
        while string.len(line) > 0 do
          local k, v
          k, line = extract(line, '^([%a_]+)=')
          if not k then break end
          v, line = extract(line, '^"([^"]*)"%s*')
          if not v then
            v, line = extract(line, '^([^%s]*)%s*')
          end
          if not v then break end
          t[k] = v
        end
        if tag == 'info' or tag == 'common' then
          for k, v in pairs(t) do font.info[k] = v end
        elseif tag == 'page' then
          font.spritesheets[1 + t.id] = { file = t.file, frames = {} }
        elseif tag == 'char' then
          t.letter = utf8.char(t.id)
          font.chars[t.letter] = {}
          for k, v in pairs(t) do
            font.chars[t.letter][k] = v
          end
          if 0 + font.chars[t.letter].width > 0 and 0 + font.chars[t.letter].height > 0 then
            font.spritesheets[1 + t.page].frames[#font.spritesheets[1 + t.page].frames + 1] = {
              x = 0 + t.x,
              y = 0 + t.y,
              width = 0 + t.width,
              height = 0 + t.height,
            }
            font.sprites[t.letter] = {
              spritesheet = 1 + t.page,
              frame = #font.spritesheets[1 + t.page].frames
            }
          end
        elseif tag == 'kerning' then
          font.kernings[utf8.char(t.first) .. utf8.char(t.second)] = 0 + t.amount -- old code
        end
      end
      for k, v in pairs(font.spritesheets) do
        font.spritesheets[k].sheet = graphics.newImageSheet(path .. v.file, v)
      end
      for k, v in pairs(font.sprites) do
        font.sprites[k].frame = v.frame
      end
      return font
    end -- end of 'loadFont'

    -- create displayGroup instance
    local instance = display.newGroup()
    if options.parent then
        options.parent:insert(instance)
    end
    
    -- load font if not in cache
    local fontFile = options.font or "default"
    if not cache[fontFile] then
      cache[fontFile] = loadFont(fontFile)
    end  
    instance.bitmapFont = cache[fontFile]

    -- Brand-spanking new render code with scaling to fontSize,
    -- word wrapping to width and general performance and 
    -- readability updates

    function instance:anchor()
      local w,h = self._width or self.width, self.height
      local x,y = self.anchorX or 0.5, self.anchorY or 0.5
      for i = self.numChildren, 1, -1 do
        if self[i]._x and self[i]._y then
          self[i].x = self[i]._x
          self[i].y = self[i]._y
          self[i]:translate(-w * x, -h * y)
        end
      end
    end

    function instance:justify(align)
      if not self._width then return false end
      -- grab first letter
      local letter = self[1]
      if not letter then return false end

      -- default
      align = self.align or "left"
      local x, last, lastX, w = 0, 1, letter.x, self._width or self.width
      
      local currY
      -- push stuff around
      local iFinal = self.numChildren
      for i = 1, iFinal do -- iterates over all characters in the text
        if self[i]._x and self[i]._y then
          x = self[i].x + self[i].width
          local newLine = false
          if i > 1 and ( self[i].y - currY ) > 3 then -- to detect line jump
              newLine = true
          end
          currY = self[i].y
          
          --if ( x < lastX ) or ( i == iFinal ) then -- original code
          if newLine or ( i == iFinal ) then -- wrapped
            print ("new line")
            -- diff is based on assigned width
            local diff = w - lastX
            if align == "right" then 
              diff = diff - w*0.5
            elseif align == "center" then 
              diff = diff * 0.5 - w*0.25
            else
              diff = 0
            end
            local jFinal = i - ( (i == self.numChildren) and 0 or 1 )
            -- positions characters for the same line
            for j = last, jFinal do
              self[j]:translate(diff,0)
              self[i].xScale = self[i]._xScale
              self[i].yScale = self[i]._yScale
            end
            last = i
          end
          lastX = x - self[i].width*0.5
        end
      end
      
    end -- end of justify
    
    
    function instance:render( size )
      local text  = self.text
      local font  = self.bitmapFont
      local info  = font.info
      local scale = self.fontSize / info.size
      if size then
          scale = self.size / info.size
      end
      if self.size ~= self.fontSize then
          scale = self.size / info.size
      end
      local fill = self.fill or {1}
      -- clear previous text
      for i = self.numChildren, 1, -1 do 
        display.remove(self[i])
      end

      -- locals
      local x, y = 0, 0
      local last = ''
      local lastWord = 0
      
      local function kern(x, pair)
        if font.kernings[pair] then
          x = x + font.kernings[pair]
        end
        return x
      end

      if text then
        local prevLetter = nil
        for _, codepoint in utf8.codes(text) do
          local letter = utf8.char(codepoint)
          if letter == '\n' then -- newline
            x, y = 0, y + info.lineHeight
          elseif font.chars[letter] then
            if letter ~= " " and tonumber(font.chars[letter].width) > 0 and tonumber(font.chars[letter].height) > 0 then
              local glyph = display.newImage(font.spritesheets[font.sprites[letter].spritesheet].sheet, font.sprites[letter].frame)
              x = kern(x, last .. letter)
              glyph.anchorX, glyph.anchorY = 0, 0            
              glyph.xScale = scale
              glyph.yScale = scale
              glyph.x = scale * (font.chars[letter].xoffset + x)
              glyph.y = scale * (font.chars[letter].yoffset + y)
              glyph._x = glyph.x -- original offset from self's x
              glyph._y = glyph.y -- original offset from self's y 
              glyph._xScale = glyph.xScale
              glyph._yScale = glyph.yScale
              glyph.chr = letter
              last = letter
              lastWord = lastWord + 1
              self:insert(glyph)
              if fill[2] then
                  glyph:setFillColor(fill[1], fill[2], fill[3])
              else
                  glyph:setFillColor(fill[1])
              end
            elseif letter == " " then
              lastWord = 0 -- save x of last word
            end
            x = x + font.chars[letter].xadvance
            
            if self._width and (x * scale) > self._width then
              -- jump to a new line
              --print ("NEW LINE")
              x = 0
              y = y + info.lineHeight
              local init = self.numChildren - lastWord + 1
              for i = init, self.numChildren do
                local glyph = self[i]
                local wrapped = glyph.chr -- current char
                x = kern(x, last .. wrapped)
                glyph.x = scale * (font.chars[wrapped].xoffset + x)
                glyph.y = scale * (font.chars[wrapped].yoffset + y)
                glyph.xScale = scale
                glyph.yScale = scale
                glyph._x = glyph.x -- orginal offset from self's x
                glyph._y = glyph.y -- orginal offset from self's y
                x = x + font.chars[wrapped].xadvance
                last = wrapped
              end
            end
            
          end
          prevLetter = letter
        end
      end
      self:anchor()
      self:justify()    
    end -- end of render

    instance = addPropertyUpdate(instance)
    function instance:propertyUpdate(event)
      if event.key == "text" then
        self.text = event.value
        self:render()    
      elseif event.key == "anchorX" then
        self.anchorX = event.value
        self:anchor()
      elseif event.key == "anchorY" then
        self.anchorY = event.value
        self:anchor()    
      elseif event.key == "align" then
        self.align = event.value
        self:justify()      
      elseif event.key == "fontSize" then
        self.fontSize = event.value
        self:render()       
      elseif event.key == "width" then
        self._width = event.value
        self:render()     
      elseif event.key == "x" then
        self._x = event.value
        self.x = self._x
      elseif event.key == "y" then
        self._y = event.value
        self.y = self._y
      elseif event.key == "size" then
        self.size = event.value
        self:render(true)
      elseif event.key == "fill" then
        self.fill = event.value
        self:render()
      end
    end

    function instance:finalize(event)
      self:removeEventListener("propertyUpdate")
    end

    -- set options
    instance.align    = options.align or "left"
    instance.fontSize = options.fontSize or 24
    instance.size     = options.size or instance.fontSize
    instance.x        = options.x or 0
    instance.y        = options.y or 0
    instance._x, instance._y = instance.x, instance.y
    instance._width   = options.width
    instance.text     = options.text
    instance.fill     = {1}
    
    instance:render()
    
    -- add listeners
    instance:addEventListener("propertyUpdate")
    instance:addEventListener("finalize")
    
    function instance:updateSize(newSize)
        if newSize and type(newSize) == "number" then
            local scaleFactor = newSize / self.fontSize
            for i = 1, self.numChildren do
                self[i].xScale = scaleFactor
                self[i].yScale = scaleFactor
            end
            self.fontSize = newSize
        end
    end
    
    return instance

end -- end of 'M.newText()' function

return M
1 Like