Skip to content

Commit 8f61064

Browse files
committed
Added descriptorFormat parameter. #1
1 parent ba45e48 commit 8f61064

File tree

7 files changed

+193
-45
lines changed

7 files changed

+193
-45
lines changed

README.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ converting a more user-friendly bitmap format to the pretty well-supported
1717
BMFont format.
1818

1919
This program was made as a tool for the [LÖVE](https://love2d.org/) game
20-
framework, but if your game engine understands BMFont files then this program
21-
may very well be useful for you.
20+
framework, but if your game engine or framework understands BMFont files
21+
(e.g. [Unity](https://github.com/litefeel/Unity-BitmapFontImporter)
22+
or [MonoGame](https://www.monogameextended.net/docs/features/bitmap-font/bitmap-font))
23+
then this program may very well be useful for you.
2224

2325
See the [full README](https://raw.githubusercontent.com/ReFreezed/ReFreezedBitmapFontConverter/master/build/README.txt) for more info and documentation,
2426
or download the [latest release](https://github.com/ReFreezed/ReFreezedBitmapFontConverter/releases/latest).
@@ -31,7 +33,7 @@ $ RbmfConverter.exe fontSources/coolPixelFont.rbmf --outdir fonts
3133

3234
**Input image:**
3335

34-
![input font](misc/exampleFont.png)
36+
![input font](misc/exampleFontInput.png)
3537

3638
**Input descriptor:**
3739
```ini
@@ -89,3 +91,33 @@ bothways=Vv .,_ -1
8991
# Other
9092
forward=r a -1
9193
```
94+
95+
**Output image:**
96+
97+
![output font](misc/exampleFontOutput.png)
98+
99+
**Output descriptor:**
100+
```
101+
info face="" size=10 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=0 padding=1,1,1,1 spacing=0,0 outline=0 CUSTOM_lineHeight=1.2
102+
common lineHeight=10 base=10 scaleW=150 scaleH=68 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0
103+
page id=0 file="coolPixelFont_0.png"
104+
chars count=129
105+
char id=36 x=0 y=0 width=7 height=12 xoffset=0 yoffset=0 xadvance=6 page=0 chnl=15
106+
char id=47 x=7 y=0 width=6 height=12 xoffset=0 yoffset=0 xadvance=5 page=0 chnl=15
107+
char id=92 x=13 y=0 width=6 height=12 xoffset=0 yoffset=0 xadvance=5 page=0 chnl=15
108+
(...)
109+
char id=95 x=25 y=62 width=6 height=3 xoffset=0 yoffset=9 xadvance=5 page=0 chnl=15
110+
char id=46 x=31 y=62 width=3 height=3 xoffset=0 yoffset=8 xadvance=3 page=0 chnl=15
111+
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=4 page=0 chnl=15
112+
kernings count=84
113+
kerning first=44 second=84 amount=-1
114+
kerning first=44 second=86 amount=-1
115+
kerning first=44 second=118 amount=-1
116+
(...)
117+
kerning first=120 second=84 amount=-1
118+
kerning first=121 second=84 amount=-1
119+
kerning first=122 second=84 amount=-1
120+
121+
```
122+
123+
Note that XML files can also be exported.

build/Changelog.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
Changelog
22
ReFreezed Bitmap Font converter
33

4+
v1.3 (2023-01-25)
5+
- Added [out] parameter 'descriptorFormat' for specifying the file format of the output descriptor (text or xml).
6+
47
v1.2 (2021-04-12)
58
- Added character filters for limiting what glyphs gets outputted.
6-
- Added 'imageFile' input parameter for specifying the filename of the input image.
7-
- Added "builtin" as a special value for the fontFile parameter.
9+
- Added [in] parameter 'imageFile' for specifying the filename of the input image.
10+
- Added "builtin" as a special value for the 'fontFile' parameter.
811
- Added --silent option for disabling output to stdout.
912
- More filename parameters can contain variables.
1013
- Better error messages when encountering invalid UTF-8 data.

build/README.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ File structure:
178178
# parameter!
179179
fileDescriptor=filename
180180

181+
# Format of the outputted BMFont descriptor. Possible values are 'text' or
182+
# 'xml'. (Default: text)
183+
descriptorFormat=format
184+
181185
# Color of the glyphs (multiplied with the input pixels). The values
182186
# should be numbers between 0 and 1. The parameter has multiple formats.
183187
# Alpha is 1 if omitted. (Default: 1 1 1 1)
@@ -283,7 +287,8 @@ File structure:
283287
custom.lineHeight=1.2
284288
custom.isBig=true
285289
# These will show up at the end of the 'info' line in the BMFont file like this:
286-
# info face="" (...) CUSTOM_lineHeight=1.2 CUSTOM_isBig="true"
290+
# info face="" (...) CUSTOM_lineHeight=1.2 CUSTOM_isBig="true"
291+
# Note that non-numeric values will be encased in quotes while numbers won't.
287292

288293

289294
# Sections with numbers as names specify what glyphs are on each row in

build/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.2.0
1+
1.3.0

misc/exampleFontOutput.png

1.42 KB
Loading

src/main.lua2p

Lines changed: 146 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ local function OutputDescriptor()
9090
filenameImage = "",
9191
filenameDescriptor = "",
9292

93+
descriptorFormat = "text", -- "text"|"xml"
94+
9395
glyphColor = {1,1,1,1},
9496

9597
glyphPaddingU = 0,
@@ -555,6 +557,9 @@ function love.run()
555557
local path = normalizePath(v ~= "" and v or fileError(rbmfFile, "Path cannot be empty."))
556558
currentOutDescr.filenameDescriptor = processPathTemplate(rbmfFile, path)
557559

560+
elseif k == "descriptorFormat" then
561+
currentOutDescr.descriptorFormat = (v == "text" or v == "xml") and v or fileError(rbmfFile, "Invalid descriptor format '%s'. (Valid values: text, xml)", v)
562+
558563
elseif k == "glyphColor" then
559564
local c = currentOutDescr.glyphColor
560565
c[1], c[2], c[3], c[4] = fileAssert(rbmfFile, parseColor(v))
@@ -1172,6 +1177,16 @@ function love.run()
11721177
--
11731178
-- Write output images and descriptors
11741179
--
1180+
local function encodeXmlEntities(v)
1181+
return (v:gsub("[<>&\"']", {
1182+
["<"] = "&lt;",
1183+
[">"] = "&gt;",
1184+
["&"] = "&amp;",
1185+
['"'] = "&quot;",
1186+
["'"] = "&apos;",
1187+
}))
1188+
end
1189+
11751190
for outIndex, outDescr in ipairs(rbmfFile.outputDescriptors) do
11761191
-- @Feature: Automatic kerning calculations?
11771192

@@ -1273,44 +1288,98 @@ function love.run()
12731288
-- Start writing descriptor.
12741289
local out_descriptorBuffer = {}
12751290

1276-
table.insert(out_descriptorBuffer, F(
1277-
-- @Polish: Output correct value for 'smooth' and 'aa'. (Is this always possible, or ever useful?)
1278-
-- @Polish: Maybe have a way of specifying 'bold' and 'italic' (though it's probably useless information).
1279-
-- @Polish: We can do more here, but it's all probably useless!
1280-
'info face="" size=%d bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=0 padding=%d,%d,%d,%d spacing=%d,%d outline=%d',
1281-
in_lineHeight, -- size
1282-
outDescr.glyphPaddingU, outDescr.glyphPaddingR, outDescr.glyphPaddingD, outDescr.glyphPaddingL, -- padding
1283-
outDescr.glyphSpacingH, outDescr.glyphSpacingV, -- spacing
1284-
outDescr.outlineWidth -- outline
1285-
))
1286-
for _, k in ipairs(outDescr.customValues) do
1287-
local v = outDescr.customValues[k]
1288-
table.insert(out_descriptorBuffer, F(" CUSTOM_%s=", k))
1289-
table.insert(out_descriptorBuffer, v:find"^%-?%d*%.?%d+$" and v or F('"%s"', v))
1291+
if outDescr.descriptorFormat == "xml" then
1292+
table.insert(out_descriptorBuffer, '<?xml version="1.0"?>\n')
1293+
table.insert(out_descriptorBuffer, "<font>\n")
1294+
end
1295+
1296+
if outDescr.descriptorFormat == "text" then
1297+
table.insert(out_descriptorBuffer, F(
1298+
-- @Polish: Output correct value for 'smooth' and 'aa'. (Is this always possible, or ever useful?)
1299+
-- @Polish: Maybe have a way of specifying 'bold' and 'italic' (though it's probably useless information).
1300+
-- @Polish: We can do more here, but it's all probably useless!
1301+
'info face="" size=%d bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=0 padding=%d,%d,%d,%d spacing=%d,%d outline=%d',
1302+
in_lineHeight, -- size
1303+
outDescr.glyphPaddingU, outDescr.glyphPaddingR, outDescr.glyphPaddingD, outDescr.glyphPaddingL, -- padding
1304+
outDescr.glyphSpacingH, outDescr.glyphSpacingV, -- spacing
1305+
outDescr.outlineWidth -- outline
1306+
))
1307+
for _, k in ipairs(outDescr.customValues) do
1308+
local v = outDescr.customValues[k]
1309+
table.insert(out_descriptorBuffer, F(" CUSTOM_%s=", k)) -- Note: Characters used in 'k' should already be validated.
1310+
table.insert(out_descriptorBuffer, v:find"^%-?%d*%.?%d+$" and v or F('"%s"', v))
1311+
end
1312+
table.insert(out_descriptorBuffer, "\n")
1313+
1314+
elseif outDescr.descriptorFormat == "xml" then
1315+
table.insert(out_descriptorBuffer, F(
1316+
-- @Polish: See above!
1317+
'<info face="" size="%d" bold="0" italic="0" charset="" unicode="1" stretchH="100" smooth="0" aa="0" padding="%d,%d,%d,%d" spacing="%d,%d" outline="%d"',
1318+
in_lineHeight, -- size
1319+
outDescr.glyphPaddingU, outDescr.glyphPaddingR, outDescr.glyphPaddingD, outDescr.glyphPaddingL, -- padding
1320+
outDescr.glyphSpacingH, outDescr.glyphSpacingV, -- spacing
1321+
outDescr.outlineWidth -- outline
1322+
))
1323+
for _, k in ipairs(outDescr.customValues) do
1324+
local v = outDescr.customValues[k]
1325+
table.insert(out_descriptorBuffer, F(' CUSTOM_%s="%s"', k, encodeXmlEntities(v))) -- Note: Characters used in 'k' should already be validated.
1326+
end
1327+
table.insert(out_descriptorBuffer, "/>\n")
1328+
1329+
else
1330+
error(outDescr.descriptorFormat)
12901331
end
1291-
table.insert(out_descriptorBuffer, "\n")
12921332

12931333
local out_lineHeight = (
12941334
in_lineHeight
12951335
+ (outDescr.paddingAffectsRenderSpacing and outDescr.glyphPaddingU+outDescr.glyphPaddingD or 0)
12961336
+ (outDescr.outlineAffectsRenderSpacing and outDescr.outlineWidth or 0)
12971337
)
12981338

1299-
table.insert(out_descriptorBuffer, F(
1300-
'common lineHeight=%d base=%d scaleW=%d scaleH=%d pages=%d packed=%d alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0\n',
1301-
out_lineHeight, -- lineHeight
1302-
out_lineHeight, -- base @Incomplete: Some way of specifying this. (Probably low priority.)
1303-
pageWidth, pageHeight, -- scaleW, scaleH
1304-
#out_imageDatas, -- pages
1305-
(outDescr.pack and 1 or 0) -- packed
1306-
))
1339+
if outDescr.descriptorFormat == "text" then
1340+
table.insert(out_descriptorBuffer, F(
1341+
'common lineHeight=%d base=%d scaleW=%d scaleH=%d pages=%d packed=%d alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0\n',
1342+
out_lineHeight, -- lineHeight
1343+
out_lineHeight, -- base @Incomplete: Some way of specifying this. (Probably low priority.)
1344+
pageWidth, pageHeight, -- scaleW, scaleH
1345+
#out_imageDatas, -- pages
1346+
(outDescr.pack and 1 or 0) -- packed
1347+
))
1348+
elseif outDescr.descriptorFormat == "xml" then
1349+
table.insert(out_descriptorBuffer, F(
1350+
'<common lineHeight="%d" base="%d" scaleW="%d" scaleH="%d" pages="%d" packed="%d" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0"/>\n',
1351+
out_lineHeight, -- lineHeight
1352+
out_lineHeight, -- base @Incomplete: Some way of specifying this. (Probably low priority.)
1353+
pageWidth, pageHeight, -- scaleW, scaleH
1354+
#out_imageDatas, -- pages
1355+
(outDescr.pack and 1 or 0) -- packed
1356+
))
1357+
else
1358+
error(outDescr.descriptorFormat)
1359+
end
13071360

13081361
-- Write glyphs.
1309-
for page = 1, #out_imageDatas do
1310-
table.insert(out_descriptorBuffer, F('page id=%d file="%s"\n', page-1, outputImageFilenameBaseToFilename(outDescr.filenameImage, page)))
1362+
if outDescr.descriptorFormat == "text" then
1363+
for page = 1, #out_imageDatas do
1364+
table.insert(out_descriptorBuffer, F('page id=%d file="%s"\n', page-1, outputImageFilenameBaseToFilename(outDescr.filenameImage, page)))
1365+
end
1366+
elseif outDescr.descriptorFormat == "xml" then
1367+
table.insert(out_descriptorBuffer, "<pages>\n")
1368+
for page = 1, #out_imageDatas do
1369+
table.insert(out_descriptorBuffer, F('\t<page id="%d" file="%s"/>\n', page-1, encodeXmlEntities(outputImageFilenameBaseToFilename(outDescr.filenameImage, page))))
1370+
end
1371+
table.insert(out_descriptorBuffer, "</pages>\n")
1372+
else
1373+
error(outDescr.descriptorFormat)
13111374
end
13121375

1313-
table.insert(out_descriptorBuffer, F('chars count=%d\n', #glyphs))
1376+
if outDescr.descriptorFormat == "text" then
1377+
table.insert(out_descriptorBuffer, F('chars count=%d\n', #glyphs))
1378+
elseif outDescr.descriptorFormat == "xml" then
1379+
table.insert(out_descriptorBuffer, F('<chars count="%d">\n', #glyphs))
1380+
else
1381+
error(outDescr.descriptorFormat)
1382+
end
13141383

13151384
-- for _, data in ipairs(out_imageDatas) do data:mapPixel(function(x,y, r,g,b,a) return 1, 0, 0, 1 end) end -- DEBUG: Show areas we don't paste glyphs in.
13161385

@@ -1417,16 +1486,35 @@ function love.run()
14171486
+ (outDescr.outlineAffectsRenderSpacing and outDescr.outlineWidth or 0)
14181487
)
14191488
)
1420-
table.insert(out_descriptorBuffer, F(
1421-
'char id=%d x=%d y=%d width=%d height=%d xoffset=%d yoffset=%d xadvance=%d page=%d chnl=%d\n',
1422-
glyphInfo.cp, -- id
1423-
glyphInfo.outX, glyphInfo.outY, -- x, y
1424-
glyphInfo.outW, glyphInfo.outH, -- width, height
1425-
glyphInfo.inOffsetX, glyphInfo.inOffsetY, -- xoffset, yoffset
1426-
xAdvance, -- xadvance
1427-
glyphInfo.outPage-1, -- page
1428-
(outDescr.pack and 2^(glyphInfo.outChannel-1) or 15) -- chnl
1429-
))
1489+
if outDescr.descriptorFormat == "text" then
1490+
table.insert(out_descriptorBuffer, F(
1491+
'char id=%d x=%d y=%d width=%d height=%d xoffset=%d yoffset=%d xadvance=%d page=%d chnl=%d\n',
1492+
glyphInfo.cp, -- id
1493+
glyphInfo.outX, glyphInfo.outY, -- x, y
1494+
glyphInfo.outW, glyphInfo.outH, -- width, height
1495+
glyphInfo.inOffsetX, glyphInfo.inOffsetY, -- xoffset, yoffset
1496+
xAdvance, -- xadvance
1497+
glyphInfo.outPage-1, -- page
1498+
(outDescr.pack and 2^(glyphInfo.outChannel-1) or 15) -- chnl
1499+
))
1500+
elseif outDescr.descriptorFormat == "xml" then
1501+
table.insert(out_descriptorBuffer, F(
1502+
'\t<char id="%d" x="%d" y="%d" width="%d" height="%d" xoffset="%d" yoffset="%d" xadvance="%d" page="%d" chnl="%d"/>\n',
1503+
glyphInfo.cp, -- id
1504+
glyphInfo.outX, glyphInfo.outY, -- x, y
1505+
glyphInfo.outW, glyphInfo.outH, -- width, height
1506+
glyphInfo.inOffsetX, glyphInfo.inOffsetY, -- xoffset, yoffset
1507+
xAdvance, -- xadvance
1508+
glyphInfo.outPage-1, -- page
1509+
(outDescr.pack and 2^(glyphInfo.outChannel-1) or 15) -- chnl
1510+
))
1511+
else
1512+
error(outDescr.descriptorFormat)
1513+
end
1514+
end
1515+
1516+
if outDescr.descriptorFormat == "xml" then
1517+
table.insert(out_descriptorBuffer, F('</chars>\n', #glyphs))
14301518
end
14311519

14321520
-- Add glyph outlines.
@@ -1705,7 +1793,13 @@ function love.run()
17051793
if kerningPairs[1] then
17061794
table.sort(kerningPairs)
17071795

1708-
table.insert(out_descriptorBuffer, F('kernings count=%d\n', #kerningPairs))
1796+
if outDescr.descriptorFormat == "text" then
1797+
table.insert(out_descriptorBuffer, F('kernings count=%d\n', #kerningPairs))
1798+
elseif outDescr.descriptorFormat == "xml" then
1799+
table.insert(out_descriptorBuffer, F('<kernings count="%d">\n', #kerningPairs))
1800+
else
1801+
error(outDescr.descriptorFormat)
1802+
end
17091803

17101804
for _, pair in ipairs(kerningPairs) do
17111805
local first, second = pair:match(F("(%s)(%s)", utf8.charpattern, utf8.charpattern))
@@ -1714,7 +1808,17 @@ function love.run()
17141808

17151809
local offset = rbmfFile.kernings[pair]
17161810

1717-
table.insert(out_descriptorBuffer, F('kerning first=%d second=%d amount=%d\n', first, second, offset))
1811+
if outDescr.descriptorFormat == "text" then
1812+
table.insert(out_descriptorBuffer, F('kerning first=%d second=%d amount=%d\n', first, second, offset))
1813+
elseif outDescr.descriptorFormat == "xml" then
1814+
table.insert(out_descriptorBuffer, F('\t<kerning first="%d" second="%d" amount="%d"/>\n', first, second, offset))
1815+
else
1816+
error(outDescr.descriptorFormat)
1817+
end
1818+
end
1819+
1820+
if outDescr.descriptorFormat == "xml" then
1821+
table.insert(out_descriptorBuffer, '</kernings>\n')
17181822
end
17191823
end
17201824

@@ -1752,6 +1856,10 @@ function love.run()
17521856
end
17531857

17541858
if outDescr.filenameDescriptor ~= "" then
1859+
if outDescr.descriptorFormat == "xml" then
1860+
table.insert(out_descriptorBuffer, '</font>\n')
1861+
end
1862+
17551863
local pathObj = Path(outDescr.filenameDescriptor, rbmfFile.outDirectory)
17561864
local dir, filename = pathObj:getDirectoryAndFilename()
17571865

0 commit comments

Comments
 (0)