diff --git a/config/font.cfg b/config/font.cfg index dabf6f20a..9c5281ae9 100644 --- a/config/font.cfg +++ b/config/font.cfg @@ -1,14 +1,25 @@ -// Execute dedicated game fonts here +// Configure dedicated game fonts here -loopfiles f "data/interface/font" cfg [ - exec (+s "data/interface/font/" $f ".cfg") +loopfiles f "data/interface/font" "ttf" [ + registerfont (+s "data/interface/font/" $f ".ttf") ] -// Change the two following lines to define default font -fontalias "default" "main" -fontalias "default.ol" "main.ol" +// font "name" "family name" + // fontweight -3..6 // < 0 = thinner, > 0 = thicker + // fontstretch -4..4 // < 0 = tighter, > 0 = wider ; many fonts don't support this + // fontstyle 0|1|2 // 0 = normal, 1 = oblique, 2 = italic + // fontsmallcaps 0|1 + // fontfeatures [...features] // OpenType features (in CSS format) + // fontvariations "variations" // for variable fonts; format: "AXIS1=VALUE,AXIS2=VALUE..." -// Shorthand aliases -fontalias "def" "default" -fontalias "def.ol" "default.ol" +font "default" "Inter Variable, Sans" + fontfeatures ["ss04", "cv12", "cv13", "tnum"] +font "mono" "Roboto Mono, Monospace" +font "bold" "Jura, Inter Variable, Sans" + fontfeatures ["liga", "calt", "zero"] + fontweight 3 +font "wide" "Tektur, Inter Variable, Sans" +font "modernpics" "ModernPictograms" + +uiFontList = [ default mono bold wide modernpics ] \ No newline at end of file diff --git a/config/glsl/hud.cfg b/config/glsl/hud.cfg index 59e5c4714..9f294c978 100644 --- a/config/glsl/hud.cfg +++ b/config/glsl/hud.cfg @@ -41,17 +41,14 @@ shader 0 "hudtext" [ colorscale = vcolor; } ] [ - uniform sampler2D tex0; - uniform vec4 textparams; + uniform sampler2DRect tex0; varying vec2 texcoord0; varying vec4 colorscale; fragdata(0) vec4 fragcolor; void main(void) { - float dist = texture2D(tex0, texcoord0).r; - float border = smoothstep(textparams.x, textparams.y, dist); - float outline = smoothstep(textparams.z, textparams.w, dist); - fragcolor = vec4(colorscale.rgb * outline, colorscale.a * border); + fragcolor = colorscale * texture2DRect(tex0, texcoord0); + if(fragcolor.a != 0) fragcolor.rgb /= fragcolor.a; } ] diff --git a/config/glsl/particle.cfg b/config/glsl/particle.cfg index 2345e2db7..1e018ee87 100644 --- a/config/glsl/particle.cfg +++ b/config/glsl/particle.cfg @@ -107,17 +107,14 @@ shader 0 "particletext" [ color = vec4(vcolor.rgb * ldrscale, vcolor.a); } ] [ - uniform vec4 textparams; - uniform sampler2D tex0; + uniform sampler2DRect tex0; varying vec4 color; varying vec2 texcoord0; fragdata(0) vec4 fragcolor; void main(void) { - float dist = texture2D(tex0, texcoord0).r; - float border = smoothstep(textparams.x, textparams.y, dist); - float outline = smoothstep(textparams.z, textparams.w, dist); - fragcolor = vec4(color.rgb * outline, color.a * border); + fragcolor = color * texture2DRect(tex0, texcoord0); + if(fragcolor.a != 0) fragcolor.rgb /= fragcolor.a; } ] diff --git a/config/ui/hud/editstats.cfg b/config/ui/hud/editstats.cfg index ae5165c15..9cc38e98b 100644 --- a/config/ui/hud/editstats.cfg +++ b/config/ui/hud/editstats.cfg @@ -335,8 +335,8 @@ uiLiteMenu "edithud" [ uigrid 2 $uiPad:3XL $uiPad:L [ uihlist $uiPad:XL [ uitext "WTR" 0.5 - uitext (tabify (format "%1k" $editstatwtr) 1) 0.5 - uitext (tabify (format "(%1%%)" $editstatvtr) 2) 0.5 + uitext (format "%1k^t" $editstatwtr) 0.5 + uitext (format "(%1%%)^t^t" $editstatvtr) 0.5 ] uihlist $uiPad:XL [ uitext "WVT" 0.5 diff --git a/config/ui/hud/gamehud.cfg b/config/ui/hud/gamehud.cfg index 733d6af45..25faed0f0 100644 --- a/config/ui/hud/gamehud.cfg +++ b/config/ui/hud/gamehud.cfg @@ -1135,12 +1135,12 @@ newui "scoreboard" [ uispace $uiPad:M $uiPad:M- [ uiFancyText "wide" @@@( at "FFA Aesir Vanir" $arg1 - ) 1.4 @@@(INT:TRANS ( + ) 1.25 @@@(INT:TRANS ( at [0x40D060 0x4060D0 0xD04040] $arg1 ) % 80) ] ; uialign- @@(? $bflip 1 -1) -1 uispace $uiPad:O5 (*f $uiPad:O5 2.9) [ - uiFancyText "wide" (getteamscore @@@arg1) 1 @@@( + uiFancyText "wide" (getteamscore @@@arg1) 0.85 @@@( at [0x40D060 0x4060D0 0xD04040] $arg1 ) ] ; uialign- @@(? $bflip -1 1) -1 @@ -1300,7 +1300,9 @@ newui "scoreboard" [ uiFastImg (fade (? $arg1 0.5 0.25)) "hud/" "shelf" "" $uiPad:5XL uiFastImg "hud/" "glow" "" $uiPad:5XL push n (+ $n 1) [ - uifontcolortext "def.ol" $n (|A! (? $arg1 0xB0 0x60)) 0.55 + uifontoutline 1 0x7F [ + uifontcolortext "default" $n (|A! (? $arg1 0xB0 0x60)) 0.55 + ] ] ] ; uialign- -1 ] diff --git a/config/ui/hud/geo_prefab.cfg b/config/ui/hud/geo_prefab.cfg index e1cc04f6f..e234d4d64 100644 --- a/config/ui/hud/geo_prefab.cfg +++ b/config/ui/hud/geo_prefab.cfg @@ -24,7 +24,9 @@ uiLiteMenu "geo_prefab" [ uiclamp*e uispace $uiPad:S $uiPad:O3 [ uialign -1 -1 - uifontcolortext "def.ol" $i (|A! 0x98) 0.55 + uifontoutline 1 0x7F [ + uifontcolortext "default" $i (|A! 0x98) 0.55 + ] ] uihover [ if (!= $i $.UI_obrsel) [ diff --git a/config/ui/hud/mapmodel_browser.cfg b/config/ui/hud/mapmodel_browser.cfg index 66471cd54..975b8b42b 100644 --- a/config/ui/hud/mapmodel_browser.cfg +++ b/config/ui/hud/mapmodel_browser.cfg @@ -54,11 +54,11 @@ uiLiteMenu "mapmodel_browser" [ uimodelpreview (mapmodelname $i 1) "mapmodel" uiclamp*e uispace $uiPad:S $uiPad:O3 [ - uifonttext "def.ol" $i 0.55 + uifontoutline 1 0x7F [ uifonttext "default" $i 0.55 ] ] ; uialign- -1 -1 if (iskeyheld "LSHIFT") [ uispace $uiPad:S 0 [ - uifonttext "modernpics.ol" "R" 0.65 + uifontoutline 1 0x7F [uifonttext "modernpics" "R" 0.65 ] ] ; uialign- -1 1 uirelease [ clearmodel (+s "mapmodel/" (mapmodelname $i)) ] ] [ @@ -77,14 +77,14 @@ uiLiteMenu "mapmodel_browser" [ uimodelpreview (mapmodelname $i 1) "mapmodel" uiclamp*e uispace $uiPad:S $uiPad:O3 [ - uifontcolortext "def.ol" $i (|A! 0xA0) 0.55 + uifontoutline 1 0x7F [ uifontcolortext "default" $i (|A! 0xA0) 0.55 ] ] ; uialign- -1 -1 uihover [ .mm_selidx = $n ; uiSetMillis ] uioutline $c_line1 $.z $.z ] if (mapmodelloaded $i) [ uispace $uiPad:S $uiPad:O3 [ - uifonttext "modernpics.ol" "%" 0.55 + uifontoutline 1 0x7F [ uifonttext "modernpics" "%" 0.55 ] ] ; uialign- 1 -1 ] uiHoverOnce [ uiHoverSound ] diff --git a/config/ui/hud/texture_browser.cfg b/config/ui/hud/texture_browser.cfg index 7ff27d299..b767ba829 100644 --- a/config/ui/hud/texture_browser.cfg +++ b/config/ui/hud/texture_browser.cfg @@ -29,7 +29,9 @@ uiLiteMenu "texture_browser" [ ] uispace $uiPad:S $uiPad:O3 [ uialign -1 -1 - uifontcolortext "def.ol" $i (|A! 0x98) 0.55 + uifontoutline 1 0x7F [ + uifontcolortext "default" $i (|A! 0x98) 0.55 + ] ] ] uipress [ uiSetMillis ] @@ -83,7 +85,7 @@ uiLiteMenu "texture_browser" [ uiclip 0 $uiPad:6XL [ uicolortext "/" $c_gray 1.2 ] ] // text takes up a lot of vertical space, have to "cut" it uifill $uiPad:DXS 0 [ - uifonttext "def.ol" (max $.tb_totalpg 1) 0.7 + uifonttext "mono" (max $.tb_totalpg 1) 0.7 uialign- -1 1 ] uiclamp*y @@ -110,7 +112,9 @@ uiLiteMenu "texture_browser" [ uislotview $i uiclamp*e uispace $uiPad:S $uiPad:O3 [ - uifontcolortext "def.ol" $i (|A! 0xA0) 0.55 + uifontoutline 1 0x7F [ + uifontcolortext "default" $i (|A! 0xA0) 0.55 + ] ] ; uialign- -1 -1 uirelease [ settex (getslottex $i) diff --git a/config/ui/lib.cfg b/config/ui/lib.cfg index af4ad0f21..5f65c9f49 100644 --- a/config/ui/lib.cfg +++ b/config/ui/lib.cfg @@ -810,23 +810,21 @@ uiButton = [ // uiFancyText FONT TEXT SIZE 0xCOLOR TRANSPARENCY uiFancyText = [ - if (=s $arg1 "") [ arg1 = "def" ] + if (=s $arg1 "") [ arg1 = "default" ] if $arg4 [] [ arg4 = $c_white ] if (< $numargs 5) [ arg5 = 1 ] - uigroup [ - uioffset $uiPad:O1- $uiPad:O1 [ - uifontcolortext [@arg1.ol] (stripcolors $arg2) (|A (*fA $arg5 0x20)) $arg3 - uifontcolortext $arg1 (stripcolors $arg2) (|A (*fA $arg5)) $arg3 - ] - uioffset $uiPad:O1 $uiPad:O1- [ - uifontcolortext $arg1 $arg2 (|A (*fA $arg5) $arg4) $arg3 + uifont $arg1 [ + uifontoutline 1 0x20 [ + uishadow 255 [ + uicolortext $arg2 (|A (*fA $arg5) $arg4) $arg3 + ] ] ] ] // uiEmbossText FONT TEXT SIZE 0xCOLOR uiEmbossText = [ - if (=s $arg1 "") [ arg1 = "def" ] + if (=s $arg1 "") [ arg1 = "default" ] uigroup [ uioffset $uiPad:O2- $uiPad:O2 [ uifontcolortext $arg1 (stripcolors $arg2) 0 $arg3 @@ -866,9 +864,11 @@ uiVerSld = [ uiclamp*x ] ] ; uiclamp-x - uivlist $uiPad:M- [ - loop i (strlen $arg6) [ - uifontcolortext "def.ol" (substr $arg6 $i 1) (|A! (? $arg9 0xA0 0x60)) 0.65 + uifontoutline 1 0xFF [ + uivlist $uiPad:M- [ + loop i (strlen $arg6) [ + uifontcolortext "default" (substr $arg6 $i 1) (|A! (? $arg9 0xA0 0x60)) 0.65 + ] ] ] ] @@ -897,7 +897,9 @@ uiHorSld = [ uiclamp*y ] ] ; uiclamp-y - uifontcolortext "def.ol" $arg6 (|A! (? $arg9 0xA0 0x60)) 0.65 + uifontoutline 1 0xFF [ + uifontcolortext "default" $arg6 (|A! (? $arg9 0xA0 0x60)) 0.65 + ] ] uiclamp*e ] @@ -925,9 +927,11 @@ uiVerColorSld = [ uiclamp*x ] ] ; uiclamp-x - uivlist $uiPad:M- [ - loop i (strlen $$arg1) [ - uifontcolortext "wide.ol" (substr $$arg1 $i 1) (|A! (? $arg9 0xA0 0x60)) 0.7 + uifontoutline 1 0xFF [ + uivlist $uiPad:M- [ + loop i (strlen $$arg1) [ + uifontcolortext "wide" (substr $$arg1 $i 1) (|A! (? $arg9 0xA0 0x60)) 0.7 + ] ] ] ] @@ -957,7 +961,9 @@ uiHorColorSld = [ uiclamp*y ] ] ; uiclamp-y - uifontcolortext "wide.ol" $$arg1 (|A! (? $arg9 0xA0 0x60)) 0.7 + uifontoutline 1 0xFF [ + uifontcolortext "wide" $$arg1 (|A! (? $arg9 0xA0 0x60)) 0.7 + ] ] uiclamp*e ] @@ -1185,6 +1191,7 @@ uiEntColor24B = [ uioutline (? $isfocused $c_red $c_line1) uipress [ local newvarcur + resetcursorblink newvarcur = (escape (+s "#" $type "bind" " " $action)) if (=s $newvarcur $.UI_KBvarcur) [ (+s $type "bind") MOUSELEFT $action @@ -1207,6 +1214,7 @@ uiEntColor24B = [ .UI_KBvarcur = "" ] [ if (!=s "MOUSELEFT" $uikeycode) [ + resetcursorblink (+s $type "bind") $uikeycode $action ] [ .UI_KBvarcur = "" @@ -1296,6 +1304,7 @@ uiKeyField = [ uitarget $width 0 [ uioutline (? $isfocused $c_red $c_line1) uipress [ + resetcursorblink if (< $uicursorindex 0) [ .UI_KBcaret = 0 ] [ @@ -1314,6 +1323,7 @@ uiKeyField = [ .UI_KBcaret = -1 uisettextinput 0 ] [ + resetcursorblink cases $uikeycode "DELETE" [ $.UI_KBvarcur = (clamp (.delzeroes (strsplice $$.UI_KBvarcur "" $.UI_KBcaret 1)) $min_ $max_) do $onchange @@ -1427,12 +1437,12 @@ uiTextField = [ uitarget $width 0 [ uioutline (? $isfocused $c_red $c_line1) uipress [ + resetcursorblink if (< $uicursorindex 0) [ - .UI_KBcaret = (? (> $align 0) 0 (strlen $$var)) + .UI_KBcaret = (? (> $align 0) 0 (char2byteindex $$var (strlen $$var))) ] [ .UI_KBcaret = (? (> (strlen $$var) 0) $uicursorindex 0) ] - .UI_KBvarcur = $arg1 ] @@ -1448,37 +1458,49 @@ uiTextField = [ .UI_KBcaret = -1 uisettextinput 0 ] [ + resetcursorblink cases $uikeycode "DELETE" [ - $.UI_KBvarcur = (strsplice $$.UI_KBvarcur "" $.UI_KBcaret 1) + $.UI_KBvarcur = (strsplice $$.UI_KBvarcur "" (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) 1) do $onchange ] "BACKSPACE" [ if (> $.UI_KBcaret 0) [ - $.UI_KBvarcur = (strsplice $$.UI_KBvarcur "" (- $.UI_KBcaret 1) 1) - -- .UI_KBcaret + local ix_c + ix_c = (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) + $.UI_KBvarcur = (strsplice $$.UI_KBvarcur "" (- $ix_c 1) 1) + .UI_KBcaret = (char2byteindex $$.UI_KBvarcur (- $ix_c 1)) ] do $onchange ] "LEFT" [ + // move cursor to the left by 1 character (not byte!) if (> $.UI_KBcaret 0) [ - -- .UI_KBcaret + local ix_c + ix_c = (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) + .UI_KBcaret = (char2byteindex $$.UI_KBvarcur (- $ix_c 1)) ] ] "RIGHT" [ - if (&& [>= $.UI_KBcaret 0] [< $.UI_KBcaret (strlen $$.UI_KBvarcur)]) [ - ++ .UI_KBcaret + // move cursor to the right by 1 character (not byte!) + if (&& [>= $.UI_KBcaret 0] [< (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) (strlen $$.UI_KBvarcur)]) [ + local ix_c + ix_c = (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) + .UI_KBcaret = (char2byteindex $$.UI_KBvarcur (+ $ix_c 1)) ] ] () [ - local inputlen pastestr + local inputlen pastestr pastebytelen if (&& [=s "V" $uikeycode] [|| [iskeyheld "LCTRL"] [iskeyheld "RCTRL"]]) [ // CTRL-V inputlen = (clamp (strlen $getclipboard) 0 (- $maxstrlen (strlen $$.UI_KBvarcur))) pastestr = (substr $getclipboard 0 $inputlen) - $.UI_KBvarcur = (substr (strsplice $$.UI_KBvarcur $pastestr $.UI_KBcaret 0) 0 $maxstrlen) - .UI_KBcaret = (min $maxstrlen (+ $.UI_KBcaret $inputlen)) + pastebytelen = (strbytelen $pastestr) + $.UI_KBvarcur = (strsplice $$.UI_KBvarcur $pastestr (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) 0) + .UI_KBcaret = (+ $.UI_KBcaret $pastebytelen) do $onchange ] [ if (&& [!=s "" $uitextinput] [< (strlen $$.UI_KBvarcur) $maxstrlen]) [ inputlen = (strlen $uitextinput) - $.UI_KBvarcur = (substr (strsplice $$.UI_KBvarcur $uitextinput $.UI_KBcaret 0) 0 $maxstrlen) - .UI_KBcaret = (min $maxstrlen (+ $.UI_KBcaret $inputlen)) + $.UI_KBvarcur = (substr (strsplice $$.UI_KBvarcur $uitextinput (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) 0) 0 $maxstrlen) + .UI_KBcaret = (char2byteindex $$.UI_KBvarcur (min $maxstrlen (+ (byte2charindex $$.UI_KBvarcur $.UI_KBcaret) $inputlen))) + do $onchange + ] ] ] diff --git a/config/ui/libnew.cfg b/config/ui/libnew.cfg index a8950ef09..12af21429 100644 --- a/config/ui/libnew.cfg +++ b/config/ui/libnew.cfg @@ -170,7 +170,9 @@ uiSliderH = [ uiFastImgStretched "" "shadow2" "" "" [ uiclamp.x ] uiclamp*e if $arg9 [ arg10 = 0xA0FFFFFF ] [ arg10 = 0x60FFFFFF ] - uiFontColorText "def.ol" $arg6 0.65 $arg10 + uifontoutline 1 0xFF [ + uiFontColorText "default" $arg6 0.65 $arg10 + ] ] ; uiclamp-y ] ] ; uiclamp-y diff --git a/config/ui/menus/server_browser.cfg b/config/ui/menus/server_browser.cfg index 0126b8b81..e9cc89dda 100644 --- a/config/ui/menus/server_browser.cfg +++ b/config/ui/menus/server_browser.cfg @@ -169,7 +169,7 @@ uiServer = [ uigroup [ uifill $uiPad:DS uiimage (+s "data/interface/ui/" (servinfomastermodeicon $arg1) ".png") $uiPad:DSS $uiPad:DSS - uispace 0 $uiPad:L [ uifonttext "wide.ol" (servinfomodename $arg1) 0.5 ] + uispace 0 $uiPad:L [ uifontoutline 1 0x7F [ uifonttext "wide" (servinfomodename $arg1) 0.5 ] ] uialign- 0 1 ] uivlist $uiPad:S [ diff --git a/config/ui/permanent.cfg b/config/ui/permanent.cfg index a4a9cd905..d44574ea6 100644 --- a/config/ui/permanent.cfg +++ b/config/ui/permanent.cfg @@ -27,9 +27,11 @@ newui "permanent" [ uispace $uiPad:4XL $uiPad:4XL [ uivlist $uiPad:3XL- [ uiFastImg "" "badge_tesseract" "" "" $uiPad:D2XL - uivlist $uiPad:M- [ - uifontcolortext "wide.ol" "TESSERACT" $c_cyan_t 0.6 - uifontcolortext "wide.ol" "ENGINE" $c_cyan_t 0.7 + uifontoutline 1 0x7F [ + uivlist $uiPad:M- [ + uifontcolortext "wide" "TESSERACT" $c_cyan_t 0.6 + uifontcolortext "wide" "ENGINE" $c_cyan_t 0.7 + ] ] ] ] diff --git a/config/ui/style.cfg b/config/ui/style.cfg index 0e839cb88..a597d0c3d 100644 --- a/config/ui/style.cfg +++ b/config/ui/style.cfg @@ -3,7 +3,16 @@ // Text Brightness & Default Paddings // /////////////////////////////////////////////////////////////////////////////// -textbright 95 +textcolor 0 0x40FF80 // green: player talk +textcolor 1 0x60A0FF // blue: "echo" command +textcolor 2 0xFFC040 // yellow: gameplay messages +textcolor 3 0xFF4040 // red: important errors +textcolor 4 0x808080 // gray +textcolor 5 0xC040C0 // magenta +textcolor 6 0xFF8000 // orange +textcolor 7 0xFFFFFF // white +textcolor 8 0x00FFFF // cyan +textcolor 9 0xFFC0CB // pink // ultra series uiPad:UXL = 1.2 // DXL * 10 diff --git a/source/Makefile b/source/Makefile index 6b588b1b6..e3f472f6f 100644 --- a/source/Makefile +++ b/source/Makefile @@ -56,8 +56,8 @@ CLIENT_LIBS= -F../bin/valhalla.app/Contents/Frameworks/ -framework SDL2 -framewo CLIENT_LIBS+= -framework SDL2_mixer -framework CoreAudio -framework AudioToolbox CLIENT_LIBS+= -framework AudioUnit -framework OpenGL -framework Cocoa -lz -Lenet -lenet else -CLIENT_INCLUDES= $(INCLUDES) -I/usr/X11R6/include `sdl2-config --cflags` -CLIENT_LIBS= -Lenet -lenet -L/usr/X11R6/lib -lX11 `sdl2-config --libs` -lSDL2_image -lSDL2_mixer -lz -lGL +CLIENT_INCLUDES= $(INCLUDES) -I/usr/X11R6/include `sdl2-config --cflags` `pkg-config --cflags pangocairo` `pkg-config --cflags fontconfig` +CLIENT_LIBS= -Lenet -lenet -L/usr/X11R6/lib -lX11 `sdl2-config --libs` -lSDL2_image -lSDL2_mixer `pkg-config --libs pangocairo` `pkg-config --libs fontconfig` -lz -lGL endif endif ifeq ($(PLATFORM),LINUX) @@ -224,12 +224,6 @@ server: libenet $(SERVER_OBJS) master: libenet $(MASTER_OBJS) $(CXX) $(CXXFLAGS) -o tess_master $(MASTER_OBJS) $(MASTER_LIBS) -shared/tessfont.o: shared/tessfont.c - $(CXX) $(CXXFLAGS) -c -o $@ $< `freetype-config --cflags` - -tessfont: shared/tessfont.o - $(CXX) $(CXXFLAGS) -o tessfont shared/tessfont.o `freetype-config --libs` -lz - ifneq (,$(findstring DARWIN,$(PLATFORM))) install: client cp tess_client ../bin/valhalla.app/Contents/MacOS/valhalla_universal @@ -268,9 +262,11 @@ shared/glemu.o: engine/sound.h shared/iengine.h shared/igame.h shared/stream.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h shared/stream.o: shared/command.h shared/glexts.h shared/glemu.h shared/stream.o: engine/sound.h shared/iengine.h shared/igame.h +shared/stream.o: shared/unicode.h shared/tools.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h shared/tools.o: shared/command.h shared/glexts.h shared/glemu.h shared/tools.o: engine/sound.h shared/iengine.h shared/igame.h +shared/tools.o: shared/unicode.h shared/zip.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h shared/zip.o: shared/command.h shared/glexts.h shared/glemu.h engine/sound.h shared/zip.o: shared/iengine.h shared/igame.h @@ -299,11 +295,13 @@ engine/command.o: shared/ents.h shared/command.h shared/glexts.h engine/command.o: shared/glemu.h engine/sound.h shared/iengine.h engine/command.o: shared/igame.h engine/world.h engine/octa.h engine/light.h engine/command.o: engine/texture.h engine/bih.h engine/model.h +engine/command.o: shared/unicode.h engine/console.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h engine/console.o: shared/ents.h shared/command.h shared/glexts.h engine/console.o: shared/glemu.h engine/sound.h shared/iengine.h engine/console.o: shared/igame.h engine/world.h engine/octa.h engine/light.h engine/console.o: engine/texture.h engine/bih.h engine/model.h +engine/console.o: shared/unicode.h engine/dynlight.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h engine/dynlight.o: shared/ents.h shared/command.h shared/glexts.h engine/dynlight.o: shared/glemu.h engine/sound.h shared/iengine.h @@ -468,84 +466,92 @@ engine/worldio.o: shared/igame.h engine/world.h engine/octa.h engine/light.h engine/worldio.o: engine/texture.h engine/bih.h engine/model.h game/ai.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/ai.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h -game/ai.o: engine/sound.h shared/iengine.h shared/igame.h game/projectile.h -game/ai.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h -game/ai.o: game/monster.h +game/ai.o: engine/sound.h shared/iengine.h shared/igame.h shared/unicode.h +game/ai.o: game/projectile.h game/weapon.h game/ai.h game/gamemode.h +game/ai.o: game/entity.h game/announcer.h game/monster.h game/gameclient.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/gameclient.o: shared/ents.h shared/command.h shared/glexts.h game/gameclient.o: shared/glemu.h engine/sound.h shared/iengine.h -game/gameclient.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/gameclient.o: game/gamemode.h game/entity.h game/monster.h game/ctf.h +game/gameclient.o: shared/igame.h shared/unicode.h game/projectile.h +game/gameclient.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/gameclient.o: game/announcer.h game/monster.h game/ctf.h game/gameclient.o: game/elimination.h game/entity.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/entity.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h game/entity.o: engine/sound.h shared/iengine.h shared/igame.h -game/entity.o: game/projectile.h game/weapon.h game/ai.h game/gamemode.h -game/entity.o: game/entity.h game/monster.h +game/entity.o: shared/unicode.h game/projectile.h game/weapon.h game/ai.h +game/entity.o: game/gamemode.h game/entity.h game/announcer.h game/monster.h game/game.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/game.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h -game/game.o: engine/sound.h shared/iengine.h shared/igame.h game/projectile.h -game/game.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h -game/game.o: game/monster.h +game/game.o: engine/sound.h shared/iengine.h shared/igame.h shared/unicode.h +game/game.o: game/projectile.h game/weapon.h game/ai.h game/gamemode.h +game/game.o: game/entity.h game/announcer.h game/monster.h game/render.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/render.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h game/render.o: engine/sound.h shared/iengine.h shared/igame.h -game/render.o: game/projectile.h game/weapon.h game/ai.h game/gamemode.h -game/render.o: game/entity.h game/monster.h +game/render.o: shared/unicode.h game/projectile.h game/weapon.h game/ai.h +game/render.o: game/gamemode.h game/entity.h game/announcer.h game/monster.h game/scoreboard.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/scoreboard.o: shared/ents.h shared/command.h shared/glexts.h game/scoreboard.o: shared/glemu.h engine/sound.h shared/iengine.h -game/scoreboard.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/scoreboard.o: game/gamemode.h game/entity.h game/monster.h +game/scoreboard.o: shared/igame.h shared/unicode.h game/projectile.h +game/scoreboard.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/scoreboard.o: game/announcer.h game/monster.h game/gameserver.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/gameserver.o: shared/ents.h shared/command.h shared/glexts.h game/gameserver.o: shared/glemu.h engine/sound.h shared/iengine.h -game/gameserver.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/gameserver.o: game/gamemode.h game/entity.h game/monster.h game/geoip.h -game/gameserver.o: game/ctf.h game/elimination.h game/extinfo.h -game/gameserver.o: game/aimanager.h game/announcer.h +game/gameserver.o: shared/igame.h shared/unicode.h game/projectile.h +game/gameserver.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/gameserver.o: game/announcer.h game/monster.h game/geoip.h game/ctf.h +game/gameserver.o: game/elimination.h game/extinfo.h game/aimanager.h game/waypoint.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/waypoint.o: shared/ents.h shared/command.h shared/glexts.h game/waypoint.o: shared/glemu.h engine/sound.h shared/iengine.h -game/waypoint.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/waypoint.o: game/gamemode.h game/entity.h game/monster.h +game/waypoint.o: shared/igame.h shared/unicode.h game/projectile.h +game/waypoint.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/waypoint.o: game/announcer.h game/monster.h game/monster.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/monster.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h game/monster.o: engine/sound.h shared/iengine.h shared/igame.h -game/monster.o: game/projectile.h game/weapon.h game/ai.h game/gamemode.h -game/monster.o: game/entity.h game/monster.h +game/monster.o: shared/unicode.h game/projectile.h game/weapon.h game/ai.h +game/monster.o: game/gamemode.h game/entity.h game/announcer.h game/monster.h game/weapon.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/weapon.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h game/weapon.o: engine/sound.h shared/iengine.h shared/igame.h -game/weapon.o: game/projectile.h game/weapon.h game/ai.h game/gamemode.h -game/weapon.o: game/entity.h game/monster.h +game/weapon.o: shared/unicode.h game/projectile.h game/weapon.h game/ai.h +game/weapon.o: game/gamemode.h game/entity.h game/announcer.h game/monster.h game/gamephysics.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/gamephysics.o: shared/ents.h shared/command.h shared/glexts.h game/gamephysics.o: shared/glemu.h engine/sound.h shared/iengine.h -game/gamephysics.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/gamephysics.o: game/gamemode.h game/entity.h game/monster.h +game/gamephysics.o: shared/igame.h shared/unicode.h game/projectile.h +game/gamephysics.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/gamephysics.o: game/announcer.h game/monster.h game/hud.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h game/hud.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h game/hud.o: engine/sound.h shared/iengine.h shared/igame.h engine/world.h game/hud.o: engine/octa.h engine/light.h engine/texture.h engine/bih.h -game/hud.o: engine/model.h game/game.h game/projectile.h game/weapon.h -game/hud.o: game/ai.h game/gamemode.h game/entity.h game/monster.h +game/hud.o: engine/model.h game/game.h shared/unicode.h game/projectile.h +game/hud.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/hud.o: game/announcer.h game/monster.h game/projectile.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/projectile.o: shared/ents.h shared/command.h shared/glexts.h game/projectile.o: shared/glemu.h engine/sound.h shared/iengine.h -game/projectile.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/projectile.o: game/gamemode.h game/entity.h game/monster.h +game/projectile.o: shared/igame.h shared/unicode.h game/projectile.h +game/projectile.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/projectile.o: game/announcer.h game/monster.h game/announcer.o: game/game.h shared/cube.h shared/tools.h shared/geom.h game/announcer.o: shared/ents.h shared/command.h shared/glexts.h game/announcer.o: shared/glemu.h engine/sound.h shared/iengine.h -game/announcer.o: shared/igame.h game/projectile.h game/weapon.h game/ai.h -game/announcer.o: game/gamemode.h game/entity.h game/monster.h game/announcer.h +game/announcer.o: shared/igame.h shared/unicode.h game/projectile.h +game/announcer.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/announcer.o: game/announcer.h game/monster.h game/camera.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h game/camera.o: shared/ents.h shared/command.h shared/glexts.h shared/glemu.h game/camera.o: engine/sound.h shared/iengine.h shared/igame.h engine/world.h game/camera.o: engine/octa.h engine/light.h engine/texture.h engine/bih.h -game/camera.o: engine/model.h game/game.h game/projectile.h game/weapon.h -game/camera.o: game/ai.h game/gamemode.h game/entity.h game/monster.h +game/camera.o: engine/model.h game/game.h shared/unicode.h game/projectile.h +game/camera.o: game/weapon.h game/ai.h game/gamemode.h game/entity.h +game/camera.o: game/announcer.h game/monster.h shared/cube.h.gch: shared/tools.h shared/geom.h shared/ents.h shared/cube.h.gch: shared/command.h shared/glexts.h shared/glemu.h @@ -558,22 +564,23 @@ engine/engine.h.gch: engine/texture.h engine/bih.h engine/model.h game/game.h.gch: shared/cube.h shared/tools.h shared/geom.h shared/ents.h game/game.h.gch: shared/command.h shared/glexts.h shared/glemu.h game/game.h.gch: engine/sound.h shared/iengine.h shared/igame.h -game/game.h.gch: game/projectile.h game/game.h game/weapon.h game/ai.h -game/game.h.gch: game/gamemode.h game/entity.h game/monster.h game/announcer.h +game/game.h.gch: shared/unicode.h game/projectile.h game/game.h game/weapon.h +game/game.h.gch: game/ai.h game/gamemode.h game/entity.h game/announcer.h +game/game.h.gch: game/monster.h standalone/shared/crypto.o: shared/cube.h shared/tools.h shared/geom.h standalone/shared/crypto.o: shared/ents.h shared/command.h engine/sound.h standalone/shared/crypto.o: shared/iengine.h shared/igame.h standalone/shared/stream.o: shared/cube.h shared/tools.h shared/geom.h standalone/shared/stream.o: shared/ents.h shared/command.h engine/sound.h -standalone/shared/stream.o: shared/iengine.h shared/igame.h +standalone/shared/stream.o: shared/iengine.h shared/igame.h shared/unicode.h standalone/shared/tools.o: shared/cube.h shared/tools.h shared/geom.h standalone/shared/tools.o: shared/ents.h shared/command.h engine/sound.h -standalone/shared/tools.o: shared/iengine.h shared/igame.h +standalone/shared/tools.o: shared/iengine.h shared/igame.h shared/unicode.h standalone/engine/command.o: engine/engine.h shared/cube.h shared/tools.h standalone/engine/command.o: shared/geom.h shared/ents.h shared/command.h standalone/engine/command.o: engine/sound.h shared/iengine.h shared/igame.h -standalone/engine/command.o: engine/world.h +standalone/engine/command.o: engine/world.h shared/unicode.h standalone/engine/server.o: engine/engine.h shared/cube.h shared/tools.h standalone/engine/server.o: shared/geom.h shared/ents.h shared/command.h standalone/engine/server.o: engine/sound.h shared/iengine.h shared/igame.h @@ -585,15 +592,17 @@ standalone/engine/worldio.o: engine/world.h standalone/game/entity.o: game/game.h shared/cube.h shared/tools.h standalone/game/entity.o: shared/geom.h shared/ents.h shared/command.h standalone/game/entity.o: engine/sound.h shared/iengine.h shared/igame.h -standalone/game/entity.o: game/projectile.h game/weapon.h game/ai.h -standalone/game/entity.o: game/gamemode.h game/entity.h game/monster.h +standalone/game/entity.o: shared/unicode.h game/projectile.h game/weapon.h +standalone/game/entity.o: game/ai.h game/gamemode.h game/entity.h +standalone/game/entity.o: game/announcer.h game/monster.h standalone/game/gameserver.o: game/game.h shared/cube.h shared/tools.h standalone/game/gameserver.o: shared/geom.h shared/ents.h shared/command.h standalone/game/gameserver.o: engine/sound.h shared/iengine.h shared/igame.h -standalone/game/gameserver.o: game/projectile.h game/weapon.h game/ai.h -standalone/game/gameserver.o: game/gamemode.h game/entity.h game/monster.h +standalone/game/gameserver.o: shared/unicode.h game/projectile.h +standalone/game/gameserver.o: game/weapon.h game/ai.h game/gamemode.h +standalone/game/gameserver.o: game/entity.h game/announcer.h game/monster.h standalone/game/gameserver.o: game/geoip.h game/ctf.h game/elimination.h -standalone/game/gameserver.o: game/extinfo.h game/aimanager.h game/announcer.h +standalone/game/gameserver.o: game/extinfo.h game/aimanager.h standalone/engine/master.o: shared/cube.h shared/tools.h shared/geom.h standalone/engine/master.o: shared/ents.h shared/command.h engine/sound.h standalone/engine/master.o: shared/iengine.h shared/igame.h @@ -607,7 +616,7 @@ standalone/engine/engine.h.gch: shared/iengine.h shared/igame.h standalone/engine/engine.h.gch: engine/world.h standalone/game/game.h.gch: shared/cube.h shared/tools.h shared/geom.h standalone/game/game.h.gch: shared/ents.h shared/command.h engine/sound.h -standalone/game/game.h.gch: shared/iengine.h shared/igame.h game/projectile.h -standalone/game/game.h.gch: game/game.h game/weapon.h game/ai.h -standalone/game/game.h.gch: game/gamemode.h game/entity.h game/monster.h -standalone/game/game.h.gch: game/announcer.h +standalone/game/game.h.gch: shared/iengine.h shared/igame.h shared/unicode.h +standalone/game/game.h.gch: game/projectile.h game/game.h game/weapon.h +standalone/game/game.h.gch: game/ai.h game/gamemode.h game/entity.h +standalone/game/game.h.gch: game/announcer.h game/monster.h diff --git a/source/confusables.py b/source/confusables.py new file mode 100755 index 000000000..802999e88 --- /dev/null +++ b/source/confusables.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import requests + +CONFUSABLES_URL = "https://www.unicode.org/Public/security/latest/confusables.txt" + +def is_codepoint_allowed(codepoint: int) -> bool: + return ( + (codepoint >= 0x0021 and codepoint <= 0x007E) # ASCII + + or (codepoint >= 0x00A1 and codepoint <= 0x00AC) + or (codepoint >= 0x00AE and codepoint <= 0x00FF) # Latin-1 Supplement + + or (codepoint >= 0x0100 and codepoint <= 0x0131) + or (codepoint >= 0x0134 and codepoint <= 0x0148) + or (codepoint >= 0x014A and codepoint <= 0x017E) # Latin Extended-A (exclude 0x131, 0x132, 0x149 and 0x17F) + + or (codepoint >= 0x0218 and codepoint <= 0x021B) # Latin Extended-B: Romanian + + or (codepoint == 0x0386) + or (codepoint >= 0x0388 and codepoint <= 0x038A) + or (codepoint == 0x038C) + or (codepoint >= 0x038E and codepoint <= 0x03A1) + or (codepoint >= 0x03A3 and codepoint <= 0x03CE) # Greek and Coptic + + or (codepoint >= 0x0400 and codepoint <= 0x045F) # Cyrillic + + or (codepoint >= 0x1EA0 and codepoint <= 0x1EF9) # Latin Extended Additional: Vietnamese + ) + +confusables = dict() + +# download confusables file from Unicode +response = requests.get(CONFUSABLES_URL).text + +# parse each line +for line in response.split('\n'): + line = line.split('#', 1)[0].strip() # ignore everything after the `#` character (comments) and remove whitespaces + if not len(line): continue # skip empty lines + + # format: 123A ;\t456B ;\tMA + # we want the lines where both the first and the second column have exactly one entry + src, dst, _ = [x.strip() for x in line.split(';', 2)] + if len(src.split(' ', 1)) != 1 or len(dst.split(' ', 1)) != 1: continue + + # check that both codepoints are allowed + src_n = int(src, 16) + dst_n = int(dst, 16) + if not is_codepoint_allowed(src_n) or not is_codepoint_allowed(dst_n): continue + + # save the entry + if confusables.get(dst_n) == None: + confusables[dst_n] = [src_n] + else: + confusables[dst_n].append(src_n) + +# print homoglyph function +print('static inline uint homoglyph(uint c)') +print('{') +print('\tswitch(c)') +print('\t{') + +for dst_n in sorted(confusables.keys()): + src_list = confusables.get(dst_n) + n = 0 + for src_n in src_list: + n = n + 1 + print(f'\t\tcase {hex(src_n)}:', end='' if n >= len(src_list) else '\n') + print(f' return {hex(dst_n)};') + +print('\t}') +print('\treturn c;') +print('}') \ No newline at end of file diff --git a/source/engine/command.cpp b/source/engine/command.cpp index 979cccaa6..98d62d72d 100644 --- a/source/engine/command.cpp +++ b/source/engine/command.cpp @@ -2,6 +2,7 @@ // is largely backwards compatible with the quake console language. #include "engine.h" +#include "unicode.h" hashnameset idents; // contains ALL vars/commands/aliases vector identmap; @@ -924,22 +925,34 @@ const char *parsestring(const char *p) int unescapestring(char *dst, const char *src, const char *end) { char *start = dst; - while(src < end) + uint c; + uint s = uni_getchar(src, c); + for(const char *p = src; src < end; p += s, s = uni_getchar(p, c)) { - int c = *src++; + if(iscubecntrl(c) || c == 0xFFFD) + { + src += s; + continue; + } if(c == '^') { - if(src >= end) break; - int e = *src++; - switch(e) + if(src+1 >= end) break; + switch(*(src+1)) { - case 'n': *dst++ = '\n'; break; - case 't': *dst++ = '\t'; break; - case 'f': *dst++ = '\f'; break; - default: *dst++ = e; break; + case 'n': *dst++ = '\n'; ++src; ++p; break; + case 'r': *dst++ = '\r'; ++src; ++p; break; + case 't': *dst++ = '\t'; ++src; ++p; break; + case 'f': *dst++ = '\f'; ++src; ++p; break; + case '^': *dst++ = '^' ; ++src; ++p; break; + default: break; } + ++src; + } + else + { + loopi(s) *dst++ = p[i]; + src += s; } - else *dst++ = c; } *dst = '\0'; return dst - start; @@ -3162,21 +3175,29 @@ bool execfile(const char *cfgfile, bool msg) } ICOMMAND(exec, "sb", (char *file, int *msg), intret(execfile(file, *msg != 0) ? 1 : 0)); -const char *escapestring(const char *s) +const char *escapestring(const char *str) { stridx = (stridx + 1)%4; vector &buf = strbuf[stridx]; buf.setsize(0); buf.add('"'); - for(; *s; s++) switch(*s) + uint c; + uint s = uni_getchar(str, c); + for(const char *p = str; c; p += s, s = uni_getchar(p, c)) switch(c) { case '\n': buf.put("^n", 2); break; + case '\r': buf.put("^r", 2); break; case '\t': buf.put("^t", 2); break; case '\f': buf.put("^f", 2); break; case '"': buf.put("^\"", 2); break; case '^': buf.put("^^", 2); break; - default: buf.add(*s); break; + default: + { + if(iscubecntrl(c) || c == 0xFFFD) break; // filter out control characters + buf.put(p, s); break; + } } + buf.put("\"\0", 2); return buf.getbuf(); } @@ -3216,7 +3237,7 @@ bool validateblock(const char *s) #ifndef STANDALONE void writecfg(const char *name) { - stream *f = openutf8file(path(name && name[0] ? name : game::savedconfig(), true), "w"); + stream *f = openfile(path(name && name[0] ? name : game::savedconfig(), true), "w"); if(!f) return; f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec()); game::writeclientinfo(f); @@ -3670,30 +3691,39 @@ COMMAND(at, "si1V"); void substr(char *s, int *start, int *count, int *numargs) { - int len = strlen(s), offset = clamp(*start, 0, len); - commandret->setstr(newstring(&s[offset], *numargs >= 3 ? clamp(*count, 0, len - offset) : len - offset)); + int len = strlen(s), offset = clamp(uni_offset(s, *start), 0, len); + int n = *numargs >= 3 ? clamp(uni_offset(s, *start + *count) - offset, 0, len - offset) : len - offset; + commandret->setstr(newstring(&s[offset], n)); } COMMAND(substr, "siiN"); void chopstr(char *s, int *lim, char *ellipsis) { - int len = strlen(s), maxlen = abs(*lim); - if(len > maxlen) + const int uni_len = uni_strlen(s); + int maxlen = abs(*lim); + if(uni_len > maxlen) { - int elen = strlen(ellipsis); - maxlen = max(maxlen, elen); - char *chopped = newstring(maxlen); - if(*lim < 0) + const int elen = strlen(ellipsis), uni_elen = uni_strlen(ellipsis); + maxlen = max(maxlen, uni_elen); + char *chopped = newstring(4 * maxlen); + if(*lim < 0) // strchop "abcdef" -3 "" => def ; strchop "abcdef" -3 "AB" => ABf ; strchop "abcdef" -3 "ABCD" => ABCD { + uint _c; + const int offset = uni_negoffset(s, maxlen - uni_elen); + int n = uni_offset(s + offset, maxlen - uni_elen); + n += uni_getchar(s + offset + n, _c); + memcpy(chopped, ellipsis, elen); - memcpy(&chopped[elen], &s[len - (maxlen - elen)], maxlen - elen); + memcpy(&chopped[elen], &s[offset], n); + chopped[elen+n] = '\0'; } - else + else // strchop "abcdef" 3 "" => abc ; strchop "abcdef" 3 "AB" => aAB ; strchop "abcdef" 3 "ABCD" => ABCD { - memcpy(chopped, s, maxlen - elen); - memcpy(&chopped[maxlen - elen], ellipsis, elen); + const int n = uni_offset(s, maxlen - uni_elen); + memcpy(chopped, s, n); + memcpy(&chopped[n], ellipsis, elen); + chopped[elen+n] = '\0'; } - chopped[maxlen] = '\0'; commandret->setstr(chopped); } else result(s); @@ -3715,7 +3745,7 @@ ICOMMAND(stripcolors, "s", (char *s), { int len = strlen(s); char *d = newstring(len); - filtertext(d, s, false, true, true, false, len); + filtertext(d, s, T_NEWLINES | T_WHITESPACE, len); stringret(d); }); @@ -4416,7 +4446,7 @@ CMPSCMD(>=s, >=); ICOMMAND(echo, "C", (char *s), conoutf(CON_ECHO, "\f1%s", s)); ICOMMAND(error, "C", (char *s), conoutf(CON_ERROR, "%s", s)); -ICOMMAND(strstr, "ss", (char *a, char *b), { char *s = strstr(a, b); intret(s ? s-a : -1); }); +ICOMMAND(strstr, "ss", (char *a, char *b), { char *s = strstr(a, b); intret(s ? uni_index(a, s-a) : -1); }); ICOMMAND(strrstr, "ss", (char *a, char *b), { if(!b[0]) intret(strlen(a)); @@ -4424,14 +4454,20 @@ ICOMMAND(strrstr, "ss", (char *a, char *b), { char *last = NULL; for(char *cur = a; char *s = strstr(cur, b); last = s, cur = s+1); - intret(last ? last-a : -1); + intret(last ? uni_index(a, last-a) : -1); } }); -ICOMMAND(strlen, "s", (char *s), intret(strlen(s))); -ICOMMAND(strcode, "si", (char *s, int *i), intret(*i > 0 ? (memchr(s, 0, *i) ? 0 : uchar(s[*i])) : uchar(s[0]))); -ICOMMAND(codestr, "i", (int *i), { char *s = newstring(1); s[0] = char(*i); s[1] = '\0'; stringret(s); }); -ICOMMAND(struni, "si", (char *s, int *i), intret(*i > 0 ? (memchr(s, 0, *i) ? 0 : cube2uni(s[*i])) : cube2uni(s[0]))); -ICOMMAND(unistr, "i", (int *i), { char *s = newstring(1); s[0] = uni2cube(*i); s[1] = '\0'; stringret(s); }); +ICOMMAND(strlen, "s", (char *s), intret(uni_strlen(s))); +ICOMMAND(strcode, "si", (char *s, int *i), intret(uni_charat(s, *i))); +ICOMMAND(codestr, "i", (int *i), +{ + char *dst = newstring(4); + uni_code2str(*i, dst); + stringret(dst); +}) +ICOMMAND(strbytelen, "s", (char *s), intret(strlen(s))); +ICOMMAND(char2byteindex, "si", (char *s, int *i), intret(uni_offset(s, *i))); +ICOMMAND(byte2charindex, "si", (char *s, int *i), intret(uni_index(s, *i))); int naturalsort(const char *a, const char *b) { @@ -4459,18 +4495,20 @@ int naturalsort(const char *a, const char *b) } ICOMMAND(naturalsort, "ss", (char *a, char *b), intret(naturalsort(a,b)<=0)); -#define STRMAPCOMMAND(name, map) \ - ICOMMAND(name, "s", (char *s), \ - { \ - int len = strlen(s); \ - char *m = newstring(len); \ - loopi(len) m[i] = map(s[i]); \ - m[len] = '\0'; \ - stringret(m); \ - }) - -STRMAPCOMMAND(strlower, cubelower); -STRMAPCOMMAND(strupper, cubeupper); +ICOMMAND(strlower, "s", (char *s), { + int len = strlen(s); + char *m = newstring(len); + uni_strlower(s, m); + m[len] = '\0'; + stringret(m); +}); +ICOMMAND(strupper, "s", (char *s), { + int len = strlen(s); + char *m = newstring(len); + uni_strupper(s, m); + m[len] = '\0'; + stringret(m); +}); char *strreplace(const char *s, const char *oldval, const char *newval, const char *newval2) { @@ -4501,8 +4539,8 @@ ICOMMAND(strreplace, "ssss", (char *s, char *o, char *n, char *n2), commandret-> void strsplice(const char *s, const char *vals, int *skip, int *count) { int slen = strlen(s), vlen = strlen(vals), - offset = clamp(*skip, 0, slen), - len = clamp(*count, 0, slen - offset); + offset = clamp(uni_offset(s, *skip), 0, slen), + len = clamp(uni_offset(s+offset, *count), 0, slen - offset); char *p = newstring(slen - len + vlen); if(offset) memcpy(p, s, offset); if(vlen) memcpy(&p[offset], vals, vlen); diff --git a/source/engine/console.cpp b/source/engine/console.cpp index 5ba3f1d7f..ba4522485 100644 --- a/source/engine/console.cpp +++ b/source/engine/console.cpp @@ -1,18 +1,26 @@ // console.cpp: the console buffer, its display, and command line control #include "engine.h" +#include "unicode.h" #define MAXCONLINES 1000 -struct cline { char *line; int type, outtime; }; +struct cline { char *line; int type, outtime; float fontsize, w; text::Label label; }; reversequeue conlines; -int commandmillis = -1; +int commandmillis = -1, inputmillis = 0; string commandbuf; char *commandaction = NULL, *commandprompt = NULL; enum { CF_COMPLETE = 1<<0, CF_EXECUTE = 1<<1 }; int commandflags = 0, commandpos = -1; -VARFP(maxcon, 10, 200, MAXCONLINES, { while(conlines.length() > maxcon) delete[] conlines.pop().line; }); +VARFP(maxcon, 10, 200, MAXCONLINES, +{ + while(conlines.length() > maxcon) + { + cline &cl = conlines.pop(); + delete[] cl.line; + } +}); #define CONSTRLEN 512 @@ -49,15 +57,19 @@ void conline(int type, const char *sf) // add a line to the console buffer if(!(prev&CON_TAG_MASK)) break; if(type == prev) { - buf = conlines.remove(i).line; + buf = conlines[i].line; + //buf = conlines.remove(i).line; + conlines.remove(i); break; } } if(!buf) buf = conlines.length() >= maxcon ? conlines.remove().line : newstring("", CONSTRLEN-1); cline &cl = conlines.add(); + cl.label.clear(); cl.line = buf; cl.type = type; cl.outtime = totalmillis; // for how long to keep line on screen + cl.fontsize = cl.w = 0; defformatstring(prefixedsf, "%s%s", getprefix(type), sf); copystring(cl.line, prefixedsf, CONSTRLEN); } @@ -90,11 +102,15 @@ float rendercommand(float x, float y, float w) const char *prompt = commandprompt ? commandprompt : ">"; formatstring(buf, "%s %s", prompt, commandbuf); - float width, height; - text_boundsf(buf, width, height, w); - y -= height; - draw_text(buf, x, y, 0xFF, 0xFF, 0xFF, 0xFF, commandpos>=0 ? commandpos+1 + strlen(prompt) : strlen(buf), w); - return height; + pushfont(); + setfont("default"); + const text::Label label = text::prepare_for_console(buf, w, commandpos>=0 ? commandpos+1 + strlen(prompt) : strlen(buf)); + y -= label.height(); + + label.draw_as_console(x, y); + + popfont(); + return label.height(); } VARP(consize, 0, 5, 100); @@ -129,9 +145,23 @@ void setconskip(int &skip, int filter, int n) ICOMMAND(conskip, "i", (int *n), setconskip(conskip, UI::uivisible("fullconsole") ? fullconfilter : confilter, *n)); ICOMMAND(miniconskip, "i", (int *n), setconskip(miniconskip, miniconfilter, *n)); -ICOMMAND(clearconsole, "", (), { while(conlines.length()) delete[] conlines.pop().line; }); +ICOMMAND(clearconsole, "", (), +{ + while(conlines.length()) + { + cline &cl = conlines.pop(); + delete[] cl.line; + cl.label.clear(); + } +}); + +// free conline textures, necessary when changing font settings or calling `resetgl()` +void clearconsoletextures() +{ + loopv(conlines) conlines[i].label.clear(); +} -float drawconlines(int conskip, int confade, float conwidth, float conheight, float conoff, int filter, float y = 0, int dir = 1) +float drawconlines(int conskip, int confade, float conwidth, float conheight, float conoff, int maxlines, bool full, int filter, float y = 0, int dir = 1) { filter &= CON_FLAGS; int numl = conlines.length(), offset = min(conskip, numl); @@ -147,15 +177,26 @@ float drawconlines(int conskip, int confade, float conwidth, float conheight, fl } int totalheight = 0; + int n = 0; loopi(numl) //determine visible height { // shuffle backwards to fill if necessary int idx = offset+i < numl ? offset+i : --offset; if(!(conlines[idx].type&filter)) continue; char *line = conlines[idx].line; - float width, height; - text_boundsf(line, width, height, conwidth); - if(totalheight + height > conheight) { numl = i; if(offset == idx) ++offset; break; } + int width, height; + const text::Label& label = conlines[idx].label; + if(conlines[idx].w != conwidth || conlines[idx].fontsize != fontsize || !label.valid()) + { + text::measure(line, conwidth, width, height); + } + else + { + width = label.width(); + height = label.height(); + } + if(maxlines > 0) { if(++n > maxlines) { numl = i; if(offset == idx) ++offset; break; } } + else if(totalheight + height > conheight) { numl = i; if(offset == idx) ++offset; break; } totalheight += height; } if(dir > 0) y = conoff; @@ -164,32 +205,50 @@ float drawconlines(int conskip, int confade, float conwidth, float conheight, fl int idx = offset + (dir > 0 ? numl-i-1 : i); if(!(conlines[idx].type&filter)) continue; char *line = conlines[idx].line; - float width, height; - text_boundsf(line, width, height, conwidth); - if(dir <= 0) y -= height; - draw_text(line, conoff, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth); - if(dir > 0) y += height; + text::Label& label = conlines[idx].label; + if(conlines[idx].w != conwidth || conlines[idx].fontsize != fontsize || !label.valid()) + { + if(!full) label = text::prepare_for_console(line, conwidth, -1); + else label = text::prepare(line, conwidth, bvec(255, 255, 255), -1); + conlines[idx].w = conwidth; + conlines[idx].fontsize = fontsize; + } + if(dir <= 0) y -= label.height(); + if(label.valid()) + { + if(!full) label.draw_as_console(conoff, y); + else label.draw(conoff, y); + } + if(dir > 0) y += label.height(); } return y+conoff; } float renderfullconsole(float w, float h) { - float conpad = FONTH/2, + pushfont(); + setfont("default"); + setfontsize(hudh * conscale / CONSOLETEXTROWS); + float conpad = FONTH*1.5/2, conheight = h - 2*conpad, conwidth = w - 2*conpad; - drawconlines(conskip, 0, conwidth, conheight, conpad, fullconfilter); + drawconlines(conskip, 0, conwidth, conheight, conpad, 0, true, fullconfilter); + popfont(); return conheight + 2*conpad; } float renderconsole(float w, float h, float abovehud) { - float conpad = FONTH/2, - conheight = min(float(FONTH*consize), h - 2*conpad), + float conpad = FONTH*1.5/2, + conheight = min(float(FONTH*1.5*consize), h - 2*conpad), conwidth = w - 2*conpad - game::clipconsole(w, h); - float y = drawconlines(conskip, confade, conwidth, conheight, conpad, confilter); + pushfont(); + setfont("default"); + setfontsize(hudh * conscale / CONSOLETEXTROWS); + float y = drawconlines(conskip, confade, conwidth, conheight, conpad, consize, false, confilter); if(miniconsize && miniconwidth) - drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*conpad))/100, min(float(FONTH*miniconsize), abovehud - y), conpad, miniconfilter, abovehud, -1); + drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*conpad))/100, min(float(FONTH*1.5*miniconsize), abovehud - y), conpad, miniconsize, false, miniconfilter, abovehud, -1); + popfont(); return y; } @@ -353,6 +412,7 @@ VARP(fullconsolecommand, 0, 1, 1); void inputcommand(char *init, char *action = NULL, char *prompt = NULL, char *flags = NULL) // turns input to the command line on or off { + inputmillis = totalmillis; commandmillis = init ? totalmillis : -1; textinput(commandmillis >= 0, TI_CONSOLE); keyrepeat(commandmillis >= 0, KR_CONSOLE); @@ -395,6 +455,7 @@ struct hline void restore() { + inputmillis = totalmillis; copystring(commandbuf, buf); if(commandpos >= (int)strlen(commandbuf)) commandpos = -1; DELETEA(commandaction); @@ -540,6 +601,7 @@ ICOMMAND(iscmdlineopen, "", (), intret(commandmillis >= 0 ? 1 : 0)); bool consoleinput(const char *str, int len) { if(commandmillis < 0) return false; + inputmillis = totalmillis; resetcomplete(); int cmdlen = (int)strlen(commandbuf), cmdspace = int(sizeof(commandbuf)) - (cmdlen+1); @@ -573,10 +635,7 @@ void getclipboard() result(""); return; } - string paste; - size_t decoded = decodeutf8((uchar *)paste, sizeof(paste)-1, (const uchar *)cb, strlen(cb)); - paste[decoded] = '\0'; - result(paste); + result(cb); SDL_free(cb); } COMMAND(getclipboard, ""); @@ -586,10 +645,7 @@ void pasteconsole() if(!SDL_HasClipboardText()) return; char *cb = SDL_GetClipboardText(); if(!cb) return; - string paste; - size_t decoded = decodeutf8((uchar *)paste, sizeof(paste)-1, (const uchar *)cb, strlen(cb)); - paste[decoded] = '\0'; - consoleinput(paste, decoded); + consoleinput(cb, strlen(cb)); SDL_free(cb); } @@ -618,6 +674,7 @@ static char *skipwordrev(char *s, int n = -1) bool consolekey(int code, bool isdown) { if(commandmillis < 0) return false; + inputmillis = totalmillis; #ifdef __APPLE__ #define MOD_KEYS (KMOD_LGUI|KMOD_RGUI) @@ -647,7 +704,9 @@ bool consolekey(int code, bool isdown) { int len = (int)strlen(commandbuf); if(commandpos<0) break; - int end = commandpos+1; + uint _codepoint; + const int s = uni_getchar(&commandbuf[commandpos], _codepoint); + int end = commandpos+s; if(SDL_GetModState()&SKIP_KEYS) end = skipword(&commandbuf[commandpos]) - commandbuf; memmove(&commandbuf[commandpos], &commandbuf[end], len + 1 - end); resetcomplete(); @@ -659,7 +718,7 @@ bool consolekey(int code, bool isdown) { int len = (int)strlen(commandbuf), i = commandpos>=0 ? commandpos : len; if(i<1) break; - int start = i-1; + int start = i - uni_prevchar(commandbuf, i); if(SDL_GetModState()&SKIP_KEYS) start = skipwordrev(commandbuf, i) - commandbuf; memmove(&commandbuf[start], &commandbuf[i], len - i + 1); resetcomplete(); @@ -670,15 +729,19 @@ bool consolekey(int code, bool isdown) case SDLK_LEFT: if(SDL_GetModState()&SKIP_KEYS) commandpos = skipwordrev(commandbuf, commandpos) - commandbuf; - else if(commandpos>0) commandpos--; - else if(commandpos<0) commandpos = (int)strlen(commandbuf)-1; + else if(commandpos>0) commandpos -= uni_prevchar(commandbuf, commandpos); + else if(commandpos<0) commandpos = (int)strlen(commandbuf) - uni_prevchar(commandbuf, strlen(commandbuf)); break; case SDLK_RIGHT: if(commandpos>=0) { if(SDL_GetModState()&SKIP_KEYS) commandpos = skipword(&commandbuf[commandpos]) - commandbuf; - else ++commandpos; + else + { + uint _codepoint; + commandpos += uni_getchar(&commandbuf[commandpos], _codepoint); + } if(commandpos>=(int)strlen(commandbuf)) commandpos = -1; } break; diff --git a/source/engine/engine.h b/source/engine/engine.h index 7e2ad31ab..001ae660a 100644 --- a/source/engine/engine.h +++ b/source/engine/engine.h @@ -33,38 +33,148 @@ extern int screenw, screenh, renderw, renderh, hudw, hudh; extern vector entgroup; // rendertext -struct font -{ - struct charinfo - { - float x, y, w, h, offsetx, offsety, advance; - int tex; - }; - - char *name; - vector texs; - vector chars; - int charoffset, defaultw, defaulth, scale; - float bordermin, bordermax, outlinemin, outlinemax; - - font() : name(NULL) {} - ~font() { DELETEA(name); } -}; - -#define FONTH (curfont->scale) +#define FONTH (fontsize) #define FONTW (FONTH/2) #define MINRESW 640 #define MINRESH 480 -extern font *curfont; +// number of text lines to fill the whole screen (higher = smaller text) +#define CONSOLETEXTROWS 45 +#define LOADSCREENTEXTROWS 45 +#define UITEXTROWS 32 +#define PARTICLETEXTROWS 15 // NOTE: particles use a different scale + extern Shader *textshader; extern const matrix4x3 *textmatrix; extern float textscale; - -extern font *findfont(const char *name); +extern double fontsize; + +extern bool init_pangocairo(); +extern void done_pangocairo(); +extern int getcurrentfontid(); +extern bool setfont(const char *name); +extern void pushfont(); +extern bool popfont(); extern void reloadfonts(); +static inline void setfontsize(double size) { fontsize = size; } -static inline void setfont(font *f) { if(f) curfont = f; } +struct _PangoLayout; +namespace text +{ + // A rendered text label, ready to be drawn + class Label + { + int w, h; + int ox, oy; // offsets + GLuint tex; + _PangoLayout *layout; + int *map_markup_to_text; + int *map_text_to_markup; + + public: + Label(); + ~Label(); + Label(const Label&) = delete; + Label(Label&&) noexcept; + Label& operator=(const Label&) = delete; + Label& operator=(Label&&) noexcept; + + bool valid() const { return tex != 0; } + int width() const { return w; } + int height() const { return h; } + + // empties the text content of the label + void clear(); + + // draws the label to the screen + void draw( + double left, // screen X coordinate + double top, // screen Y coordinate + int alpha = 255, // text opacity (0-255) + bool black = false // make it black? (used for shadows) + ) const; + // like `draw()` but with a shadow + void draw_as_console(double left, double top) const; + + // converts (x,y) pixel coordinates to a character (byte) index + // NOTE: do not call if the label was not prepared with `keep_layout=true` + int xy_to_index(float x, float y) const; + + friend Label prepare(const char *, int, const bvec&, int, double, const bvec4&, int, int, const char *, bool, bool, bool); + }; + + // creates a label from a string + Label prepare( + const char *str, // the label text + int max_width, // maximum width in pixels + const bvec& color = bvec(255, 255, 255), // initial color of the text + int cursor = -1, // byte index of the cursor (disabled if <0) + double outline = 0, // outline thickness + const bvec4& outline_color = bvec4(0, 0, 0, 0), // outline color (RGBA) + int align = -1, // text alignment: -1 = left, 0 = center, 1 = right + int justify = 0, // text justification (0 = disable, enable otherwise) + const char *lang = nullptr, // optional language code of the text (can affect text shaping) + bool no_fallback = false, // disable fallback fonts for unavailable glyphs? + bool keep_layout = false, // keep layout in memory? (necessary if you need to call `xy_to_index()` on the label) + bool reserve_cursor = false // reserve space to the right for the cursor? + ); + + // same as `prepare()` but with appropriate settings for console text + Label prepare_for_console(const char *str, int max_width, int cursor); + + // same as `prepare()` but use a cache so that the same label doesn't have to be recreated every time + const Label& prepare_for_particle(const char *str, + const bvec& color = bvec(255, 255, 255), + double outline = 0, + const bvec4& outline_color = bvec4(0, 0, 0, 0), + const char *lang = nullptr, + bool no_fallback = false + ); + + // measure text before creating the label + void measure( + const char *str, // the string to measure + int max_width, // maximum width in pixels + int& width, // output: the measured width + int& height, // output: the measured height + int align = -1, // text alignment: -1 = left, 0 = center, 1 = right + int justify = 0, // text justification (0 = disable, enable otherwise) + const char *lang = nullptr, // optional language code of the text (can affect text shaping) + bool no_fallback = false // disable fallback fonts for unavailable glyphs? + ); + + // draw a string directly to the screen + void draw( + const char *str, // the string to draw + double left, // screen X coordinate + double top, // screen Y coordinate + const bvec& color = bvec(255, 255, 255), // initial color of the text + int alpha = 255, // text opacity (0-255) + int max_width = 0, // maximum width in pixels + int align = -1, // text alignment: -1 = left, 0 = center, 1 = right + int justify = 0, // text justification (0 = disable, enable otherwise) + const char *lang = nullptr, // optional language code of the text (can affect text shaping) + bool no_fallback = false // disable fallback fonts for unavailable glyphs? + ); + static inline void draw_fmt(const char *fstr, double left, double top, ...) + { + defvformatstring(str, top, fstr); + draw(str, left, top); + } + + // same as `draw()` but with appropriate settings for console text + void draw_as_console(const char *str, double left, double top, + int max_width = 0, + int cursor = -1 + ); + static inline void draw_as_console_fmt(const char *fstr, double left, double top, ...) + { + defvformatstring(str, top, fstr); + draw_as_console(str, left, top); + } + + void getres(int& w, int& h); +} // texture extern int hwtexsize, hwcubetexsize, hwmaxaniso, maxtexsize, hwtexunits, hwvtexunits; @@ -580,6 +690,7 @@ extern void clearsleep(bool clearoverrides = true); // console extern float conscale; +extern int inputmillis; extern void processkey(int code, bool isdown, int modstate = 0); extern void binduikey(char* key, char* action, const char* window); @@ -597,6 +708,7 @@ extern const char *addreleaseaction(char *s); extern tagval *addreleaseaction(ident *id, int numargs); extern void writebinds(stream *f); extern void writecompletions(stream *f); +extern void clearconsoletextures(); // main enum @@ -734,6 +846,7 @@ namespace UI void update(); void render(); void cleanup(); + void clearlabels(); void resetcursor(); void getcursorpos(float& x, float& y); diff --git a/source/engine/main.cpp b/source/engine/main.cpp index 67887d3a7..4de89045f 100644 --- a/source/engine/main.cpp +++ b/source/engine/main.cpp @@ -28,6 +28,7 @@ void cleanup() if(screen) SDL_SetWindowFullscreen(screen, 0); #endif SDL_Quit(); + done_pangocairo(); } extern void writeinitcfg(); @@ -67,6 +68,7 @@ void fatal(const char *s, ...) // failure exit #endif } SDL_Quit(); + done_pangocairo(); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Tesseract fatal error", msg, NULL); } } @@ -107,7 +109,7 @@ VARFN(screenh, scr_h, SCR_MINH, -1, SCR_MAXH, initwarning("screen resolution")); void writeinitcfg() { - stream *f = openutf8file("config/init.cfg", "w"); + stream *f = openfile("config/init.cfg", "w"); if(!f) return; f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n"); extern int fullscreen; @@ -207,22 +209,29 @@ void renderbackgroundview(int w, int h, const char *caption, Texture *mapshot, c if(caption) { - int tw = text_width(caption); - float tsz = 0.04f*lw/FONTH, + setfontsize(h * 1.5 / LOADSCREENTEXTROWS); + pushfont(); + setfont("wide"); + const text::Label caption_label = text::prepare(caption, 0); + popfont(); + const int tw = caption_label.width(); + const float tsz = 0.04f*lw/FONTH, tx = 0.5f*(w - tw*tsz), ty = h - 0.075f*1.5f*lw - FONTH*tsz; - pushhudtranslate(tx, ty, tsz); - draw_text(caption, 0, 0); - pophudmatrix(); + + caption_label.draw(tx, ty); } if(mapshot || mapname) { - float infowidth = 14*FONTH, sz = 0.35f*lw, + setfontsize(h * 1 / LOADSCREENTEXTROWS); + text::Label info_label; + float infowidth = 0.5f*lw, sz = 0.35f*lw, msz = (0.85f*lw - sz)/(infowidth + FONTH), x = 0.5f*w, ly = 0.5f*lw, y = (0.5f*(h*0.5f - ly) + ly) - sz/15, - mx = 0, my = 0, mw = 0, mh = 0; + mx = 0, my = 0, mw = 0; if(mapinfo) { - text_boundsf(mapinfo, mw, mh, infowidth); + info_label = text::prepare(mapinfo, infowidth); + mw = info_label.width(); x -= 0.5f*mw*msz; if(mapshot && mapshot!=notexture) { @@ -239,17 +248,26 @@ void renderbackgroundview(int w, int h, const char *caption, Texture *mapshot, c } if(mapname) { - float tw = text_widthf(mapname), tsz = sz/(8*FONTH), tx = max(0.5f*(mw*msz - tw*tsz), 0.0f); - pushhudtranslate(x+mx+tx, y, tsz); - draw_text(mapname, 0, 0); - pophudmatrix(); + setfontsize(h * 1.5 / LOADSCREENTEXTROWS); + pushfont(); + setfont("wide"); + const text::Label name_label = text::prepare(mapname, 0); + popfont(); + float tsz = sz/(8*FONTH); + + if(mapshot && mapshot!=notexture && mapinfo) + { + name_label.draw(x + sz + (infowidth - name_label.width()) / 2, y); + } + else + { + name_label.draw(x - name_label.width() / 2, y); + } my = 1.5f*FONTH*tsz; } if(mapinfo) { - pushhudtranslate(x+mx, y+my, msz); - draw_text(mapinfo, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, -1, infowidth); - pophudmatrix(); + info_label.draw(x+mx, y+my); } } @@ -278,7 +296,7 @@ void renderbackground(const char *caption, Texture *mapshot, const char *mapname int w = hudw, h = hudh; if(forceaspect) w = int(ceil(h*forceaspect)); getbackgroundres(w, h); - gettextres(w, h); + text::getres(w, h); if(force) { @@ -339,13 +357,13 @@ void renderprogressview(int w, int h, float bar, const char *text) // also use if(text) { - int tw = text_width(text); + setfontsize(h * 0.8 / LOADSCREENTEXTROWS); + const text::Label label = text::prepare(text, 0, bvec(255, 255, 255)); + const int tw = label.width(); float tsz = bh*0.6f/FONTH; if(tw*tsz > mw) tsz = mw/tw; - pushhudtranslate(bx+sw, by + (bh - FONTH*tsz)/2, tsz); - draw_text(text, 0, 0); - pophudmatrix(); + label.draw(bx+sw, by+ (bh - FONTH * tsz)/2); } glDisable(GL_BLEND); @@ -374,7 +392,7 @@ void renderprogress(float bar, const char *text, bool background) // also used int w = hudw, h = hudh; if(forceaspect) w = int(ceil(h*forceaspect)); getbackgroundres(w, h); - gettextres(w, h); + text::getres(w, h); extern int mesa_swap_bug, curvsync; bool forcebackground = progressbackground || (mesa_swap_bug && (curvsync || totalmillis==1)); @@ -676,6 +694,7 @@ void resetgl() !reloadtexture("data/interface/loading_frame.png") || !reloadtexture("data/interface/loading_bar.png")) fatal("Failed to reload core texture"); + clearconsoletextures(); reloadfonts(); inbetweenframes = true; renderbackground("Initializing"); @@ -821,9 +840,7 @@ void checkinput() case SDL_TEXTINPUT: if(textinputmask && int(event.text.timestamp-textinputtime) >= textinputfilter) { - uchar buf[SDL_TEXTINPUTEVENT_TEXT_SIZE+1]; - size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)event.text.text, strlen(event.text.text)); - if(len > 0) { buf[len] = '\0'; processtextinput((const char *)buf, len); } + processtextinput(event.text.text, strlen(event.text.text)); } break; @@ -1198,6 +1215,9 @@ int main(int argc, char **argv) notexture = textureload("data/texture/world/base/notexture.png"); if(!notexture) fatal("Could not find core textures"); + logoutf("init: pangocairo"); + if(!init_pangocairo()) fatal("Could not initialize pangocairo"); + logoutf("init: console"); if(!execfile("config/stdlib.cfg", false)) fatal("Cannot find required configuration files (\"/config/stdlib.cfg\")!\nYou might not be running from the main folder."); // this is the first file we load. if(!execfile("config/font.cfg", false)) fatal("Cannot find font definitions"); diff --git a/source/engine/movie.cpp b/source/engine/movie.cpp index 78b28e018..5f67fca78 100644 --- a/source/engine/movie.cpp +++ b/source/engine/movie.cpp @@ -1125,10 +1125,9 @@ namespace recorder { int w = hudw, h = hudh; if(forceaspect) w = int(ceil(h*forceaspect)); - gettextres(w, h); + text::getres(w, h); hudmatrix.ortho(0, w, h, 0, -1, 1); - hudmatrix.scale(1/3.0f, 1/3.0f, 1); resethudmatrix(); glEnable(GL_BLEND); @@ -1139,7 +1138,8 @@ namespace recorder else if(totalsize >= 1e6) { totalsize /= 1e6; unit = "MB"; } else totalsize /= 1e3; - draw_textf("recorded %.1f%s %d%%", w*3-10*FONTH, h*3-FONTH-FONTH*3/2, totalsize, unit, int(calcquality()*100)); + setfontsize(hudh * conscale / CONSOLETEXTROWS); + text::draw_as_console_fmt("recorded %.1f%s %d%%", w-10*FONTH, FONTH-FONTH/2, totalsize, unit, int(calcquality()*100)); glDisable(GL_BLEND); } diff --git a/source/engine/rendergl.cpp b/source/engine/rendergl.cpp index e10949b93..093160099 100644 --- a/source/engine/rendergl.cpp +++ b/source/engine/rendergl.cpp @@ -1183,7 +1183,7 @@ void printtimers(int conw, int conh) { static int printmillis = 0; if(totalmillis - lastprint >= 200) printmillis = framemillis; - draw_textf("frame time %i ms", conw-20*FONTH, conh-FONTH*3/2-offset*9*FONTH/8, printmillis); + text::draw_as_console_fmt("frame time %i ms", conw-20*FONTH, conh-FONTH*3/2-offset*9*FONTH/8, printmillis); offset++; } if(usetimers) loopv(timerorder) @@ -1191,7 +1191,7 @@ void printtimers(int conw, int conh) timer &t = timers[timerorder[i]]; if(t.print < 0 ? t.result >= 0 : totalmillis - lastprint >= 200) t.print = t.result; if(t.print < 0 || (t.gpu && !(t.waiting&(1<= 200) lastprint = totalmillis; @@ -2366,7 +2366,7 @@ VARP(showfps, 0, 0, 1); VARP(showfpsrange, 0, 0, 1); VAR(statrate, 1, 200, 1000); -FVARP(conscale, 1e-3f, 0.45f, 1e3f); +FVARP(conscale, 0.5f, 1.f, 2.f); void resethudshader() { @@ -2379,14 +2379,15 @@ void gl_drawhud() int w = hudw, h = hudh; if(forceaspect) w = int(ceil(h*forceaspect)); - gettextres(w, h); + text::getres(w, h); hudmatrix.ortho(0, w, h, 0, -1, 1); resethudmatrix(); resethudshader(); pushfont(); - setfont("default.ol"); + setfont("default"); + setfontsize(hudh * conscale / CONSOLETEXTROWS); debuglights(); @@ -2394,12 +2395,12 @@ void gl_drawhud() debugparticles(); - float conw = w/conscale, conh = h/conscale, abovehud = conh - FONTH; + float conw = w, conh = h, abovehud = conh - FONTH; if(showhud && !mainmenu) { if(showstats) { - pushhudscale(conscale); + setfontsize(hudh * conscale / CONSOLETEXTROWS); int roffset = 0; if(showfps) @@ -2413,8 +2414,8 @@ void gl_drawhud() int nextfps[3]; getfps(nextfps[0], nextfps[1], nextfps[2]); loopi(3) if(prevfps[i]==curfps[i]) curfps[i] = nextfps[i]; - if(showfpsrange) draw_textf("fps %d+%d-%d", conw-7*FONTH, conh-FONTH*3/2, curfps[0], curfps[1], curfps[2]); - else draw_textf("fps %d", conw-5*FONTH, conh-FONTH*3/2, curfps[0]); + if(showfpsrange) text::draw_as_console_fmt("fps %d+%d-%d", conw-7*FONTH, conh-FONTH*3/2, curfps[0], curfps[1], curfps[2]); + else text::draw_as_console_fmt("fps %d", conw-5*FONTH, conh-FONTH*3/2, curfps[0]); roffset += FONTH; } @@ -2434,12 +2435,10 @@ void gl_drawhud() const char *src = &buf[!wallclock24 && buf[0]=='0' ? 1 : 0]; while(*src) *dst++ = tolower(*src++); *dst++ = '\0'; - draw_text(buf, conw-5*FONTH, conh-FONTH*3/2-roffset); + text::draw_as_console_fmt(buf, conw-5*FONTH, conh-FONTH*3/2-roffset); roffset += FONTH; } } - - pophudmatrix(); } rendertexturepanel(w, h); @@ -2447,13 +2446,12 @@ void gl_drawhud() abovehud = min(abovehud, conh*UI::abovehud()); - pushhudscale(conscale); + setfontsize(hudh * conscale / CONSOLETEXTROWS); abovehud -= rendercommand(FONTH/2, abovehud - FONTH/2, conw-FONTH); if(showhud && !(UI::uivisible("main") || UI::uivisible("fullconsole"))) { renderconsole(conw, conh, abovehud - FONTH/2); } - pophudmatrix(); game::drawpointers(w, h); diff --git a/source/engine/renderparticles.cpp b/source/engine/renderparticles.cpp index a2e1ad867..e199c8548 100644 --- a/source/engine/renderparticles.cpp +++ b/source/engine/renderparticles.cpp @@ -134,7 +134,12 @@ struct particle float initsize, size, maxsize; union { - const char *text; + struct + { + const char *text; + const char *font; + const char *language; + }; float val; physent *owner; struct @@ -485,6 +490,7 @@ static meterrenderer meters(PT_METER), metervs(PT_METERVS); struct textrenderer : listrenderer { + textrenderer(int type = 0) : listrenderer(type|PT_TEXT|PT_LERP|PT_SHADER|PT_NOLAYER) {} @@ -492,26 +498,36 @@ struct textrenderer : listrenderer void startrender() { textshader = particletextshader; - - pushfont(); - setfont("wide.ol"); } void endrender() { textshader = NULL; - - popfont(); } void killpart(listparticle *p) { - if(p->text && p->flags&1) delete[] p->text; + if(p->flags&1) + { + if(p->text) delete[] p->text; + if(p->font) delete[] p->font; + if(p->language) delete[] p->language; + } } void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) { - float scale = p->size/80.0f, xoff = -text_width(p->text)/2, yoff = 0; + pushfont(); + setfont(p->font); + setfontsize(hudh / PARTICLETEXTROWS); + + const text::Label& label = text::prepare_for_particle(p->text, p->color, FONTH / 32.f, bvec4(0, 0, 0, 255), p->language); + if(!label.valid()) + { + popfont(); + return; + } + float scale = p->size/80.0f, xoff = -label.width()/2, yoff = -label.width()/2; if((type&0xFF)==PT_TEXTUP) { xoff += detrnd((size_t)p, 100)-50; yoff -= detrnd((size_t)p, 101); } matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); @@ -519,7 +535,8 @@ struct textrenderer : listrenderer m.translate(xoff, yoff, 50); textmatrix = &m; - draw_text(p->text, 0, 0, p->color.r, p->color.g, p->color.b, blend); + label.draw(0, 0, blend); + popfont(); textmatrix = NULL; } }; @@ -944,7 +961,7 @@ void debugparticles() pushhudmatrix(); hudmatrix.ortho(0, FONTH*n*2*vieww/float(viewh), FONTH*n*2, 0, -1, 1); // squeeze into top-left corner flushhudmatrix(); - loopi(n) draw_text(parts[i]->info, FONTH, (i+n/2)*FONTH); + loopi(n) text::draw(parts[i]->info, FONTH, (i+n/2)*FONTH); pophudmatrix(); } @@ -1106,20 +1123,24 @@ void particle_trail(int type, int fade, const vec &s, const vec &e, int color, f VARP(particletext, 0, 1, 1); VARP(maxparticletextdistance, 0, 64, 10000); -void particle_text(const vec &s, const char *t, int type, int fade, int color, float size, int gravity) +void particle_text(const vec &s, const char *t, int type, int fade, int color, float size, int gravity, const char *font, const char *language) { if(!canaddparticles()) return; if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); p->text = t; + p->font = font; + p->language = language; } -void particle_textcopy(const vec &s, const char *t, int type, int fade, int color, float size, int gravity) +void particle_textcopy(const vec &s, const char *t, int type, int fade, int color, float size, int gravity, const char *font, const char *language) { if(!canaddparticles()) return; if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); p->text = newstring(t); + p->font = newstring(font); + p->language = newstring(language ? language : ""); p->flags = 1; } @@ -1148,6 +1169,23 @@ void particle_hud_mark(const vec &s, int ix, int iy, int type, int fade, int col p->flags |= ix | (iy<<2); } +void particle_hud_text(const vec &s, const char *t, int type, int fade, int color, float size, const char *font, const char *language) +{ + if(!canaddparticles()) return; + vec o; + if((camera1->o.dist(s) <= hudmarkmindist && raycubelos(s, camera1->o, o)) || camera1->o.dist(s) > hudmarkmaxdist) + { + return; + } + o = s; + vec camera = camera1->o; + o.sub(camera).normalize(); + particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size * camera1->o.dist(s) / 80, 0); + p->text = t; + p->font = font; + p->language = language; +} + void particle_meter(const vec &s, float val, int type, int fade, int color, int color2, float size) { if(!canaddparticles()) return; diff --git a/source/engine/rendertext.cpp b/source/engine/rendertext.cpp index ed0dde562..227c3418f 100644 --- a/source/engine/rendertext.cpp +++ b/source/engine/rendertext.cpp @@ -1,150 +1,234 @@ #include "engine.h" -static hashnameset fonts; -static font *fontdef = NULL; -static int fontdeftex = 0; +#include + +#if (!PANGO_VERSION_CHECK(1, 56, 0) || defined(OLDPANGO)) + #if (defined(WIN32) || defined(__APPLE__)) + #error this project requires pango >= 1.56 + #else + #define OLDPANGO 1 + #pragma message ("compiling with support for old pango (>= 1.38)") + #endif +#endif + +#ifdef OLDPANGO + #include +#endif + +static int fontid = 0; // used by UI for change detection +double fontsize = 0; // pixel height of the current font +const matrix4x3 *textmatrix = nullptr; // used for text particles +Shader *textshader = nullptr; // used for text particles + +#pragma region global_font_settings +// apply a black shadow or outline to console text to improve visibility +VARFP(conshadow, 0, 255, 255, clearconsoletextures()); +VARFP(conoutline, 0, 0, 255, clearconsoletextures()); + +// how often the cursor blinks, in milliseconds; set to zero to disable blinking +VARFP(cursorblink, 0, 750, 2000, +{ + cursorblink = cursorblink ? max(250, cursorblink) : 0; +}); -font *curfont = NULL; -int curfonttex = 0; +CVARP(cursorcolor, 0xFFFFFF); -void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *scale) +// configurable text colors +static bvec palette[10]; +ICOMMAND(textcolor, "ii", (int *i, int *c), { - font *f = &fonts[name]; - if(!f->name) f->name = newstring(name); - f->texs.shrink(0); - f->texs.add(textureload(tex)); - f->chars.shrink(0); - f->charoffset = '!'; - f->defaultw = *defaultw; - f->defaulth = *defaulth; - f->scale = *scale > 0 ? *scale : f->defaulth; - f->bordermin = 0.49f; - f->bordermax = 0.5f; - f->outlinemin = -1; - f->outlinemax = 0; - - fontdef = f; - fontdeftex = 0; -} + if(*i >= 0 && *i <= 9) + { + palette[*i] = bvec::hexcolor(*c); + clearconsoletextures(); + reloadfonts(); + } +}); -void fontborder(float *bordermin, float *bordermax) -{ - if(!fontdef) return; +static cairo_font_options_t *global_font_options = nullptr; // global font options +static cairo_surface_t *dummy_surface = nullptr; // used to measure text - fontdef->bordermin = *bordermin; - fontdef->bordermax = max(*bordermax, *bordermin+0.01f); +// NOTE: subpixel antialiasing is not available because we need to render on transparent backgrounds +static cairo_antialias_t antialias_ = CAIRO_ANTIALIAS_GRAY; +static cairo_hint_style_t hintstyle_ = CAIRO_HINT_STYLE_DEFAULT; +VARFP(fontantialias, 0, 1, 1, +{ + antialias_ = fontantialias ? CAIRO_ANTIALIAS_GRAY : CAIRO_ANTIALIAS_NONE; + if(!global_font_options) return; + cairo_font_options_set_antialias(global_font_options, antialias_); + clearconsoletextures(); + reloadfonts(); +}); +VARFP(fonthinting, -1, -1, 3, +{ + switch(fonthinting) + { + case 0: hintstyle_ = CAIRO_HINT_STYLE_NONE ; break; + case 1: hintstyle_ = CAIRO_HINT_STYLE_SLIGHT ; break; + case 2: hintstyle_ = CAIRO_HINT_STYLE_MEDIUM ; break; + case 3: hintstyle_ = CAIRO_HINT_STYLE_FULL ; break; + default: hintstyle_ = CAIRO_HINT_STYLE_DEFAULT; + } + if(!global_font_options) return; + cairo_font_options_set_hint_style(global_font_options, hintstyle_); + clearconsoletextures(); + reloadfonts(); +}) +#pragma endregion global_font_settings + +#pragma region particles +// a cache for rendered text particles +static hashtable particle_cache; +static vector particle_queue; +static void clear_text_particles() +{ + enumerate(particle_cache, text::Label, label, { label.clear(); }); + particle_cache.clear(); + particle_queue.setsize(0); } +#pragma endregion particles -void fontoutline(float *outlinemin, float *outlinemax) +#pragma region font_management +// register a TTF file +void addfontfile(const char *filename) { - if(!fontdef) return; - - fontdef->outlinemin = min(*outlinemin, *outlinemax-0.01f); - fontdef->outlinemax = *outlinemax; + const char *found = findfile(filename, "rb"); + if(!found || !found[0]) return; + //conoutf(CON_DEBUG, "Loading font file: %s", found); + #ifdef OLDPANGO + FcConfigAppFontAddFile(FcConfigGetCurrent(), (const unsigned char *)found); + #else + static PangoFontMap *fontMap = pango_cairo_font_map_get_default(); + pango_font_map_add_font_file(fontMap, found, nullptr); + #endif } +COMMANDN(registerfont, addfontfile, "s"); -void fontoffset(char *c) +struct Font { - if(!fontdef) return; + char *name; + int id; + char *opentype_features; + PangoFontDescription *desc; - fontdef->charoffset = c[0]; -} + Font() : name(nullptr), opentype_features(nullptr), desc(nullptr) {} + ~Font() + { + delete[] name; + delete[] opentype_features; + if(desc) pango_font_description_free(desc); + } +}; +static Font *curfont = nullptr, *lastfont = nullptr; +static hashnameset fonts; +static vector fontstack; -void fontscale(int *scale) +void newfont(const char *name, const char *family) { - if(!fontdef) return; - - fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth; + Font *f = &fonts[name]; + if(f->name) f->~Font(); + f->name = newstring(name); + f->id = fontid++; + f->opentype_features = newstring(""); + f->desc = pango_font_description_from_string(family); + + lastfont = f; } +COMMANDN(font, newfont, "ss"); -void fonttex(char *s) +void fontweight(int *weight) { - if(!fontdef) return; - - Texture *t = textureload(s); - loopv(fontdef->texs) if(fontdef->texs[i] == t) { fontdeftex = i; return; } - fontdeftex = fontdef->texs.length(); - fontdef->texs.add(t); + if(!lastfont || !lastfont->desc) return; + PangoWeight w; + switch(*weight) + { + case -2: w = PANGO_WEIGHT_ULTRALIGHT; break; + case -1: w = PANGO_WEIGHT_LIGHT; break; + case 1: w = PANGO_WEIGHT_MEDIUM; break; + case 2: w = PANGO_WEIGHT_SEMIBOLD; break; + case 3: w = PANGO_WEIGHT_BOLD; break; + case 4: w = PANGO_WEIGHT_ULTRABOLD; break; + case 5: w = PANGO_WEIGHT_HEAVY; break; + default: w = *weight < 0 ? PANGO_WEIGHT_THIN : (*weight > 0 ? PANGO_WEIGHT_ULTRAHEAVY : PANGO_WEIGHT_NORMAL); + } + pango_font_description_set_weight(lastfont->desc, w); } +COMMAND(fontweight, "i"); -void fontchar(float *x, float *y, float *w, float *h, float *offsetx, float *offsety, float *advance) +void fontstretch(int *stretch) { - if(!fontdef) return; - - font::charinfo &c = fontdef->chars.add(); - c.x = *x; - c.y = *y; - c.w = *w ? *w : fontdef->defaultw; - c.h = *h ? *h : fontdef->defaulth; - c.offsetx = *offsetx; - c.offsety = *offsety; - c.advance = *advance ? *advance : c.offsetx + c.w; - c.tex = fontdeftex; + if(!lastfont || !lastfont->desc) return; + PangoStretch s; + switch(*stretch) + { + case -3: s = PANGO_STRETCH_EXTRA_CONDENSED; break; + case -2: s = PANGO_STRETCH_CONDENSED; break; + case -1: s = PANGO_STRETCH_SEMI_CONDENSED; break; + case 0: s = PANGO_STRETCH_NORMAL; break; + case 1: s = PANGO_STRETCH_SEMI_EXPANDED; break; + case 2: s = PANGO_STRETCH_EXPANDED; break; + case 3: s = PANGO_STRETCH_EXTRA_EXPANDED; break; + default: s = *stretch < 0 ? PANGO_STRETCH_ULTRA_CONDENSED : PANGO_STRETCH_ULTRA_EXPANDED; + } + pango_font_description_set_stretch(lastfont->desc, s); } +COMMAND(fontstretch, "i"); -void fontskip(int *n) +// 0 = normal, 1 = oblique, 2 = italic +void fontstyle(int *style) { - if(!fontdef) return; - loopi(max(*n, 1)) + if(!lastfont || !lastfont->desc) return; + PangoStyle s; + switch(*style) { - font::charinfo &c = fontdef->chars.add(); - c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = 0; - c.tex = 0; + case 1: s = PANGO_STYLE_OBLIQUE; break; + case 2: s = PANGO_STYLE_ITALIC; break; + default: s = PANGO_STYLE_NORMAL; } + pango_font_description_set_style(lastfont->desc, s); } +COMMAND(fontstyle, "i"); -COMMANDN(font, newfont, "ssiii"); -COMMAND(fontborder, "ff"); -COMMAND(fontoutline, "ff"); -COMMAND(fontoffset, "s"); -COMMAND(fontscale, "i"); -COMMAND(fonttex, "s"); -COMMAND(fontchar, "fffffff"); -COMMAND(fontskip, "i"); - -void fontalias(const char *dst, const char *src) +void fontsmallcaps(int *val) { - font *s = fonts.access(src); - if(!s) return; - font *d = &fonts[dst]; - if(!d->name) d->name = newstring(dst); - d->texs = s->texs; - d->chars = s->chars; - d->charoffset = s->charoffset; - d->defaultw = s->defaultw; - d->defaulth = s->defaulth; - d->scale = s->scale; - d->bordermin = s->bordermin; - d->bordermax = s->bordermax; - d->outlinemin = s->outlinemin; - d->outlinemax = s->outlinemax; - - fontdef = d; - fontdeftex = d->texs.length()-1; + if(!lastfont || !lastfont->desc) return; + pango_font_description_set_variant(lastfont->desc, *val ? PANGO_VARIANT_SMALL_CAPS : PANGO_VARIANT_NORMAL); } +COMMAND(fontsmallcaps, "i"); -COMMAND(fontalias, "ss"); +void fontfeatures(char *features) +{ + if(!lastfont) return; + if(lastfont->opentype_features) delete[] lastfont->opentype_features; + lastfont->opentype_features = newstring(features); +} +COMMAND(fontfeatures, "s"); -font *findfont(const char *name) +void fontvariations(char *variations) { - return fonts.access(name); + if(!lastfont || !lastfont->desc) return; + pango_font_description_set_variations(lastfont->desc, variations); } +COMMAND(fontvariations, "s"); -bool setfont(const char *name) +// set the current font +static inline bool setfont(Font *f) { - font *f = fonts.access(name); if(!f) return false; curfont = f; return true; } +bool setfont(const char *name) +{ + Font *f = fonts.access(name); + return setfont(f); +} -static vector fontstack; - +// font stack management void pushfont() { fontstack.add(curfont); } - bool popfont() { if(fontstack.empty()) return false; @@ -152,263 +236,607 @@ bool popfont() return true; } -void gettextres(int &w, int &h) +int getcurrentfontid() { - if(w < MINRESW || h < MINRESH) - { - if(MINRESW > w*MINRESH/h) - { - h = h*MINRESW/w; - w = MINRESW; - } - else - { - w = w*MINRESH/h; - h = MINRESH; - } - } + return curfont->id; } -float text_widthf(const char *str) +void reloadfonts() { - float width, height; - text_boundsf(str, width, height); - return width; + clear_text_particles(); + UI::clearlabels(); } +#pragma endregion font_management -#define FONTTAB (4*FONTW) -#define TEXTTAB(x) ((int((x)/FONTTAB)+1.0f)*FONTTAB) - -void tabify(const char *str, int *numtabs) +#pragma region pango_initialization +bool init_pangocairo() { - int tw = max(*numtabs, 0)*FONTTAB-1, tabs = 0; - for(float w = text_widthf(str); w <= tw; w = TEXTTAB(w)) ++tabs; - int len = strlen(str); - char *tstr = newstring(len + tabs); - memcpy(tstr, str, len); - memset(&tstr[len], '\t', tabs); - tstr[len+tabs] = '\0'; - stringret(tstr); -} + global_font_options = cairo_font_options_create(); + if(CAIRO_STATUS_SUCCESS != cairo_font_options_status(global_font_options)) + { + return false; + } + cairo_font_options_set_antialias (global_font_options, antialias_); + cairo_font_options_set_hint_style (global_font_options, hintstyle_); + cairo_font_options_set_hint_metrics(global_font_options, CAIRO_HINT_METRICS_ON); + + dummy_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); + if(CAIRO_STATUS_SUCCESS != cairo_surface_status(dummy_surface)) + { + return false; + } -COMMAND(tabify, "si"); + loopi(10) palette[i] = bvec(255, 255, 255); -void draw_textf(const char *fstr, double left, double top, ...) + conoutf(CON_INIT, "Text rendering: Pango %s, Cairo %s", pango_version_string(), cairo_version_string()); + return true; +} + +void done_pangocairo() { - defvformatstring(str, top, fstr); - draw_text(str, left, top); + fonts.clear(); + if(global_font_options) cairo_font_options_destroy(global_font_options); + if(dummy_surface) cairo_surface_destroy(dummy_surface); } +#pragma endregion pango_initialization -const matrix4x3 *textmatrix = NULL; -float textscale = 1; +#pragma region markup_and_layout +//stack[sp] is current color index +static inline bvec text_color(char c, char *stack, int size, int& sp, bvec color) +{ + if(c=='s') // save color + { + c = stack[sp]; + if(sp 0) --sp; c = stack[sp]; } // restore color + else stack[sp] = c; + if(c >= '0' && c <= '9') return palette[c - '0']; + } + return color; +} -static float draw_char(Texture *&tex, int c, float x, float y, float scale) +#define MARKUP_CASE(x, X, pango_attr_func, PANGO_ATTR, name) \ + case x: \ + if(begin_##name < 0) begin_##name = j; \ + ++n_##name; \ + break; \ + case X: \ + if(begin_##name >= 0) \ + { \ + if(n_##name >= 0) --n_##name; \ + if(n_##name) break; \ + m = pango_attr_func(PANGO_ATTR); \ + m->start_index = begin_##name; \ + m->end_index = j; \ + pango_attr_list_insert(list, m); \ + begin_##name = -1; \ + } \ + break; + +// adds a string to the layout parsing basic markup (\f codes) +// NOTE: `markup` is the original string with \f codes; `text` is the stripped version without \f codes +static void add_text_to_layout( + const char *markup, // the input string with \f codes + int markup_length, // length of the above + PangoLayout *layout, // the layout to fill + const bvec& color, // initial color of the text + int *map_markup_to_text, // output: maps character indices from markup to text + int *map_text_to_markup, // output: reverse of the above + const char *lang, // optional language code of the text (can affect text shaping) + bool no_fallback // disable fallback fonts for unavailable glyphs? +) { - font::charinfo &info = curfont->chars[c-curfont->charoffset]; - if(tex != curfont->texs[info.tex]) + char *text = newstring(markup_length); + + bvec tcolor = color; + char colorstack[10]; + colorstack[0] = '\0'; + int cpos = 0; + + PangoAttrList *list = pango_attr_list_new(); + PangoAttribute *attr; // colors and global features + PangoAttribute *m; // used for markup attributes + + // OpenType features + if(curfont->opentype_features[0]) + { + attr = pango_attr_font_features_new(curfont->opentype_features); // pango 1.38 + pango_attr_list_insert(list, attr); + } + // no fallback to system fonts + if(no_fallback) + { + attr = pango_attr_fallback_new(FALSE); + pango_attr_list_insert(list, attr); + } + // language + if(lang) { - xtraverts += gle::end(); - tex = curfont->texs[info.tex]; - setusedtexture(tex); + attr = pango_attr_language_new(pango_language_from_string(lang)); + pango_attr_list_insert(list, attr); } - x *= textscale; - y *= textscale; - scale *= textscale; + // initial color: arguments must be converted to 16-bit values + attr = pango_attr_foreground_new(tcolor.r * 257, tcolor.g * 257, tcolor.b * 257); + attr->start_index = 0; - float x1 = x + scale*info.offsetx, - y1 = y + scale*info.offsety, - x2 = x + scale*(info.offsetx + info.w), - y2 = y + scale*(info.offsety + info.h), - tx1 = info.x / tex->xs, - ty1 = info.y / tex->ys, - tx2 = (info.x + info.w) / tex->xs, - ty2 = (info.y + info.h) / tex->ys; + int begin_bold = -1, begin_italic = -1, begin_underline = -1, begin_strikethrough = -1; + int n_bold = 0, n_italic = 0, n_underline = 0, n_strikethrough = 0; // counters used for nesting - if(textmatrix) + // parse markup + int i = 0, j = 0; + for(; i < markup_length; ++i) { - gle::attrib(textmatrix->transform(vec2(x1, y1))); gle::attribf(tx1, ty1); - gle::attrib(textmatrix->transform(vec2(x2, y1))); gle::attribf(tx2, ty1); - gle::attrib(textmatrix->transform(vec2(x2, y2))); gle::attribf(tx2, ty2); - gle::attrib(textmatrix->transform(vec2(x1, y2))); gle::attribf(tx1, ty2); + if(markup[i] == '\f' && i < (markup_length - 1)) + { + switch(markup[i+1]) + { + MARKUP_CASE('b', 'B', pango_attr_weight_new , PANGO_WEIGHT_BOLD , bold); + MARKUP_CASE('i', 'I', pango_attr_style_new , PANGO_STYLE_ITALIC , italic); + MARKUP_CASE('u', 'U', pango_attr_underline_new , PANGO_UNDERLINE_SINGLE, underline); + MARKUP_CASE('t', 'T', pango_attr_strikethrough_new, TRUE , strikethrough); + default: + { + tcolor = text_color(markup[i+1], colorstack, sizeof(colorstack), cpos, color); + if(!attr) break; + + attr->end_index = j + 1; + pango_attr_list_insert(list, attr); + attr = nullptr; + } + } + if(map_markup_to_text) map_markup_to_text[i] = j; + ++i; + continue; + } + if(!attr) + { + attr = pango_attr_foreground_new(tcolor.r * 257, tcolor.g * 257, tcolor.b * 257); + attr->start_index = j; + } + if(map_markup_to_text) map_markup_to_text[i] = j; + if(map_text_to_markup) map_text_to_markup[j] = i; + text[j++] = markup[i]; } - else + text[j] = '\0'; + if(map_markup_to_text) map_markup_to_text[i] = j; + if(map_text_to_markup) map_text_to_markup[j] = i; + + if(attr) // color { - gle::attribf(x1, y1); gle::attribf(tx1, ty1); - gle::attribf(x2, y1); gle::attribf(tx2, ty1); - gle::attribf(x2, y2); gle::attribf(tx2, ty2); - gle::attribf(x1, y2); gle::attribf(tx1, ty2); + attr->end_index = j; + pango_attr_list_insert(list, attr); + } + if(begin_bold >= 0) + { + m = pango_attr_weight_new(PANGO_WEIGHT_BOLD); + m->start_index = begin_bold; m->end_index = j; + pango_attr_list_insert(list, m); + } + if(begin_italic >= 0) + { + m = pango_attr_style_new(PANGO_STYLE_ITALIC); + m->start_index = begin_italic; m->end_index = j; + pango_attr_list_insert(list, m); + } + if(begin_underline >= 0) + { + m = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + m->start_index = begin_underline; m->end_index = j; + pango_attr_list_insert(list, m); + } + if(begin_strikethrough >= 0) + { + m = pango_attr_strikethrough_new(TRUE); + m->start_index = begin_strikethrough; m->end_index = j; + pango_attr_list_insert(list, m); } - return scale*info.advance; + pango_layout_set_text(layout, text, -1); + delete[] text; + pango_layout_set_attributes(layout, list); + pango_attr_list_unref(list); } +#undef MARKUP_CASE + +// creates a layout and fills it with text, and measures its width, height and horizontal offset +static PangoLayout *measure_text_internal( + const char *str, // the string to measure + int len, // length of the string + int max_width, // maximum width in pixels (no limit if <=0) + int align, // text alignment: -1 = left, 0 = center, 1 = right + int justify, // text justification (0 = disable, enable otherwise) + const bvec& color, // initial color of the text + int& width, // output: the measured width + int& height, // output: the measured height + int& offset, // output: the measured offset + int *map_markup_to_text, // output: maps character indices from markup to text + int *map_text_to_markup, // output: reverse of the above + const char *lang, // optional language code of the text (can affect text shaping) + bool no_fallback // disable fallback fonts for unavailable glyphs? +) +{ + // create cairo context + cairo_t *cairo_context = cairo_create(dummy_surface); + cairo_set_font_options(cairo_context, global_font_options); -VARP(textbright, 0, 85, 100); + // create layout + PangoLayout *layout = pango_cairo_create_layout(cairo_context); -//stack[sp] is current color index -static void text_color(char c, char *stack, int size, int &sp, bvec color, int a) + // set font family and size + pango_font_description_set_absolute_size(curfont->desc, fontsize * PANGO_SCALE); // pango 1.8 + pango_layout_set_font_description(layout, curfont->desc); + + // line wrapping: set maximum width, alignment and justification + if(max_width > 0) + { + pango_layout_set_width(layout, max_width * PANGO_SCALE); + pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); + pango_layout_set_alignment(layout, align > 0 ? PANGO_ALIGN_RIGHT : align < 0 ? PANGO_ALIGN_LEFT : PANGO_ALIGN_CENTER); + pango_layout_set_justify(layout, justify ? TRUE : FALSE); + } + + // fill layout + add_text_to_layout(str, len, layout, color, map_markup_to_text, map_text_to_markup, lang, no_fallback); + + // get pixel size + PangoRectangle r; + pango_layout_get_extents(layout, nullptr, &r); + width = r.width / PANGO_SCALE; + height = r.height / PANGO_SCALE; + offset = -r.x / PANGO_SCALE; + + cairo_destroy(cairo_context); + return layout; +} +#pragma endregion markup_and_layout + +#pragma region label +namespace text { - if(c=='s') // save color + Label::Label() : + tex(0), + layout(nullptr), + map_markup_to_text(nullptr), + map_text_to_markup(nullptr) + {}; + Label::~Label() { - c = stack[sp]; - if(sp 0) --sp; c = stack[sp]; } // restore color - else stack[sp] = c; - switch(c) + other.tex = 0; + other.layout = nullptr; + other.map_markup_to_text = nullptr; + other.map_text_to_markup = nullptr; + } + Label& Label::operator=(Label&& other) noexcept + { + if(&other == this) return *this; + this->~Label(); + w = other.w; + h = other.h; + ox = other.ox; + oy = other.oy; + tex = exchange(other.tex, 0); + layout = exchange(other.layout, nullptr); + map_markup_to_text = exchange(other.map_markup_to_text, nullptr); + map_text_to_markup = exchange(other.map_text_to_markup, nullptr); + return *this; + } + // empties the text content of the label + void Label::clear() + { + if(tex != 0) { - case '0': color = bvec( 64, 255, 128); break; // green: player talk - case '1': color = bvec( 96, 160, 255); break; // blue: "echo" command - case '2': color = bvec(255, 192, 64); break; // yellow: gameplay messages - case '3': color = bvec(255, 64, 64); break; // red: important errors - case '4': color = bvec(128, 128, 128); break; // gray - case '5': color = bvec(192, 64, 192); break; // magenta - case '6': color = bvec(255, 128, 0); break; // orange - case '7': color = bvec(255, 255, 255); break; // white - case '8': color = bvec( 0, 255, 255); break; // cyan - case '9': color = bvec(255, 192, 203); break; // pink - default: gle::color(color, a); return; // provided color: everything else + glDeleteTextures(1, &tex); + tex = 0; } - if(textbright != 100) color.scale(textbright, 100); - gle::color(color, a); + if(layout != nullptr) + { + g_object_unref(layout); + layout = nullptr; + } + delete[] map_markup_to_text; + delete[] map_text_to_markup; + map_markup_to_text = map_text_to_markup = nullptr; } -} -#define TEXTSKELETON \ - float y = 0, x = 0, scale = curfont->scale/float(curfont->defaulth);\ - int i;\ - for(i = 0; str[i]; i++)\ - {\ - TEXTINDEX(i)\ - int c = uchar(str[i]);\ - if(c=='\t') { x = TEXTTAB(x); TEXTWHITE(i) }\ - else if(c==' ') { x += scale*curfont->defaultw; TEXTWHITE(i) }\ - else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\ - else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\ - else if(curfont->chars.inrange(c-curfont->charoffset))\ - {\ - float cw = scale*curfont->chars[c-curfont->charoffset].advance;\ - if(cw <= 0) continue;\ - if(maxwidth >= 0)\ - {\ - int j = i;\ - float w = cw;\ - for(; str[i+1]; i++)\ - {\ - int c = uchar(str[i+1]);\ - if(c=='\f') { if(str[i+2]) i++; continue; }\ - if(!curfont->chars.inrange(c-curfont->charoffset)) break;\ - float cw = scale*curfont->chars[c-curfont->charoffset].advance;\ - if(cw <= 0 || w + cw > maxwidth) break;\ - w += cw;\ - }\ - if(x + w > maxwidth && x > 0) { (void)j; TEXTLINE(j-1) x = 0; y += FONTH; }\ - TEXTWORD\ - }\ - else { TEXTCHAR(i) }\ - }\ - } - -//all the chars are guaranteed to be either drawable or color commands -#define TEXTWORDSKELETON \ - for(; j <= i; j++)\ - {\ - TEXTINDEX(j)\ - int c = uchar(str[j]);\ - if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\ - else { float cw = scale*curfont->chars[c-curfont->charoffset].advance; TEXTCHAR(j) }\ - } + // draws the label to the screen + void Label::draw( + double left, // screen X coordinate + double top, // screen Y coordinate + int alpha, // text opacity (0-255) + bool black // make it black? (used for shadows) + ) const + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -#define TEXTEND(cursor) if(cursor >= i) { do { TEXTINDEX(cursor); } while(0); } + if(textshader) textshader->set(); // text particles + else hudtextshader->set(); // UI text -int text_visible(const char *str, float hitx, float hity, int maxwidth) -{ - #define TEXTINDEX(idx) - #define TEXTWHITE(idx) if(y+FONTH > hity && x >= hitx) return idx; - #define TEXTLINE(idx) if(y+FONTH > hity) return idx; - #define TEXTCOLOR(idx) - #define TEXTCHAR(idx) x += cw; TEXTWHITE(idx) - #define TEXTWORD TEXTWORDSKELETON - TEXTSKELETON - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD - return i; -} + gle::color(black ? bvec(0, 0, 0) : bvec(255*alpha/255.f, 255*alpha/255.f, 255*alpha/255.f), alpha); + glBindTexture(GL_TEXTURE_RECTANGLE, tex); + gle::defvertex(textmatrix ? 3 : 2); + gle::deftexcoord0(); -void text_boundsf(const char *str, float &width, float &height, int maxwidth) -{ - #define TEXTINDEX(idx) - #define TEXTWHITE(idx) - #define TEXTLINE(idx) if(x > width) width = x; - #define TEXTCOLOR(idx) - #define TEXTCHAR(idx) x += cw; - #define TEXTWORD x += w; - width = 0; - TEXTSKELETON - height = y + FONTH; - TEXTLINE(_) - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD -} + // NOTE: `GL_TRIANGLE_STRIP` does not work with `textmatrix` so we have to use `GL_QUADS` instead + gle::begin(textmatrix ? GL_QUADS : GL_TRIANGLE_STRIP); + if(textmatrix) // text particle inside the world + { + gle::attrib(textmatrix->transform(vec2(left , top ))); gle::attribf(0, 0); + gle::attrib(textmatrix->transform(vec2(left+w, top ))); gle::attribf(w, 0); + gle::attrib(textmatrix->transform(vec2(left+w, top+h))); gle::attribf(w, h); + gle::attrib(textmatrix->transform(vec2(left , top+h))); gle::attribf(0, h); + } + else // UI text + { + gle::attribf(left+w, top ); gle::attribf(w, 0); + gle::attribf(left , top ); gle::attribf(0, 0); + gle::attribf(left+w, top+h); gle::attribf(w, h); + gle::attribf(left , top+h); gle::attribf(0, h); + } + gle::end(); + } + // like `draw()` but with a shadow + void Label::draw_as_console(double left, double top) const + { + if(conshadow) + { + const double d = 0.75 * conscale; + draw(left - d, top + d, conshadow, true); + } + draw(left, top); + } -Shader *textshader = NULL; + // converts (x,y) pixel coordinates to a character (byte) index + // NOTE: do not call if the label was not prepared with `keep_layout=true` + int Label::xy_to_index(float x, float y) const + { + const int px = (x - ox) * PANGO_SCALE, py = (y - oy) * PANGO_SCALE; + int ix, _trailing; + if(!pango_layout_xy_to_index(layout, px, py, &ix, &_trailing)) + { + // user clicked outside of the label: set the cursor to the end of the string + ix = strlen(pango_layout_get_text(layout)); + } + return map_text_to_markup ? map_text_to_markup[ix] : ix; + } -void draw_text(const char *str, float left, float top, int r, int g, int b, int a, int cursor, int maxwidth) -{ - #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; } - #define TEXTWHITE(idx) - #define TEXTLINE(idx) - #define TEXTCOLOR(idx) if(usecolor) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a); - #define TEXTCHAR(idx) draw_char(tex, c, left+x, top+y, scale); x += cw; - #define TEXTWORD TEXTWORDSKELETON - char colorstack[10]; - colorstack[0] = '\0'; //indicate user color - bvec color(r, g, b); - if(textbright != 100) color.scale(textbright, 100); - int colorpos = 0; - float cx = -FONTW, cy = 0; - bool usecolor = true; - if(a < 0) { usecolor = false; a = -a; } - Texture *tex = curfont->texs[0]; - (textshader ? textshader : hudtextshader)->set(); - LOCALPARAMF(textparams, curfont->bordermin, curfont->bordermax, curfont->outlinemin, curfont->outlinemax); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - setusedtexture(tex); - gle::color(color, a); - gle::defvertex(textmatrix ? 3 : 2); - gle::deftexcoord0(); - gle::begin(GL_QUADS); - TEXTSKELETON - TEXTEND(cursor) - xtraverts += gle::end(); - if(cursor >= 0 && (totalmillis/350)&1) - { - gle::color(color, a); - if(maxwidth >= 0 && cx >= maxwidth && cx > 0) { cx = 0; cy += FONTH; } - draw_char(tex, '_', left+cx, top+cy, scale); - xtraverts += gle::end(); - } - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD -} + // creates a label from a string + Label prepare( + const char *str, // the label text + int max_width, // maximum width in pixels + const bvec& color, // initial color of the text + int cursor, // byte index of the cursor (disabled if <0) + double outline, // outline thickness + const bvec4& outline_color, // outline color (RGBA) + int align, // text alignment: -1 = left, 0 = center, 1 = right + int justify, // text justification (0 = disable, enable otherwise) + const char *lang, // optional language code of the text (can affect text shaping) + bool no_fallback, // disable fallback fonts for unavailable glyphs? + bool keep_layout, // keep layout in memory? (necessary if you need to call `xy_to_index()` on the label) + bool reserve_cursor // reserve space to the right for the cursor? + ) + { + Label label; + + // measure text dimensions and create pango layout + int width, height, offset; + const int len = strlen(str); + if(cursor >= 0 || keep_layout) label.map_markup_to_text = new int[len+1]; + if(keep_layout) label.map_text_to_markup = new int[len+1]; + label.layout = measure_text_internal(str, len, max_width, align, justify, color, width, height, offset, label.map_markup_to_text, label.map_text_to_markup, lang, no_fallback); + if(!width || !height) + { + return label; + } -void reloadfonts() -{ - enumerate(fonts, font, f, loopv(f.texs) { if(!reloadtexture(f.texs[i])) { fatal("Failed to reload font texture"); }}); -} + // create surface and cairo context + if(cursor >= 0 || reserve_cursor) // reserve space for the cursor + { + width += max(1., ceil(fontsize / 16.)); + } + const int outline_offset = ceil(outline); + width += 2 * outline_offset; + height += 2 * outline_offset; + cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *cairo_context = cairo_create(surface); + cairo_set_font_options(cairo_context, global_font_options); + cairo_move_to(cairo_context, offset + outline_offset, outline_offset); + + // draw text outline + if(outline) + { + cairo_set_source_rgba(cairo_context, + outline_color.r/255., + outline_color.g/255., + outline_color.b/255., + outline_color.a/255. + ); + cairo_set_line_join(cairo_context, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_width(cairo_context, 2 * outline); + pango_cairo_layout_path(cairo_context, label.layout); + cairo_stroke(cairo_context); + } + + // draw text + cairo_set_source_rgba(cairo_context, 1.0, 1.0, 1.0, 1.0); + cairo_move_to(cairo_context, offset + outline_offset, outline_offset); + pango_cairo_show_layout(cairo_context, label.layout); + + // draw the cursor + if(cursor >= 0 && ( + !cursorblink || + (totalmillis - inputmillis <= cursorblink) || + ((totalmillis - inputmillis) % (2 * cursorblink)) <= cursorblink + )) + { + if(cursor > len) cursor = len; + cursor = min((int)strlen(pango_layout_get_text(label.layout)), label.map_markup_to_text[cursor]); + PangoRectangle cursor_rect; + pango_layout_get_cursor_pos(label.layout, cursor, &cursor_rect, nullptr); + + const double cursor_width = max(1., fontsize / 16.); + cairo_rectangle(cairo_context, + cursor_rect.x / PANGO_SCALE + outline_offset, + cursor_rect.y / PANGO_SCALE + outline_offset, + cursor_width, + cursor_rect.height / PANGO_SCALE + ); + cairo_set_source_rgba(cairo_context, + cursorcolor.r / 255., + cursorcolor.g / 255., + cursorcolor.b / 255., + 1. + ); + cairo_fill(cairo_context); + } + + if(!keep_layout) + { + g_object_unref(label.layout); + label.layout = nullptr; + } + + // create GPU texture + glGenTextures(1, &label.tex); + if(!label.tex) + { + cairo_destroy(cairo_context); + cairo_surface_destroy(surface); + return label; + } + glBindTexture(GL_TEXTURE_RECTANGLE, label.tex); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_COMPRESSED_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, cairo_image_surface_get_data(surface)); + label.w = width; + label.h = height; + label.ox = offset + outline_offset; + label.oy = outline_offset; + + // clean up + cairo_destroy(cairo_context); + cairo_surface_destroy(surface); + return label; + } + + // same as `prepare()` but with appropriate settings for console text + Label prepare_for_console(const char *str, int max_width, int cursor) + { + return prepare(str, max_width, bvec(255, 255, 255), cursor, conoutline ? ceil(FONTH / 32.) : 0, bvec4(0, 0, 0, conoutline)); + } + + // same as `prepare()` but use a cache so that the same label doesn't have to be recreated every time + const Label& prepare_for_particle( + const char *str, + const bvec& color, + double outline, + const bvec4& outline_color, + const char *lang, + bool no_fallback + ) + { + const int c = color.tohexcolor(); + const char *l = lang ? lang : ""; + uint + key = crc32(0 , (const Bytef *)str, strlen(str)) + curfont->id; + key = crc32(key, (const Bytef *)(&outline), sizeof(double)); + key = crc32(key, (const Bytef *)(&c), sizeof(int)); + key = crc32(key, (const Bytef *)(&outline_color.mask), sizeof(uint)); + key = crc32(key, (const Bytef *)l, strlen(l)); + + Label& label = particle_cache[key]; + if(label.valid()) return label; + + label = prepare(str, 0, color, -1, outline, outline_color, -1, 0, lang, no_fallback); + if(particle_queue.length() >= 256) + { + const uint oldkey = particle_queue[0]; + particle_cache[oldkey].clear(); + particle_cache.remove(oldkey); + particle_queue.remove(0); + } + particle_queue.add(key); + return label; + } + // measure the dimensions of a string without creating the label (skips texture creation) + void measure( + const char *str, // the string to measure + int max_width, // maximum width in pixels + int& width, // output: the measured width + int& height, // output: the measured height + int align, // text alignment: -1 = left, 0 = center, 1 = right + int justify, // text justification (0 = disable, enable otherwise) + const char *lang, // optional language code of the text (can affect text shaping) + bool no_fallback // disable fallback fonts for unavailable glyphs? + ) + { + int _offset; + PangoLayout *layout = measure_text_internal(str, strlen(str), max_width, align, justify, bvec(0, 0, 0), width, height, _offset, nullptr, nullptr, lang, no_fallback); + g_object_unref(layout); + } + + // draw a string directly to the screen + void draw( + const char *str, // the string to draw + double left, // screen X coordinate + double top, // screen Y coordinate + const bvec& color, // initial color of the text + int alpha, // text opacity (0-255) + int max_width, // maximum width in pixels + int align, // text alignment: -1 = left, 0 = center, 1 = right + int justify, // text justification (0 = disable, enable otherwise) + const char *lang, // optional language code of the text (can affect text shaping) + bool no_fallback // disable fallback fonts for unavailable glyphs? + ) + { + const Label label = prepare(str, max_width, color, -1, 0, bvec4(color, alpha), align, justify, lang, no_fallback); + if(label.valid()) label.draw(left, top, alpha); + } + + // same as `draw()` but with appropriate settings for console text + void draw_as_console(const char *str, double left, double top, int max_width, int cursor) + { + const Label label = prepare_for_console(str, max_width, cursor); + if(label.valid()) + { + if(conshadow) + { + const double d = 0.75 * conscale; + label.draw(left - d, top + d, conshadow, true); + } + label.draw(left, top); + } + } + + void getres(int& w, int& h) + { + if(w < MINRESW || h < MINRESH) + { + if(MINRESW > w*MINRESH/h) + { + h = h*MINRESW/w; + w = MINRESW; + } + else + { + w = w*MINRESH/h; + h = MINRESH; + } + } + } +} // namespace text +#pragma endregion label \ No newline at end of file diff --git a/source/engine/server.cpp b/source/engine/server.cpp index 5176a81bb..579fed8a8 100644 --- a/source/engine/server.cpp +++ b/source/engine/server.cpp @@ -45,17 +45,13 @@ void logoutf(const char *fmt, ...) va_end(args); } - static void writelog(FILE *file, const char *buf) { - static uchar ubuf[512]; - size_t len = strlen(buf), carry = 0; - while(carry < len) - { - size_t numu = encodeutf8(ubuf, sizeof(ubuf)-1, &((const uchar *)buf)[carry], len - carry, &carry); - if(carry >= len) ubuf[numu++] = '\n'; - fwrite(ubuf, 1, numu, file); - } + static char ubuf[512]; + filteruni(ubuf, buf, 511); + const size_t len = min((size_t)511, strlen(ubuf)); + ubuf[len] = '\n'; + fwrite(ubuf, 1, len+1, file); } static void writelogv(FILE *file, const char *fmt, va_list args) @@ -810,14 +806,11 @@ static BOOL WINAPI consolehandler(DWORD dwCtrlType) static void writeline(logline &line) { - static uchar ubuf[512]; - size_t len = strlen(line.buf), carry = 0; - while(carry < len) - { - size_t numu = encodeutf8(ubuf, sizeof(ubuf), &((uchar *)line.buf)[carry], len - carry, &carry); - DWORD written = 0; - WriteConsole(outhandle, ubuf, numu, &written, NULL); - } + static char ubuf[512]; + filteruni(ubuf, line.buf, 511); + const size_t len = min((size_t)511, strlen(ubuf)); + DWORD written; + WriteConsole(outhandle, ubuf, len, &written, NULL); } static void setupconsole() diff --git a/source/engine/serverbrowser.cpp b/source/engine/serverbrowser.cpp index 88c535f4b..3e83c9fd1 100644 --- a/source/engine/serverbrowser.cpp +++ b/source/engine/serverbrowser.cpp @@ -542,9 +542,9 @@ void checkpings() si->attr.setsize(0); loopj(numattr) { int attr = getint(p); if(p.overread()) break; si->attr.add(attr); } getstring(text, p); - filtertext(si->map, text, false, false, false); + filtertext(si->map, text, T_NONE); getstring(text, p); - filtertext(si->desc, text, true, false); + filtertext(si->desc, text, T_COLORS | T_WHITESPACE); if(p.remaining()) { si->gameversion = getint(p); @@ -733,7 +733,7 @@ COMMAND(initservers, ""); void writeservercfg() { if(!game::savedservers()) return; - stream *f = openutf8file(path(game::savedservers(), true), "w"); + stream *f = openfile(path(game::savedservers(), true), "w"); if(!f) return; int kept = 0; loopv(servers) diff --git a/source/engine/shader.cpp b/source/engine/shader.cpp index 922a9c16f..8ddbebed1 100644 --- a/source/engine/shader.cpp +++ b/source/engine/shader.cpp @@ -1031,21 +1031,20 @@ void setupshaders() "uniform mat4 hudmatrix;\n" "varying vec2 texcoord0;\n" "varying vec4 colorscale;\n" - "void main(void) {\n" + "void main(void)\n" + "{\n" " gl_Position = hudmatrix * vvertex;\n" " texcoord0 = vtexcoord0;\n" " colorscale = vcolor;\n" "}\n", - "uniform sampler2D tex0;\n" - "uniform vec4 textparams;\n" + "uniform sampler2DRect tex0;\n" "varying vec2 texcoord0;\n" "varying vec4 colorscale;\n" "fragdata(0) vec4 fragcolor;\n" - "void main(void) {\n" - " float dist = texture2D(tex0, texcoord0).r;\n" - " float border = smoothstep(textparams.x, textparams.y, dist);\n" - " float outline = smoothstep(textparams.z, textparams.w, dist);\n" - " fragcolor = vec4(colorscale.rgb * outline, colorscale.a * border);\n" + "void main(void)\n" + "{\n" + " fragcolor = colorscale * texture2DRect(tex0, texcoord0);\n" + " if(fragcolor.a != 0) fragcolor.rgb /= fragcolor.a;\n" "}\n"); hudnotextureshader = newshader(0, "hudnotexture", "attribute vec4 vvertex, vcolor;\n" @@ -1546,7 +1545,7 @@ void cleanupshaders() cleanuppostfx(true); loadedshaders = false; - nullshader = hudshader = hudnotextureshader = NULL; + nullshader = hudshader = hudtextshader = hudnotextureshader = NULL; enumerate(shaders, Shader, s, s.cleanup()); Shader::lastshader = NULL; glUseProgram_(0); diff --git a/source/engine/ui.cpp b/source/engine/ui.cpp index ee669a02e..0218cb8c8 100644 --- a/source/engine/ui.cpp +++ b/source/engine/ui.cpp @@ -184,7 +184,10 @@ namespace UI void resetblend() { changeblend(BLEND_ALPHA, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } void modblend() { changeblend(BLEND_MOD, GL_ZERO, GL_SRC_COLOR); } + static int uimillis = 0; + FVARP(uiscale, 0.5f, 1.0f, 1.5f); + VARFP(uifps, 0, 60, 1000, { uimillis = uifps ? (1000/uifps) : 0; }); struct Object { @@ -290,6 +293,23 @@ namespace UI }); } + // called when the window is closed + virtual void hide() + { + loopchildren(o, + { + o->hide(); + }); + } + + virtual void clearlabels() + { + loopchildren(o, + { + o->clearlabels(); + }); + } + void adjustchildrento(float px, float py, float pw, float ph) { loopchildren(o, o->adjustlayout(px, py, pw, ph)); @@ -625,6 +645,7 @@ namespace UI void hide() { + Object::hide(); if(onhide) execute(onhide); } @@ -804,6 +825,8 @@ namespace UI resetstate(); } + void hide() { Object::hide(); }; + bool show(Window *w) { if(children.find(w) >= 0) return false; @@ -1309,6 +1332,15 @@ namespace UI void attrib() { gle::attribub(r, g, b, a); } static void def() { gle::defcolor(4, GL_UNSIGNED_BYTE); } + + bool operator==(const Color &other) const + { + return r == other.r && g == other.g && b == other.b && a == other.a; + } + bool operator!=(const Color &other) const + { + return r != other.r || g != other.g || b != other.b || a != other.a; + } }; struct FillColor : Target @@ -1941,8 +1973,6 @@ namespace UI } }; - // default size of text in terms of rows per screenful - VARP(uitextrows, 1, 24, 200); FVAR(uitextscale, 1, 0, 0); #define SETSTR(dst, src) do { \ @@ -1950,9 +1980,97 @@ namespace UI else dst = newstring(src); \ } while(0) + // text attributes + static int curwrapalign = -1, curshadow = 0, curfontoutlinealpha = 0; + static float curfontoutline = 0.f; + static int curjustify = false, curnofallback = false; + static const char *curlanguage = newstring(""); + + #define WITHTEXTATTR(name, tmp, val, body) do { \ + tmp = cur##name; \ + cur##name = val; \ + body; \ + cur##name = tmp; \ + } while(0) + + struct WrapAlign : Object + { + static const char *typestr() { return "#WrapAlign"; } + const char *gettype() const { return typestr(); } + + int val, tmp; + void setup(int val_) { val = val_; } + void layout() { WITHTEXTATTR(wrapalign, tmp, val, Object::layout()); } + void draw(float sx, float sy) { WITHTEXTATTR(wrapalign, tmp, val, Object::draw(sx, sy)); } + void buildchildren(uint *contents) { WITHTEXTATTR(wrapalign, tmp, val, Object::buildchildren(contents)); } + }; + struct Justify : Object + { + static const char *typestr() { return "#Justify"; } + const char *gettype() const { return typestr(); } + + int val, tmp; + void setup(int val_) { val = val_; } + void layout() { WITHTEXTATTR(justify, tmp, val, Object::layout()); } + void draw(float sx, float sy) { WITHTEXTATTR(justify, tmp, val, Object::draw(sx, sy)); } + void buildchildren(uint *contents) { WITHTEXTATTR(justify, tmp, val, Object::buildchildren(contents)); } + }; + struct Shadow : Object + { + static const char *typestr() { return "#Shadow"; } + const char *gettype() const { return typestr(); } + + int val, tmp; + void setup(int val_) { val = val_; } + void layout() { WITHTEXTATTR(shadow, tmp, val, Object::layout()); } + void draw(float sx, float sy) { WITHTEXTATTR(shadow, tmp, val, Object::draw(sx, sy)); } + void buildchildren(uint *contents) { WITHTEXTATTR(shadow, tmp, val, Object::buildchildren(contents)); } + }; + struct FontOutline : Object + { + static const char *typestr() { return "#FontOutline"; } + const char *gettype() const { return typestr(); } + + float val, tmp; + int alpha_val, alpha_tmp; + void setup(float val_, int alpha_val_) { val = val_; alpha_val = alpha_val_; } + void layout() { WITHTEXTATTR(fontoutline, tmp, val, WITHTEXTATTR(fontoutlinealpha, alpha_tmp, alpha_val, Object::layout())); } + void draw(float sx, float sy) { WITHTEXTATTR(fontoutline, tmp, val, WITHTEXTATTR(fontoutlinealpha, alpha_tmp, alpha_val, Object::draw(sx, sy))); } + void buildchildren(uint *contents) { WITHTEXTATTR(fontoutline, tmp, val, WITHTEXTATTR(fontoutlinealpha, alpha_tmp, alpha_val, Object::buildchildren(contents))); } + }; + struct NoFallback : Object + { + static const char *typestr() { return "#NoFallback"; } + const char *gettype() const { return typestr(); } + + int val, tmp; + void setup(int val_) { val = val_; } + void layout() { WITHTEXTATTR(nofallback, tmp, val, Object::layout()); } + void draw(float sx, float sy) { WITHTEXTATTR(nofallback, tmp, val, Object::draw(sx, sy)); } + void buildchildren(uint *contents) { WITHTEXTATTR(nofallback, tmp, val, Object::buildchildren(contents)); } + }; + struct Language : Object + { + static const char *typestr() { return "#Language"; } + const char *gettype() const { return typestr(); } + + const char *val, *tmp; + Language() : val(nullptr), tmp(nullptr) {} + ~Language() { DELETEA(val); DELETEA(tmp); } + void setup(const char *val_) { SETSTR(val, val_); } + void layout() { SETSTR(tmp, curlanguage); SETSTR(curlanguage, val); Object::layout(); SETSTR(curlanguage, tmp); } + void draw(float sx, float sy) { SETSTR(tmp, curlanguage); SETSTR(curlanguage, val); Object::draw(sx, sy); SETSTR(curlanguage, tmp); } + void buildchildren(uint *contents) { SETSTR(tmp, curlanguage); SETSTR(curlanguage, val); Object::buildchildren(contents); SETSTR(curlanguage, tmp); } + }; + + #undef WITHTEXTATTR + static int uicursorindex = -1; ICOMMAND(uicursorindex, "", (), intret(uicursorindex)); + // makes the text input cursor stop blinking for a short while, call this when setting the cursor position from cubescript + ICOMMAND(resetcursorblink, "", (), { inputmillis = totalmillis; }); + string uikeycode, uitextinput; ICOMMAND(uikeycode, "", (), result(uikeycode)); ICOMMAND(uitextinput, "", (), result(uitextinput)); @@ -1962,23 +2080,67 @@ namespace UI ::textinput(*val ? true : false, TI_GUI); ::keyrepeat(*val ? true : false, KR_GUI); }); - + + // NOTE: `scale` is the text height in screenfuls at `uiscale 1` struct Text : Object { float scale, wrap, alpha; Color color; + text::Label label; + int fontid, lastchange; + int align, shadow, outlinealpha; + float outline; + int justify, nofallback; + const char *language; int cursor; bool has_cursor; + bool changed; + uint crc; // string hash used for change detection + + Text() : scale(0), wrap(0), color(0), lastchange(0), align(curwrapalign), shadow(curshadow), outlinealpha(curfontoutlinealpha), outline(curfontoutline), justify(curjustify), nofallback(curnofallback), language(nullptr), cursor(-1), has_cursor(false), crc(0) {} void setup(float scale_ = 1, const Color &color_ = Color(255, 255, 255), float wrap_ = -1, int cursor_ = -1, bool has_cursor_ = false, float alpha_ = 1) { Object::setup(); + changed = false; + const float newscale = scale_ * uiscale; + const int curfontid = getcurrentfontid(); + if(!uimillis || (totalmillis - lastchange >= uimillis) || !lastchange) + { + if( + newscale != scale || + color_ != color || + wrap_ != wrap || + fontid != curfontid || + curwrapalign != align || + curjustify != justify || + curshadow != shadow || + curfontoutline != outline || + curfontoutlinealpha != outlinealpha || + curnofallback != nofallback || + (!language || strcmp(curlanguage, language)) || + cursor_ != cursor || + cursor_ >= 0 // ensures the cursor blinks + ) + { + changed = true; + lastchange = totalmillis; + } - scale = scale_ * uiscale; - color = color_; + scale = newscale; + color = color_; + wrap = wrap_; + fontid = curfontid; + align = curwrapalign; + justify = curjustify; + shadow = curshadow; + outline = curfontoutline; + outlinealpha = curfontoutlinealpha; + nofallback = curnofallback; + SETSTR(language, curlanguage); + cursor = cursor_; + } alpha = alpha_; - wrap = wrap_; - cursor = cursor_; has_cursor = has_cursor_; } @@ -1986,7 +2148,7 @@ namespace UI { if(!has_cursor) return; const float k = drawscale(); - uicursorindex = text_visible(getstr(), cx/k, cy/k, wrap >= 0 ? wrap/k : -1); + uicursorindex = label.xy_to_index(cx/k, cy/k); } bool rawkey(int code, bool isdown) { @@ -2008,7 +2170,7 @@ namespace UI static const char *typestr() { return "#Text"; } const char *gettype() const { return typestr(); } - float drawscale() const { return scale / FONTH; } + float drawscale() const { return 1.f / hudh; } virtual const char *getstr() const { return ""; } @@ -2018,13 +2180,23 @@ namespace UI changedraw(CHANGE_SHADER | CHANGE_COLOR); - const float oldscale = textscale; - textscale = drawscale(); + const double textscale = drawscale(); + const double x = round(sx/textscale), y = round(sy/textscale); const int coloralpha = alpha < 1 ? static_cast(alpha * 255.0f) : color.a; - draw_text(getstr(), sx/textscale, sy/textscale, color.r, color.g, color.b, coloralpha, cursor, wrap >= 0 ? int(wrap/textscale) : -1); - textscale = oldscale; + pushhudscale(textscale); + if(shadow) + { + label.draw(x-0.001/textscale, y+0.001/textscale, (coloralpha < shadow ? coloralpha : shadow), true); + } + label.draw(x, y, coloralpha); + pophudmatrix(); } + void hide() { Object::hide() ; label.clear(); } + void clearlabels() { Object::clearlabels(); label.clear(); } + + ~Text() { delete[] language; } + void layout() { Object::layout(); @@ -2033,10 +2205,30 @@ namespace UI copystring(uikeycode, ""); copystring(uitextinput, ""); - float k = drawscale(), tw, th; - text_boundsf(getstr(), tw, th, wrap >= 0 ? int(wrap/k) : -1); - w = max(w, tw*k); - h = max(h, th*k); + setfontsize(scale * hudh); + + const float k = drawscale(); + + // text changes are detected here + const char *text = getstr(); + if(!uimillis || (totalmillis - lastchange >= uimillis) || !crc) + { + const uint crc_new = crc32(0, (const Bytef *)text, strlen(text)); + if(crc_new != crc) + { + changed = true; + lastchange = totalmillis; + crc = crc_new; + } + } + if(changed && label.valid()) label.clear(); + + if(!label.valid()) + { + label = text::prepare(text, int(wrap/k), bvec(color.r, color.g, color.b), cursor, outline * FONTH / 16.f, bvec4(0, 0, 0, outlinealpha), align, justify, language, nofallback, /*keep_layout=*/has_cursor, /*reserve_cursor=*/has_cursor); + } + w = max(w, label.width()*k); + h = max(h, label.height()*k); } }; @@ -2102,9 +2294,10 @@ namespace UI struct Font : Object { - ::font *font; + char *font; - Font() : font(NULL) {} + Font() : font(nullptr) {} + ~Font() { delete[] font; } static const char* typestr() { return "#Font"; } const char* gettype() const { return typestr(); } @@ -2112,11 +2305,7 @@ namespace UI void setup(const char *name) { Object::setup(); - - if(!font || strcmp(font->name, name)) - { - font = findfont(name); - } + SETSTR(font, name); } void layout() @@ -2203,7 +2392,7 @@ namespace UI changedraw(CHANGE_SHADER | CHANGE_COLOR); - float k = drawscale(); + const float k = drawscale() / uiscale; pushhudtranslate(sx, sy, k); renderfullconsole(w/k, h/k); pophudmatrix(); @@ -3222,6 +3411,24 @@ namespace UI ICOMMAND(uimodcircle, "ife", (int *c, float *size, uint *children), BUILD(Circle, o, o->setup(Color(*c), *size, Circle::MODULATE), children)); + ICOMMAND(uiwrapalign, "ie", (int *val, uint *children), + BUILD(WrapAlign, o, o->setup(*val), children)); + + ICOMMAND(uijustify, "ie", (int *val, uint *children), + BUILD(Justify, o, o->setup(*val), children)); + + ICOMMAND(uishadow, "ie", (int *val, uint *children), + BUILD(Shadow, o, o->setup(*val), children)); + + ICOMMAND(uifontoutline, "fie", (float *val, int *a, uint *children), + BUILD(FontOutline, o, o->setup(*val, *a), children)); + + ICOMMAND(uinofallback, "ie", (int *val, uint *children), + BUILD(NoFallback, o, o->setup(*val), children)); + + ICOMMAND(uilanguage, "se", (char *val, uint *children), + BUILD(Language, o, o->setup(val), children)); + static inline void buildtext(tagval &t, float scale, float scalemod, const Color &color, float wrap, int cursor, bool has_cursor, uint *children, float alpha = 1) { if(scale <= 0) scale = 1; @@ -3447,14 +3654,19 @@ namespace UI DELETEP(world); } + void clearlabels() + { + world->clearlabels(); + } + void calctextscale() { - uitextscale = 1.0f/uitextrows; + uitextscale = 1.0f/UITEXTROWS; int tw = hudw, th = hudh; if(forceaspect) tw = int(ceil(th*forceaspect)); - gettextres(tw, th); - uicontextscale = conscale/th * uiscale; + text::getres(tw, th); + uicontextscale = 1.f/th * uiscale; } void update() diff --git a/source/game/ctf.h b/source/game/ctf.h index 6173f00c8..978277f3c 100644 --- a/source/game/ctf.h +++ b/source/game/ctf.h @@ -450,7 +450,7 @@ struct ctfclientmode : clientmode if(hud->holdingflag && f.team == hud->team) { vec base = f.spawnloc; - particle_hud_mark(base, 2, 1, PART_GAME_ICONS, 1, teamtextcolor[hud->team], 4.0f); + particle_hud_text(base, "GOAL", PART_TEXT, 1, teamtextcolor[hud->team], 4.0f, "wide"); } else if(f.owner) { diff --git a/source/game/game.cpp b/source/game/game.cpp index 32131ddfe..ec7a94591 100644 --- a/source/game/game.cpp +++ b/source/game/game.cpp @@ -1008,7 +1008,7 @@ namespace game void initclient() { self = spawnstate(new gameent); - filtertext(self->name, "player", false, false, true, false, MAXNAMELEN); + filtertext(self->name, "player", T_WHITESPACE | T_NAME, MAXNAMELEN, MAXNAMEUNILEN); players.add(self); } @@ -1133,8 +1133,8 @@ namespace game bool duplicatename(gameent *d, const char *name = NULL, const char *alt = NULL) { if(!name) name = d->name; - if(alt && d != self && !strcmp(name, alt)) return true; - loopv(players) if(d!=players[i] && !strcmp(name, players[i]->name)) return true; + if(alt && d != self && duplicatestring(name, alt)) return true; + loopv(players) if(d!=players[i] && duplicatestring(name, players[i]->name)) return true; return false; } diff --git a/source/game/game.h b/source/game/game.h index 84a50d38b..13d578a5f 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -2,6 +2,7 @@ #define __GAME_H__ #include "cube.h" +#include "unicode.h" // animations @@ -456,7 +457,8 @@ struct gamestate #include "monster.h" -const int MAXNAMELEN = 15; +const int MAXNAMELEN = 60; // bytes +const int MAXNAMEUNILEN = 15; // unicode characters const int MAXCOUNTRYCODELEN = 8; const int MAXTEAMS = 2; @@ -704,6 +706,19 @@ namespace physics extern int liquidtransition(physent* d, int material, bool isinwater); } +// checks if two strings are visually identical or very similar +static inline bool duplicatestring(const char *a, const char *b) +{ + if(uni_strlen(a) != uni_strlen(b)) return false; + uint c, d; + size_t s = uni_getchar(a, c), z = uni_getchar(b, d); + for(const char *p = a, *q = b; c; p += s, q += z, s = uni_getchar(p, c), z = uni_getchar(q, d)) + { + if(homoglyph(c) != homoglyph(d)) return false; + } + return true; +} + namespace game { extern int gamemode, mutators; diff --git a/source/game/gameclient.cpp b/source/game/gameclient.cpp index f7dc9c545..81cddefdc 100644 --- a/source/game/gameclient.cpp +++ b/source/game/gameclient.cpp @@ -27,7 +27,7 @@ namespace game void switchname(const char* name) { - filtertext(self->name, name, false, false, true, false, MAXNAMELEN); + filtertext(self->name, name, T_WHITESPACE | T_NAME, MAXNAMELEN, MAXNAMEUNILEN); if (!self->name[0]) copystring(self->name, "player"); if (!name[0]) { @@ -1552,11 +1552,11 @@ namespace game break; } getstring(text, p); - filtertext(text, text, false, false, false, false, MAXCOUNTRYCODELEN); + filtertext(text, text, T_NONE, MAXCOUNTRYCODELEN); validcountrycode(d->country_code, text); getstring(text, p); - filtertext(d->country_name, text, false, false, true, false, MAXSTRLEN); + filtertext(d->country_name, text, T_WHITESPACE, MAXSTRLEN); break; } @@ -1612,10 +1612,10 @@ namespace game int cn = getint(p); gameent *d = getclient(cn); getstring(text, p); - filtertext(text, text, false, false, true, true); + filtertext(text, text, T_WHITESPACE | T_FORCESPACE); if(!d || isignored(d->clientnum)) break; if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR) - particle_textcopy(d->abovehead(), text, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); + particle_textcopy(d->abovehead(), text, PART_TEXT, 2000, 0x32FF64, 4.0f, -8, "default"); conoutf(CON_CHAT, "%s: \fs%s%s\fr", teamcolorname(d), chatcolor(d), text); if(chatsound == 1) playsound(S_CHAT); break; @@ -1626,13 +1626,13 @@ namespace game int tcn = getint(p); gameent *t = getclient(tcn); getstring(text, p); - filtertext(text, text, false, false, true, true); + filtertext(text, text, T_WHITESPACE | T_FORCESPACE); int sound = getint(p); if(!t || isignored(t->clientnum)) break; if(sound >= 0 && (t->state != CS_DEAD || t->state != CS_SPECTATOR)) playsound(sound, t); int team = validteam(t->team) ? t->team : 0; if(t->state!=CS_DEAD) - particle_textcopy(t->abovehead(), text, PART_TEXT, 2000, teamtextcolor[team], 4.0f, -8); + particle_textcopy(t->abovehead(), text, PART_TEXT, 2000, teamtextcolor[team], 4.0f, -8, "default"); conoutf(CON_TEAMCHAT, "%s \fs%s(team)\fr: \fs%s%s\fr", teamcolorname(t), teamtextcode[t->team], teamtextcode[t->team], text); if(chatsound == 1) playsound(S_CHAT); break; @@ -1643,7 +1643,7 @@ namespace game int scn = getint(p); gameent *s = getclient(scn); getstring(text, p); - filtertext(text, text, false, false, true, true); + filtertext(text, text, T_WHITESPACE | T_FORCESPACE); if(!s || isignored(s->clientnum)) break; conoutf(CON_CHAT, "%s \fs\f5(whisper)\fr: \fs\f5%s\fr", teamcolorname(s), text); if(chatsound) playsound(S_CHAT); @@ -1702,7 +1702,7 @@ namespace game break; } getstring(text, p); - filtertext(text, text, false, false, true, false, MAXNAMELEN); + filtertext(text, text, T_WHITESPACE | T_NAME, MAXNAMELEN, MAXNAMEUNILEN); if(!text[0]) copystring(text, "player"); // if no text is specified for the name change, change to default name if(d->name[0]) // already connected but the client changed their name { @@ -1727,11 +1727,11 @@ namespace game d->playercolor = getint(p); getstring(text, p); - filtertext(text, text, false, false, false, false, MAXCOUNTRYCODELEN); + filtertext(text, text, T_NONE, MAXCOUNTRYCODELEN); validcountrycode(d->country_code, text); getstring(text, p); - filtertext(text, text, false, false, true, false, MAXSTRLEN); + filtertext(text, text, T_WHITESPACE, MAXSTRLEN); copystring(d->country_name, text); break; @@ -1741,7 +1741,7 @@ namespace game getstring(text, p); if(d) { - filtertext(text, text, false, false, true, false, MAXNAMELEN); + filtertext(text, text, T_WHITESPACE | T_NAME, MAXNAMELEN, MAXNAMEUNILEN); if(!text[0]) copystring(text, "player"); if(strcmp(text, d->name)) { @@ -2102,7 +2102,7 @@ namespace game int type = getint(p); getstring(text, p); string name; - filtertext(name, text, false, false, false); + filtertext(name, text, T_NONE); ident *id = getident(name); switch(type) { @@ -2391,7 +2391,7 @@ namespace game int bn = getint(p), on = getint(p), at = getint(p), sk = clamp(getint(p), 1, 101), pm = getint(p), col = getint(p), team = getint(p); string name; getstring(text, p); - filtertext(name, text, false, false, false, false, MAXNAMELEN); + filtertext(name, text, T_NAME, MAXNAMELEN, MAXNAMEUNILEN); gameent *b = newclient(bn); if(!b) break; ai::init(b, at, on, sk, bn, pm, col, name, team); diff --git a/source/game/gameserver.cpp b/source/game/gameserver.cpp index b7750e15e..e203987bd 100644 --- a/source/game/gameserver.cpp +++ b/source/game/gameserver.cpp @@ -868,7 +868,7 @@ namespace server bool duplicatename(clientinfo *ci, const char *name) { if(!name) name = ci->name; - loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true; + loopv(clients) if(clients[i]!=ci && duplicatestring(name, clients[i]->name)) return true; return false; } @@ -3727,7 +3727,7 @@ namespace server ci->cleanauth(); if(!nextauthreq) nextauthreq = 1; ci->authreq = nextauthreq++; - filtertext(ci->authname, user, false, false, false, false, 100); + filtertext(ci->authname, user, T_NONE, 100); copystring(ci->authdesc, desc); if(ci->authdesc[0]) { @@ -3907,13 +3907,13 @@ namespace server case N_CONNECT: { getstring(text, p); - filtertext(text, text, false, false, true, false, MAXNAMELEN); + filtertext(text, text, T_WHITESPACE | T_NAME, MAXNAMELEN, MAXNAMEUNILEN); if(!text[0]) copystring(text, "player"); copystring(ci->name, text, MAXNAMELEN+1); ci->playermodel = getint(p); ci->playercolor = getint(p); getstring(text, p); - filtertext(ci->preferred_flag, text, false, false, false, false, MAXCOUNTRYCODELEN); + filtertext(ci->preferred_flag, text, T_NONE, MAXCOUNTRYCODELEN); string password, authdesc, authname; getstring(password, p, sizeof(password)); @@ -4246,7 +4246,7 @@ namespace server { getstring(text, p); if(cq->mute) break; - filtertext(text, text, false, false, true, true); + filtertext(text, text, T_WHITESPACE | T_FORCESPACE); loopv(clients) { clientinfo *c = clients[i]; @@ -4263,7 +4263,7 @@ namespace server getstring(text, p); int sound = getint(p); if(!ci || !cq || cq->mute || !m_teammode || !validteam(cq->team) || cq->state.state==CS_SPECTATOR || (m_round && cq->state.state==CS_DEAD)) break; - filtertext(text, text, false, false, true, true); + filtertext(text, text, T_WHITESPACE | T_FORCESPACE); loopv(clients) { clientinfo *t = clients[i]; @@ -4279,7 +4279,7 @@ namespace server int rcn = getint(p); getstring(text, p); if(!cq || cq->mute) break; - filtertext(text, text, false, false); + filtertext(text, text, T_WHITESPACE); clientinfo *recipient = NULL; loopv(clients) { @@ -4298,7 +4298,7 @@ namespace server { QUEUE_MSG; getstring(text, p); - filtertext(ci->name, text, false, false, true, false, MAXNAMELEN); + filtertext(ci->name, text, T_WHITESPACE | T_NAME, MAXNAMELEN, MAXNAMEUNILEN); if (!ci->name[0]) { copystring(ci->name, "player"); @@ -4337,7 +4337,7 @@ namespace server case N_MAPVOTE: { getstring(text, p); - filtertext(text, text, false, false, true, false); + filtertext(text, text, T_WHITESPACE); fixmapname(text); int reqmode = getint(p), reqmuts = getint(p); vote(text, reqmode, reqmuts, sender); @@ -4454,7 +4454,7 @@ namespace server { int victim = getint(p); getstring(text, p); - filtertext(text, text); + filtertext(text, text, T_NEWLINES | T_WHITESPACE); trykick(ci, victim, text); break; } @@ -4463,7 +4463,7 @@ namespace server { int victim = getint(p), val = getint(p); getstring(text, p); - filtertext(text, text); + filtertext(text, text, T_NEWLINES | T_WHITESPACE); trymute(ci, victim, val, text); break; } @@ -4632,7 +4632,7 @@ namespace server getstring(name, p, sizeof(name)); int victim = getint(p); getstring(text, p); - filtertext(text, text); + filtertext(text, text, T_NEWLINES | T_WHITESPACE); int authpriv = PRIV_MODERATOR; if(desc[0]) { @@ -4750,7 +4750,7 @@ namespace server case N_COUNTRY: { getstring(text, p); - filtertext(ci->preferred_flag, text, false, false, false, false, MAXCOUNTRYCODELEN); + filtertext(ci->preferred_flag, text, T_NONE, MAXCOUNTRYCODELEN); geoip_set_custom_flag(ci->preferred_flag, ci->country_code, ci->country_name, ci->customflag_code, ci->customflag_name); packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(q, N_COUNTRY); diff --git a/source/game/geoip.h b/source/game/geoip.h index 2c6110659..120393597 100644 --- a/source/game/geoip.h +++ b/source/game/geoip.h @@ -56,7 +56,6 @@ void geoip_lookup_ip(enet_uint32 ip, char *dst_country_code, char *dst_country_n #ifdef HAVE_MAXMINDDB static string text; - static uchar buf[MAXSTRLEN]; if(!mmdb) return; int error; @@ -75,18 +74,15 @@ void geoip_lookup_ip(enet_uint32 ip, char *dst_country_code, char *dst_country_n if(MMDB_SUCCESS == error && data.has_data && MMDB_DATA_TYPE_UTF8_STRING == data.type && data.data_size >= 2) { copystring(text, data.utf8_string, data.data_size+1); - filtertext(dst_country_code, text, false, false, false, false, MAXCOUNTRYCODELEN); + filtertext(dst_country_code, text, T_NONE, MAXCOUNTRYCODELEN); } // get country name error = MMDB_get_value(&result.entry, &data, "country", "names", "en", NULL); if(MMDB_SUCCESS == error && data.has_data && MMDB_DATA_TYPE_UTF8_STRING == data.type) { - size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)data.utf8_string, data.data_size); - if(len > 0) { - buf[len] = 0; - copystring(dst_country_name, (const char*)buf, len+1); - } + copystring(text, data.utf8_string, data.data_size <= MAXSTRLEN ? data.data_size : MAXSTRLEN); + filtertext(dst_country_name, text, T_WHITESPACE | T_FORCESPACE, MAXSTRLEN); } } #endif diff --git a/source/game/render.cpp b/source/game/render.cpp index c6b03f12d..f7efe50b3 100644 --- a/source/game/render.cpp +++ b/source/game/render.cpp @@ -208,7 +208,7 @@ namespace game } if(strcmp(self->preferred_flag, customflag) != 0) { - filtertext(self->preferred_flag, customflag, false, false, false, false, MAXCOUNTRYCODELEN); + filtertext(self->preferred_flag, customflag, T_NONE, MAXCOUNTRYCODELEN); addmsg(N_COUNTRY, "rs", self->preferred_flag); } } @@ -575,7 +575,7 @@ namespace game else if (d->state == CS_ALIVE && !hidenames()) { int team = m_teammode && validteam(d->team) ? d->team : 0; - particle_text(position, d->info, PART_TEXT, 1, teamtextcolor[team], 2.0f); + particle_text(position, d->info, PART_TEXT, 1, teamtextcolor[team], 2.0f, 0, "wide"); } booteffect(d); } diff --git a/source/game/scoreboard.cpp b/source/game/scoreboard.cpp index a861d0b87..e208f25b6 100644 --- a/source/game/scoreboard.cpp +++ b/source/game/scoreboard.cpp @@ -186,7 +186,7 @@ namespace game { if(servdesc[0]) { - filtertext(servdesc, servdesc, true, false); + filtertext(servdesc, servdesc, T_COLORS | T_WHITESPACE); result(servdesc); } else diff --git a/source/shared/iengine.h b/source/shared/iengine.h index baf4d31d0..a1adf2447 100644 --- a/source/shared/iengine.h +++ b/source/shared/iengine.h @@ -245,30 +245,6 @@ extern void fatal(const char *s, ...) PRINTFARGS(1, 2); extern void drawquad(float x, float y, float w, float h, float tx1 = 0, float ty1 = 0, float tx2 = 1, float ty2 = 1, bool flipx = false, bool flipy = false); extern int currentversion; -// rendertext -extern bool setfont(const char *name); -extern void pushfont(); -extern bool popfont(); -extern void gettextres(int &w, int &h); -extern void draw_text(const char *str, float left, float top, int r = 255, int g = 255, int b = 255, int a = 255, int cursor = -1, int maxwidth = -1); -extern void draw_textf(const char *fstr, double left, double top, ...) PRINTFARGS(1, 4); -extern float text_widthf(const char *str); -extern void text_boundsf(const char *str, float &width, float &height, int maxwidth = -1); -extern int text_visible(const char *str, float hitx, float hity, int maxwidth); - -static inline int text_width(const char *str) -{ - return int(ceil(text_widthf(str))); -} - -static inline void text_bounds(const char *str, int &width, int &height, int maxwidth = -1) -{ - float widthf, heightf; - text_boundsf(str, widthf, heightf, maxwidth); - width = int(ceil(widthf)); - height = int(ceil(heightf)); -} - // texture struct VSlot; @@ -334,10 +310,11 @@ extern void regular_particle_splash(int type, int num, int fade, const vec &p, i extern void regular_particle_flame(int type, const vec &p, float radius, float height, int color, int density = 3, float scale = 2.0f, float speed = 200.0f, float fade = 600.0f, int gravity = -15); extern void particle_splash(int type, int num, int fade, const vec &p, int color = 0xFFFFFF, float size = 1.0f, int radius = 150, int gravity = 2, float maxsize = 0); extern void particle_trail(int type, int fade, const vec &from, const vec &to, int color = 0xFFFFFF, float size = 1.0f, int gravity = 20, float maxsize = 0); -extern void particle_text(const vec &s, const char *t, int type, int fade = 2000, int color = 0xFFFFFF, float size = 2.0f, int gravity = 0); -extern void particle_textcopy(const vec &s, const char *t, int type, int fade = 2000, int color = 0xFFFFFF, float size = 2.0f, int gravity = 0); +extern void particle_text(const vec &s, const char *t, int type, int fade = 2000, int color = 0xFFFFFF, float size = 2.0f, int gravity = 0, const char *font = "wide", const char *language = NULL); +extern void particle_textcopy(const vec &s, const char *t, int type, int fade = 2000, int color = 0xFFFFFF, float size = 2.0f, int gravity = 0, const char *font = "wide", const char *language = NULL); extern void particle_icon(const vec &s, int ix, int iy, int type, int fade = 2000, int color = 0xFFFFFF, float size = 2.0f, int gravity = 0); extern void particle_hud_mark(const vec &s, int ix, int iy, int type, int fade, int color, float size); +extern void particle_hud_text(const vec &s, const char *t, int type, int fade, int color, float size, const char *font, const char *language = NULL); extern void particle_meter(const vec &s, float val, int type, int fade = 1, int color = 0xFFFFFF, int color2 = 0xFFFFF, float size = 2.0f); extern void particle_flare(const vec &p, const vec &dest, int fade, int type, int color = 0xFFFFFF, float size = 0.28f, physent *owner = NULL, float maxsize = 0); extern void particle_fireball(const vec &dest, float max, int type, int fade = -1, int color = 0xFFFFFF, float size = 4.0f); diff --git a/source/shared/stream.cpp b/source/shared/stream.cpp index 1253b953f..423fd5c76 100644 --- a/source/shared/stream.cpp +++ b/source/shared/stream.cpp @@ -1,4 +1,5 @@ #include "cube.h" +#include "unicode.h" ///////////////////////////// console //////////////////////// @@ -28,231 +29,12 @@ void conoutf(int type, int tag, const char *fmt, ...) ///////////////////////// character conversion /////////////// -#define CUBECTYPE(s, p, d, a, A, u, U) \ - 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \ - d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \ - p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \ - A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \ - p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \ - a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \ - U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u - -extern const uchar cubectype[256] = -{ - CUBECTYPE(CT_SPACE, - CT_PRINT, - CT_PRINT|CT_DIGIT, - CT_PRINT|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_ALPHA|CT_UPPER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_UPPER) -}; -extern const int cube2unichars[256] = -{ - 0, 192, 193, 194, 195, 196, 197, 198, 199, 9, 10, 11, 12, 13, 200, 201, - 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 220, - 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, - 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 0x104, - 0x105, 0x106, 0x107, 0x10C, 0x10D, 0x10E, 0x10F, 0x118, 0x119, 0x11A, 0x11B, 0x11E, 0x11F, 0x130, 0x131, 0x141, - 0x142, 0x143, 0x144, 0x147, 0x148, 0x150, 0x151, 0x152, 0x153, 0x158, 0x159, 0x15A, 0x15B, 0x15E, 0x15F, 0x160, - 0x161, 0x164, 0x165, 0x16E, 0x16F, 0x170, 0x171, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x404, 0x411, - 0x413, 0x414, 0x416, 0x417, 0x418, 0x419, 0x41B, 0x41F, 0x423, 0x424, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, - 0x42C, 0x42D, 0x42E, 0x42F, 0x431, 0x432, 0x433, 0x434, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, - 0x43F, 0x442, 0x444, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x454, 0x490, 0x491 -}; -extern const int uni2cubeoffsets[8] = -{ - 0, 256, 658, 658, 512, 658, 658, 658 -}; -extern const uchar uni2cubechars[878] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, 20, 21, 0, 22, 23, 24, 25, 26, 27, 0, 28, 29, 30, 31, 127, 128, 0, 129, - 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 0, 146, 147, 148, 149, 150, 151, 0, 152, 153, 154, 155, 156, 157, 0, 158, - 0, 0, 0, 0, 159, 160, 161, 162, 0, 0, 0, 0, 163, 164, 165, 166, 0, 0, 0, 0, 0, 0, 0, 0, 167, 168, 169, 170, 0, 0, 171, 172, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 174, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 175, 176, 177, 178, 0, 0, 179, 180, 0, 0, 0, 0, 0, 0, 0, 181, 182, 183, 184, 0, 0, 0, 0, 185, 186, 187, 188, 0, 0, 189, 190, - 191, 192, 0, 0, 193, 194, 0, 0, 0, 0, 0, 0, 0, 0, 195, 196, 197, 198, 0, 0, 0, 0, 0, 0, 199, 200, 201, 202, 203, 204, 205, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 17, 0, 0, 206, 83, 73, 21, 74, 0, 0, 0, 0, 0, 0, 0, 65, 207, 66, 208, 209, 69, 210, 211, 212, 213, 75, 214, 77, 72, 79, 215, - 80, 67, 84, 216, 217, 88, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 97, 228, 229, 230, 231, 101, 232, 233, 234, 235, 236, 237, 238, 239, 111, 240, - 112, 99, 241, 121, 242, 120, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 0, 141, 0, 0, 253, 115, 105, 145, 106, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; -extern const uchar cubelowerchars[256] = -{ - 0, 130, 131, 132, 133, 134, 135, 136, 137, 9, 10, 11, 12, 13, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 156, - 157, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 160, - 160, 162, 162, 164, 164, 166, 166, 168, 168, 170, 170, 172, 172, 105, 174, 176, - 176, 178, 178, 180, 180, 182, 182, 184, 184, 186, 186, 188, 188, 190, 190, 192, - 192, 194, 194, 196, 196, 198, 198, 158, 201, 201, 203, 203, 205, 205, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 -}; -extern const uchar cubeupperchars[256] = -{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123, 124, 125, 126, 127, - 128, 129, 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127, 128, 199, 159, - 159, 161, 161, 163, 163, 165, 165, 167, 167, 169, 169, 171, 171, 173, 73, 175, - 175, 177, 177, 179, 179, 181, 181, 183, 183, 185, 185, 187, 187, 189, 189, 191, - 191, 193, 193, 195, 195, 197, 197, 199, 200, 200, 202, 202, 204, 204, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 -}; - -size_t decodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry) -{ - uchar *dst = dstbuf, *dstend = &dstbuf[dstlen]; - const uchar *src = srcbuf, *srcend = &srcbuf[srclen]; - if(dstbuf == srcbuf) - { - int len = min(dstlen, srclen); - for(const uchar *end4 = &srcbuf[len&~3]; src < end4; src += 4) if(*(const int *)src & 0x80808080) goto decode; - for(const uchar *end = &srcbuf[len]; src < end; src++) if(*src & 0x80) goto decode; - if(carry) *carry += len; - return len; - } - -decode: - dst += src - srcbuf; - while(src < srcend && dst < dstend) - { - int c = *src++; - if(c < 0x80) *dst++ = c; - else if(c >= 0xC0) - { - int uni; - if(c >= 0xE0) - { - if(c >= 0xF0) - { - if(c >= 0xF8) - { - if(c >= 0xFC) - { - if(c >= 0xFE) continue; - uni = c&1; if(srcend - src < 5) break; - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&3; if(srcend - src < 4) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&7; if(srcend - src < 3) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&0xF; if(srcend - src < 2) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&0x1F; if(srcend - src < 1) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - c = uni2cube(uni); - if(!c) continue; - *dst++ = c; - } - } - if(carry) *carry += src - srcbuf; - return dst - dstbuf; -} - -size_t encodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry) -{ - uchar *dst = dstbuf, *dstend = &dstbuf[dstlen]; - const uchar *src = srcbuf, *srcend = &srcbuf[srclen]; - if(src < srcend && dst < dstend) do - { - int uni = cube2uni(*src); - if(uni <= 0x7F) - { - if(dst >= dstend) goto done; - const uchar *end = min(srcend, &src[dstend-dst]); - do - { - if(uni == '\f') - { - if(++src >= srcend) goto done; - goto uni1; - } - *dst++ = uni; - if(++src >= end) goto done; - uni = cube2uni(*src); - } - while(uni <= 0x7F); - } - if(uni <= 0x7FF) { if(dst + 2 > dstend) goto done; *dst++ = 0xC0 | (uni>>6); goto uni2; } - else if(uni <= 0xFFFF) { if(dst + 3 > dstend) goto done; *dst++ = 0xE0 | (uni>>12); goto uni3; } - else if(uni <= 0x1FFFFF) { if(dst + 4 > dstend) goto done; *dst++ = 0xF0 | (uni>>18); goto uni4; } - else if(uni <= 0x3FFFFFF) { if(dst + 5 > dstend) goto done; *dst++ = 0xF8 | (uni>>24); goto uni5; } - else if(uni <= 0x7FFFFFFF) { if(dst + 6 > dstend) goto done; *dst++ = 0xFC | (uni>>30); goto uni6; } - else goto uni1; - uni6: *dst++ = 0x80 | ((uni>>24)&0x3F); - uni5: *dst++ = 0x80 | ((uni>>18)&0x3F); - uni4: *dst++ = 0x80 | ((uni>>12)&0x3F); - uni3: *dst++ = 0x80 | ((uni>>6)&0x3F); - uni2: *dst++ = 0x80 | (uni&0x3F); - uni1:; - } - while(++src < srcend); - -done: - if(carry) *carry += src - srcbuf; - return dst - dstbuf; -} - int cubecasecmp(const char *s1, const char *s2, int n) { if(!s1 || !s2) return !s2 - !s1; while(n-- > 0) { - int c1 = cubelower(*s1++), c2 = cubelower(*s2++); + const char c1 = uni_charlower(*s1++), c2 = uni_charlower(*s2++); if(c1 != c2) return c1 - c2; if(!c1) break; } @@ -263,7 +45,7 @@ char *cubecasefind(const char *haystack, const char *needle) { if(haystack && needle) for(const char *h = haystack, *n = needle;;) { - int hc = cubelower(*h++), nc = cubelower(*n++); + const char hc = uni_charlower(*h++), nc = uni_charlower(*n++); if(!nc) return (char*)h - (n - needle); if(hc != nc) { @@ -1057,178 +839,6 @@ struct gzstream : stream } }; -struct utf8stream : stream -{ - enum - { - BUFSIZE = 4096 - }; - stream *file; - offset pos; - size_t bufread, bufcarry, buflen; - bool reading, writing, autoclose; - uchar buf[BUFSIZE]; - - utf8stream() : file(NULL), pos(0), bufread(0), bufcarry(0), buflen(0), reading(false), writing(false), autoclose(false) - { - } - - ~utf8stream() - { - close(); - } - - bool readbuf(size_t size = BUFSIZE) - { - if(bufread >= bufcarry) { if(bufcarry > 0 && bufcarry < buflen) memmove(buf, &buf[bufcarry], buflen - bufcarry); buflen -= bufcarry; bufread = bufcarry = 0; } - size_t n = file->read(&buf[buflen], min(size, BUFSIZE - buflen)); - if(n <= 0) return false; - buflen += n; - size_t carry = bufcarry; - bufcarry += decodeutf8(&buf[bufcarry], BUFSIZE-bufcarry, &buf[bufcarry], buflen-bufcarry, &carry); - if(carry > bufcarry && carry < buflen) { memmove(&buf[bufcarry], &buf[carry], buflen - carry); buflen -= carry - bufcarry; } - return true; - } - - bool checkheader() - { - size_t n = file->read(buf, 3); - if(n == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) return true; - buflen = n; - return false; - } - - bool open(stream *f, const char *mode, bool needclose) - { - if(file) return false; - for(; *mode; mode++) - { - if(*mode=='r') { reading = true; break; } - else if(*mode=='w') { writing = true; break; } - } - if(!reading && !writing) return false; - - file = f; - - if(reading) checkheader(); - - autoclose = needclose; - return true; - } - - void finishreading() - { - if(!reading) return; - } - - void stopreading() - { - if(!reading) return; - reading = false; - } - - void stopwriting() - { - if(!writing) return; - writing = false; - } - - void close() - { - stopreading(); - stopwriting(); - if(autoclose) DELETEP(file); - } - - bool end() { return !reading && !writing; } - offset tell() { return reading || writing ? pos : offset(-1); } - - bool seek(offset off, int whence) - { - if(writing || !reading) return false; - - if(whence == SEEK_END) - { - uchar skip[512]; - while(read(skip, sizeof(skip)) == sizeof(skip)); - return !off; - } - else if(whence == SEEK_CUR) off += pos; - - if(off >= pos) off -= pos; - else if(off < 0 || !file->seek(0, SEEK_SET)) return false; - else - { - bufread = bufcarry = buflen = 0; - pos = 0; - checkheader(); - } - - uchar skip[512]; - while(off > 0) - { - size_t skipped = (size_t)min(off, (offset)sizeof(skip)); - if(read(skip, skipped) != skipped) { stopreading(); return false; } - off -= skipped; - } - - return true; - } - - size_t read(void *dst, size_t len) - { - if(!reading || !dst || !len) return 0; - size_t next = 0; - while(next < len) - { - if(bufread >= bufcarry) { if(readbuf(BUFSIZE)) continue; stopreading(); break; } - size_t n = min(len - next, bufcarry - bufread); - memcpy(&((uchar *)dst)[next], &buf[bufread], n); - next += n; - bufread += n; - } - pos += next; - return next; - } - - bool getline(char *dst, size_t len) - { - if(!reading || !dst || !len) return false; - --len; - size_t next = 0; - while(next < len) - { - if(bufread >= bufcarry) { if(readbuf(BUFSIZE)) continue; stopreading(); if(!next) return false; break; } - size_t n = min(len - next, bufcarry - bufread); - uchar *endline = (uchar *)memchr(&buf[bufread], '\n', n); - if(endline) { n = endline+1 - &buf[bufread]; len = next + n; } - memcpy(&((uchar *)dst)[next], &buf[bufread], n); - next += n; - bufread += n; - } - dst[next] = '\0'; - pos += next; - return true; - } - - size_t write(const void *src, size_t len) - { - if(!writing || !src || !len) return 0; - uchar dst[512]; - size_t next = 0; - while(next < len) - { - size_t carry = 0, n = encodeutf8(dst, sizeof(dst), &((uchar *)src)[next], len - next, &carry); - if(n > 0 && file->write(dst, n) != n) { stopwriting(); break; } - next += carry; - } - pos += next; - return next; - } - - bool flush() { return file->flush(); } -}; - stream *openrawfile(const char *filename, const char *mode) { const char *found = findfile(filename, mode); @@ -1264,16 +874,7 @@ stream *opengzfile(const char *filename, const char *mode, stream *file, int lev return gz; } -stream *openutf8file(const char *filename, const char *mode, stream *file) -{ - stream *source = file ? file : openfile(filename, mode); - if(!source) return NULL; - utf8stream *utf8 = new utf8stream; - if(!utf8->open(source, mode, !file)) { if(!file) delete source; delete utf8; return NULL; } - return utf8; -} - -char *loadfile(const char *fn, size_t *size, bool utf8) +char *loadfile(const char *fn, size_t *size) { stream *f = openfile(fn, "rb"); if(!f) return NULL; @@ -1283,16 +884,9 @@ char *loadfile(const char *fn, size_t *size, bool utf8) char *buf = new (false) char[len+1]; if(!buf) { delete f; return NULL; } size_t offset = 0; - if(utf8 && len >= 3) - { - if(f->read(buf, 3) != 3) { delete f; delete[] buf; return NULL; } - if(((uchar *)buf)[0] == 0xEF && ((uchar *)buf)[1] == 0xBB && ((uchar *)buf)[2] == 0xBF) len -= 3; - else offset += 3; - } size_t rlen = f->read(&buf[offset], len-offset); delete f; if(rlen != len-offset) { delete[] buf; return NULL; } - if(utf8) len = decodeutf8((uchar *)buf, len, (uchar *)buf, len); buf[len] = '\0'; if(size!=NULL) *size = len; return buf; diff --git a/source/shared/tessfont.c b/source/shared/tessfont.c deleted file mode 100644 index 9934a9399..000000000 --- a/source/shared/tessfont.c +++ /dev/null @@ -1,764 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include FT_FREETYPE_H -#include FT_STROKER_H -#include FT_GLYPH_H - -typedef unsigned char uchar; -typedef unsigned short ushort; -typedef unsigned int uint; - -static inline int imin(int a, int b) { return a < b ? a : b; } -static inline int imax(int a, int b) { return a > b ? a : b; } -static inline int iclamp(int n, int l, int h) { return imax(l, imin(n, h)); } -static inline float fclamp(float n, float l, float h) { return fmax(l, fmin(n, h)); } - -void fatal(const char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - vfprintf(stderr, fmt, v); - va_end(v); - fputc('\n', stderr); - - exit(EXIT_FAILURE); -} - -uint bigswap(uint n) -{ - const int islittleendian = 1; - return *(const uchar *)&islittleendian ? (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000) : n; -} - -size_t writebig(FILE *f, uint n) -{ - n = bigswap(n); - return fwrite(&n, 1, sizeof(n), f); -} - -void writepngchunk(FILE *f, const char *type, uchar *data, uint len) -{ - uint crc; - writebig(f, len); - fwrite(type, 1, 4, f); - fwrite(data, 1, len, f); - - crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)type, 4); - if(data) crc = crc32(crc, data, len); - writebig(f, crc); -} - -struct pngihdr -{ - uint width, height; - uchar bitdepth, colortype, compress, filter, interlace; -}; - -void savepng(const char *filename, uchar *data, int w, int h, int bpp, int flip) -{ - const uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; - struct pngihdr ihdr; - FILE *f; - long idat; - uint len, crc; - z_stream z; - uchar buf[1<<12]; - int i, j; - - memset(&ihdr, 0, sizeof(ihdr)); - ihdr.width = bigswap(w); - ihdr.height = bigswap(h); - ihdr.bitdepth = 8; - switch(bpp) - { - case 1: ihdr.colortype = 0; break; - case 2: ihdr.colortype = 4; break; - case 3: ihdr.colortype = 2; break; - case 4: ihdr.colortype = 6; break; - default: fatal("tessfont: invalid PNG bpp"); return; - } - f = fopen(filename, "wb"); - if(!f) { fatal("tessfont: could not write to %s", filename); return; } - - fwrite(signature, 1, sizeof(signature), f); - - writepngchunk(f, "IHDR", (uchar *)&ihdr, 13); - - idat = ftell(f); - len = 0; - fwrite("\0\0\0\0IDAT", 1, 8, f); - crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)"IDAT", 4); - - z.zalloc = NULL; - z.zfree = NULL; - z.opaque = NULL; - - if(deflateInit(&z, Z_BEST_COMPRESSION) != Z_OK) - goto error; - - z.next_out = (Bytef *)buf; - z.avail_out = sizeof(buf); - - for(i = 0; i < h; i++) - { - uchar filter = 0; - for(j = 0; j < 2; j++) - { - z.next_in = j ? (Bytef *)data + (flip ? h-i-1 : i)*w*bpp : (Bytef *)&filter; - z.avail_in = j ? w*bpp : 1; - while(z.avail_in > 0) - { - if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror; - #define FLUSHZ do { \ - int flush = sizeof(buf) - z.avail_out; \ - crc = crc32(crc, buf, flush); \ - len += flush; \ - fwrite(buf, 1, flush, f); \ - z.next_out = (Bytef *)buf; \ - z.avail_out = sizeof(buf); \ - } while(0) - FLUSHZ; - } - } - } - - for(;;) - { - int err = deflate(&z, Z_FINISH); - if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror; - FLUSHZ; - if(err == Z_STREAM_END) break; - } - - deflateEnd(&z); - - fseek(f, idat, SEEK_SET); - writebig(f, len); - fseek(f, 0, SEEK_END); - writebig(f, crc); - - writepngchunk(f, "IEND", NULL, 0); - - fclose(f); - return; - -cleanuperror: - deflateEnd(&z); - -error: - fclose(f); - - fatal("tessfont: failed saving PNG to %s", filename); -} - -enum -{ - CT_PRINT = 1<<0, - CT_SPACE = 1<<1, - CT_DIGIT = 1<<2, - CT_ALPHA = 1<<3, - CT_LOWER = 1<<4, - CT_UPPER = 1<<5, - CT_UNICODE = 1<<6 -}; -#define CUBECTYPE(s, p, d, a, A, u, U) \ - 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \ - d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \ - p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \ - A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \ - p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \ - a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \ - U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u -const uchar cubectype[256] = -{ - CUBECTYPE(CT_SPACE, - CT_PRINT, - CT_PRINT|CT_DIGIT, - CT_PRINT|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_ALPHA|CT_UPPER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_UPPER) -}; -int iscubeprint(uchar c) { return cubectype[c]&CT_PRINT; } -int iscubespace(uchar c) { return cubectype[c]&CT_SPACE; } -int iscubealpha(uchar c) { return cubectype[c]&CT_ALPHA; } -int iscubealnum(uchar c) { return cubectype[c]&(CT_ALPHA|CT_DIGIT); } -int iscubelower(uchar c) { return cubectype[c]&CT_LOWER; } -int iscubeupper(uchar c) { return cubectype[c]&CT_UPPER; } -const int cube2unichars[256] = -{ - 0, 192, 193, 194, 195, 196, 197, 198, 199, 9, 10, 11, 12, 13, 200, 201, - 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 220, - 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, - 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 0x104, - 0x105, 0x106, 0x107, 0x10C, 0x10D, 0x10E, 0x10F, 0x118, 0x119, 0x11A, 0x11B, 0x11E, 0x11F, 0x130, 0x131, 0x141, - 0x142, 0x143, 0x144, 0x147, 0x148, 0x150, 0x151, 0x152, 0x153, 0x158, 0x159, 0x15A, 0x15B, 0x15E, 0x15F, 0x160, - 0x161, 0x164, 0x165, 0x16E, 0x16F, 0x170, 0x171, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x404, 0x411, - 0x413, 0x414, 0x416, 0x417, 0x418, 0x419, 0x41B, 0x41F, 0x423, 0x424, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, - 0x42C, 0x42D, 0x42E, 0x42F, 0x431, 0x432, 0x433, 0x434, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, - 0x43F, 0x442, 0x444, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x454, 0x490, 0x491 -}; -int cube2uni(uchar c) -{ - return cube2unichars[c]; -} - -const char *encodeutf8(int uni) -{ - static char buf[7]; - char *dst = buf; - if(uni <= 0x7F) { *dst++ = uni; goto uni1; } - else if(uni <= 0x7FF) { *dst++ = 0xC0 | (uni>>6); goto uni2; } - else if(uni <= 0xFFFF) { *dst++ = 0xE0 | (uni>>12); goto uni3; } - else if(uni <= 0x1FFFFF) { *dst++ = 0xF0 | (uni>>18); goto uni4; } - else if(uni <= 0x3FFFFFF) { *dst++ = 0xF8 | (uni>>24); goto uni5; } - else if(uni <= 0x7FFFFFFF) { *dst++ = 0xFC | (uni>>30); goto uni6; } - else goto uni1; -uni6: *dst++ = 0x80 | ((uni>>24)&0x3F); -uni5: *dst++ = 0x80 | ((uni>>18)&0x3F); -uni4: *dst++ = 0x80 | ((uni>>12)&0x3F); -uni3: *dst++ = 0x80 | ((uni>>6)&0x3F); -uni2: *dst++ = 0x80 | (uni&0x3F); -uni1: *dst++ = '\0'; - return buf; -} - -struct fontchar { int code, uni, tex, x, y, sdfradius, sdfpitch, sdfx, sdfy, sdfw, sdfh; float w, h, left, top, advance; FT_BitmapGlyph glyph; uchar *sdf; }; - -const char *texdir = ""; - -const char *texfilename(const char *name, int texnum) -{ - static char file[256]; - snprintf(file, sizeof(file), "%s%d.png", name, texnum); - return file; -} - -const char *texname(const char *name, int texnum) -{ - static char file[512]; - snprintf(file, sizeof(file), "%s%s", texdir, texfilename(name, texnum)); - return file; -} - -static int sdist; -static inline void searchdist(int x, int y, int cx, int cy) -{ - int dx = cx - x, dy = cy - y, dist = dx*dx + dy*dy; - if(dist < sdist) sdist = dist; -} - -static int sx1, ex1; -static inline int search1(int x, int y, int w, int radius, int cy, uchar *src) -{ - int cx = imin(ex1, x)-1; - if(cx >= sx1) - { - uchar *bsrc = &src[cx>>3]; - int bx = cx&~7, bits = *bsrc; - if(bits==0xFF) cx = bx-1; - else - { - bits >>= 7-(cx&7); - if(bx <= sx1) - { - for(; cx >= sx1; cx--, bits >>= 1) if(!(bits&1)) { sx1 = cx+1; searchdist(x, y, cx, cy); goto foundbelow1; } - goto foundbelow1; - } - else for(; cx >= bx; cx--, bits >>= 1) if(!(bits&1)) { sx1 = cx+1; searchdist(x, y, cx, cy); goto foundbelow1; } - } - while(cx >= sx1) - { - bits = *--bsrc; - if(bits==0xFF) cx -= 8; - else if((bx = cx - 7) <= sx1) - { - for(; cx >= sx1; cx--, bits >>= 1) if(!(bits&1)) { sx1 = cx+1; searchdist(x, y, cx, cy); goto foundbelow1; } - goto foundbelow1; - } - else for(; cx >= bx; cx--, bits >>= 1) if(!(bits&1)) { sx1 = cx+1; searchdist(x, y, cx, cy); goto foundbelow1; } - - } - } -foundbelow1: - cx = imax(sx1, x); - if(cx < ex1) - { - uchar *bsrc = &src[cx>>3]; - int bx = (cx&~7) + 8, bits = *bsrc; - if(bits==0xFF) cx = bx; - else - { - bits <<= cx&7; - if(bx >= ex1) - { - for(; cx < ex1; cx++, bits <<= 1) if(!(bits&0x80)) { ex1 = cx-1; searchdist(x, y, cx, cy); goto foundabove1; } - goto foundabove1; - } - else for(; cx < bx; cx++, bits <<= 1) if(!(bits&0x80)) { ex1 = cx-1; searchdist(x, y, cx, cy); goto foundabove1; } - } - while(cx < ex1) - { - bits = *++bsrc; - if(bits==0xFF) cx += 8; - else if((bx = cx + 8) >= ex1) - { - for(; cx < ex1; cx++, bits <<= 1) if(!(bits&0x80)) { ex1 = cx-1; searchdist(x, y, cx, cy); goto foundabove1; } - goto foundabove1; - } - else for(; cx < bx; cx++, bits <<= 1) if(!(bits&0x80)) { ex1 = cx-1; searchdist(x, y, cx, cy); goto foundabove1; } - } - } -foundabove1: - if(x - radius < 0) searchdist(x, y, -1, cy); - if(x + radius > w) searchdist(x, y, w, cy); - return sx1 < ex1; -} - -static int sx0, ex0; -static inline int search0(int x, int y, int w, int radius, int cy, uchar *src) -{ - int cx = imin(ex0, x)-1; - if(cx >= sx0) - { - uchar *bsrc = &src[cx>>3]; - int bx = cx&~7, bits = *bsrc; - if(!bits) cx = bx-1; - else - { - bits >>= 7-(cx&7); - if(bx <= sx0) - { - for(; cx >= sx0; cx--, bits >>= 1) if(bits&1) { sx0 = cx+1; searchdist(x, y, cx, cy); goto foundbelow0; } - goto foundbelow0; - } - else for(; cx >= bx; cx--, bits >>= 1) if(bits&1) { sx0 = cx+1; searchdist(x, y, cx, cy); goto foundbelow0; } - } - while(cx >= sx0) - { - bits = *--bsrc; - if(!bits) cx -= 8; - else if((bx = cx - 7) <= sx0) - { - for(; cx >= sx0; cx--, bits >>= 1) if(bits&1) { sx0 = cx+1; searchdist(x, y, cx, cy); goto foundbelow0; } - goto foundbelow0; - } - else for(; cx >= bx; cx--, bits >>= 1) if(bits&1) { sx0 = cx+1; searchdist(x, y, cx, cy); goto foundbelow0; } - } - } -foundbelow0: - cx = imax(sx0, x); - if(cx < ex0) - { - uchar *bsrc = &src[cx>>3]; - int bx = (cx&~7) + 8, bits = *bsrc; - if(!bits) cx = bx; - else - { - bits <<= cx&7; - if(bx >= ex0) - { - for(; cx < ex0; cx++, bits <<= 1) if(bits&0x80) { ex0 = cx-1; searchdist(x, y, cx, cy); goto foundabove0; } - goto foundabove0; - } - else for(; cx < bx; cx++, bits <<= 1) if(bits&0x80) { ex0 = cx-1; searchdist(x, y, cx, cy); goto foundabove0; } - } - while(cx < ex0) - { - bits = *++bsrc; - if(!bits) cx += 8; - else if((bx = cx + 8) >= ex0) - { - for(; cx < ex0; cx++, bits <<= 1) if(bits&0x80) { ex0 = cx-1; searchdist(x, y, cx, cy); goto foundabove0; } - goto foundabove0; - } - else for(; cx < bx; cx++, bits <<= 1) if(bits&0x80) { ex0 = cx-1; searchdist(x, y, cx, cy); goto foundabove0; } - } - } -foundabove0: - return sx0 < ex0; -} - -#define SUPERSAMPLE_MIN 1 -#define SUPERSAMPLE_MAX 32 -static int supersample = SUPERSAMPLE_MIN; - -void gensdf(struct fontchar *c) -{ - int w = c->glyph->bitmap.width, h = c->glyph->bitmap.rows, radius = c->sdfradius*supersample; - int dx, dy, dw = (w + 2*radius + supersample-1)/supersample, dh = (h + 2*radius + supersample-1)/supersample; - int x, y, x1 = INT_MAX, y1 = INT_MAX, x2 = INT_MIN, y2 = INT_MIN; - uchar *dst = (uchar *)malloc(dw*dh), *src; - if(!dst) fatal("tessfont: failed allocating signed distance field"); - c->sdfpitch = dw; - c->sdf = dst; - for(dy = 0; dy < dh; dy++) - for(dx = 0; dx < dw; dx++) - { - double total = 0; - for(y = dy*supersample - radius; y < (dy+1)*supersample - radius; y++) - for(x = dx*supersample - radius; x < (dx+1)*supersample - radius; x++) - { - int sx = imax(x - radius, 0), sy = imax(y - radius, 0), ex = imin(x + radius, w), ey = imin(y + radius, h), cy, val = 0; - if(y >= 0 && y < h && x >= 0 && x < w) - { - uchar *center = (uchar *)c->glyph->bitmap.buffer + y*c->glyph->bitmap.pitch; - val = (center[x>>3]<<(x&7))&0x80; - } - sdist = INT_MAX; - if(val) - { - for(cy = imin(ey, y)-1, sx1 = sx, ex1 = ex, src = (uchar *)c->glyph->bitmap.buffer + cy*c->glyph->bitmap.pitch; - cy >= sy && search1(x, y, w, radius, cy, src); - cy--, src -= c->glyph->bitmap.pitch); - for(cy = imax(sy, y), sx1 = sx, ex1 = ex, src = (uchar *)c->glyph->bitmap.buffer + cy*c->glyph->bitmap.pitch; - cy < ey && search1(x, y, w, radius, cy, src); - cy++, src += c->glyph->bitmap.pitch); - if(y - radius < 0) searchdist(x, y, x, -1); - if(y + radius > h) searchdist(x, y, x, h); - } - else - { - for(cy = imin(ey, y)-1, sx0 = sx, ex0 = ex, src = (uchar *)c->glyph->bitmap.buffer + cy*c->glyph->bitmap.pitch; - cy >= sy && search0(x, y, w, radius, cy, src); - cy--, src -= c->glyph->bitmap.pitch); - for(cy = imax(sy, y), sx0 = sx, ex0 = ex, src = (uchar *)c->glyph->bitmap.buffer + cy*c->glyph->bitmap.pitch; - cy < ey && search0(x, y, w, radius, cy, src); - cy++, src += c->glyph->bitmap.pitch); - } - if(val) total += sqrt(sdist); - else total -= sqrt(sdist); - } - *dst = (uchar)iclamp((int)round(127.5 + (127.5/(supersample*supersample))*total/radius), 0, 255); - if(*dst) - { - x1 = imin(x1, dx); - y1 = imin(y1, dy); - x2 = imax(x2, dx); - y2 = imax(y2, dy); - } - dst++; - } - if(x1 <= x2 && y1 <= y2) - { - c->sdfx = x1; - c->sdfy = y1; - c->sdfw = x2 - x1 + 1; - c->sdfh = y2 - y1 + 1; - } -} - -void writetexs(const char *name, struct fontchar *chars, int numchars, int numtexs, int tw, int th) -{ - int tex; - uchar *pixels = (uchar *)malloc(tw*th); - if(!pixels) fatal("tessfont: failed allocating textures"); - for(tex = 0; tex < numtexs; tex++) - { - const char *file = texfilename(name, tex); - int texchars = 0, i; - uchar *dst, *src; - memset(pixels, 0, tw*th); - for(i = 0; i < numchars; i++) - { - struct fontchar *c = &chars[i]; - int y; - if(c->tex != tex) continue; - texchars++; - dst = &pixels[c->y*tw + c->x]; - src = c->sdf + c->sdfy*c->sdfpitch + c->sdfx; - for(y = 0; y < c->sdfh; y++) - { - memcpy(dst, src, c->sdfw); - dst += tw; - src += c->sdfpitch; - } - } - printf("tessfont: writing %d chars to %s\n", texchars, file); - savepng(file, pixels, tw, th, 1, 0); - } - free(pixels); -} - -static float offsetx = 0, offsety = 0, border = 0, border2 = 0, outline = 0, outline2 = 0; -static int scale = 0; - -void writecfg(const char *name, struct fontchar *chars, int numchars, float x1, float y1, float x2, float y2, int sw, int sh, int argc, char **argv) -{ - FILE *f; - char file[256]; - int i, lastcode = 0, lasttex = 0; - snprintf(file, sizeof(file), "%s.cfg", name); - f = fopen(file, "w"); - if(!f) fatal("tessfont: failed writing %s", file); - printf("tessfont: writing %d chars to %s\n", numchars, file); - fprintf(f, "//"); - for(i = 1; i < argc; i++) - fprintf(f, " %s", argv[i]); - fprintf(f, "\n"); - fprintf(f, "font \"%s\" \"%s\" %d %d\n", name, texname(name, 0), sw, sh); - if(scale > 0) fprintf(f, "fontscale %d\n", scale); - if(border2) fprintf(f, "fontborder %g %g\n", border, border2); - else if(border) fprintf(f, "fontborder %g\n", border); - if(outline2) fprintf(f, "fontoutline %g %g\n", outline, outline2); - else if(outline) fprintf(f, "fontoutline %g\n", outline); - for(i = 0; i < numchars; i++) - { - struct fontchar *c = &chars[i]; - if(!lastcode && lastcode < c->code) - { - fprintf(f, "fontoffset \"%s\"\n", encodeutf8(c->uni)); - lastcode = c->code; - } - else if(lastcode < c->code) - { - if(lastcode + 1 == c->code) - fprintf(f, "fontskip // %d\n", lastcode); - else - fprintf(f, "fontskip %d // %d .. %d\n", c->code - lastcode, lastcode, c->code-1); - lastcode = c->code; - } - if(lasttex != c->tex) - { - fprintf(f, "\nfonttex \"%s\"\n", texname(name, c->tex)); - lasttex = c->tex; - } - float offx = c->sdfx-c->sdfradius + c->left + offsetx, offy = c->sdfy-c->sdfradius + y2-c->top + offsety; - if(c->code != c->uni) - fprintf(f, "fontchar %d %d %d %d %g %g %g // %s (%d -> 0x%X)\n", c->x, c->y, c->sdfw, c->sdfh, offx, offy, c->advance, encodeutf8(c->uni), c->code, c->uni); - else - fprintf(f, "fontchar %d %d %d %d %g %g %g // %s (%d)\n", c->x, c->y, c->sdfw, c->sdfh, offx, offy, c->advance, encodeutf8(c->uni), c->code); - lastcode++; - } - fclose(f); -} - -int groupchar(int c) -{ - switch(c) - { - case 0x152: case 0x153: case 0x178: return 1; - } - if(c < 127 || c >= 0x2000) return 0; - if(c < 0x100) return 1; - if(c < 0x400) return 2; - return 3; -} - -int sortchars(const void *x, const void *y) -{ - const struct fontchar *xc = *(const struct fontchar **)x, *yc = *(const struct fontchar **)y; - int xg = groupchar(xc->uni), yg = groupchar(yc->uni); - if(xg < yg) return -1; - if(xg > yg) return 1; - if(xc->sdfh != yc->sdfh) return yc->sdfh - xc->sdfh; - if(xc->sdfw != yc->sdfw) return yc->sdfw - xc->sdfw; - return yc->uni - xc->uni; -} - -int scorechar(struct fontchar *f, int pad, int tw, int th, int rw, int rh, int ry) -{ - int score = 0; - if(rw + f->sdfw > tw) { ry += rh + pad; score = 1; } - if(ry + f->sdfh > th) score = 2; - return score; -} - -int main(int argc, char **argv) -{ - FT_Library l; - FT_Face f; - int i, radius, pad, w, h, tw, th, c, numgen = 0, trial = -2, rw = 0, rh = 0, ry = 0, sw = 0, sh = 0; - float advance, x1 = INT_MAX, x2 = INT_MIN, y1 = INT_MAX, y2 = INT_MIN, w2 = 0, h2 = 0; - time_t starttime, endtime; - struct fontchar chars[256]; - struct fontchar *order[256]; - int numchars = 0, numtex = 0; - if(argc < 13) - fatal("Usage: tessfont infile outfile supersample border[:border2[:outline:outline2]] radius pad offsetx[:offsety] advance charwidth charheight texwidth texheight [spacewidth spaceheight scale texdir]"); - supersample = iclamp(atoi(argv[3]), SUPERSAMPLE_MIN, SUPERSAMPLE_MAX); - sscanf(argv[4], "%f:%f:%f:%f", &border, &border2, &outline, &outline2); - radius = atoi(argv[5]); - pad = atoi(argv[6]); - sscanf(argv[7], "%f:%f", &offsetx, &offsety); - advance = atof(argv[8]); - w = atoi(argv[9]); - h = atoi(argv[10]); - tw = atoi(argv[11]); - th = atoi(argv[12]); - if(argc > 13) sw = atoi(argv[13]); - if(argc > 14) sh = atoi(argv[14]); - if(argc > 15) scale = atoi(argv[15]); - if(argc > 16) texdir = argv[16]; - if(FT_Init_FreeType(&l)) - fatal("tessfont: failed initing freetype"); - if(FT_New_Face(l, argv[1], 0, &f) || - FT_Set_Charmap(f, f->charmaps[0]) || - FT_Set_Pixel_Sizes(f, w*supersample, h*supersample)) - fatal("tessfont: failed loading font %s", argv[1]); - setbuf(stdout, NULL); - starttime = time(NULL); - for(c = 0; c < 256; c++) if(iscubeprint(c)) - { - FT_Glyph p; - FT_BitmapGlyph b; - struct fontchar *dst = &chars[numchars]; - dst->code = c; - dst->uni = cube2uni(c); - if(FT_Load_Char(f, dst->uni, FT_LOAD_TARGET_MONO)) - fatal("tessfont: failed loading character %s", encodeutf8(dst->uni)); - FT_Get_Glyph(f->glyph, &p); - FT_Glyph_To_Bitmap(&p, FT_RENDER_MODE_MONO, 0, 1); - b = (FT_BitmapGlyph)p; - dst->tex = -1; - dst->x = INT_MIN; - dst->y = INT_MIN; - dst->w = b->bitmap.width/(float)supersample; - dst->h = b->bitmap.rows/(float)supersample; - dst->left = b->left/(float)supersample; - dst->top = b->top/(float)supersample; - dst->advance = offsetx + p->advance.x/(float)(supersample<<16) + advance; - dst->glyph = b; - dst->sdfradius = radius; - dst->sdf = NULL; - dst->sdfpitch = 0; - dst->sdfx = 0; - dst->sdfy = 0; - dst->sdfw = 0; - dst->sdfh = 0; - order[numchars++] = dst; - if(!numgen) printf("tessfont: generating %d", dst->code); - else printf(" %d", dst->code); - numgen += dst->code >= 100 ? 4 : (dst->code >= 10 ? 3 : 2); - if(numgen > 50) { printf("\n"); numgen = 0; } - gensdf(dst); - } - if(numgen) printf("\n"); - qsort(order, numchars, sizeof(order[0]), sortchars); - for(i = 0; i < numchars;) - { - struct fontchar *dst; - int j, k, trial0, prevscore, dstscore, fitscore; - for(trial0 = trial, prevscore = -1; (trial -= 2) >= trial0-512;) - { - int g, fw = rw, fh = rh, fy = ry, curscore = 0, reused = 0; - for(j = i; j < numchars; j++) - { - dst = order[j]; - if(dst->tex >= 0 || dst->tex <= trial) continue; - g = groupchar(dst->uni); - dstscore = scorechar(dst, pad, tw, th, fw, fh, fy); - for(k = j; k < numchars; k++) - { - struct fontchar *fit = order[k]; - if(fit->tex >= 0 || fit->tex <= trial) continue; - if(fit->tex >= trial0 && groupchar(fit->uni) != g) break; - fitscore = scorechar(fit, pad, tw, th, fw, fh, fy); - if(fitscore < dstscore || (fitscore == dstscore && fit->sdfh > dst->sdfh)) - { - dst = fit; - dstscore = fitscore; - } - } - if(fw + dst->sdfw > tw) - { - fy += fh + pad; - fw = fh = 0; - } - if(fy + dst->sdfh > th) - { - fy = fw = fh = 0; - if(curscore > 0) break; - } - if(dst->tex >= trial+1 && dst->tex <= trial+2) { dst->tex = trial; reused++; } - else dst->tex = trial; - fw += dst->sdfw + pad; - fh = imax(fh, dst->sdfh); - if(dst != order[j]) --j; - curscore++; - } - if(reused < prevscore || curscore <= prevscore) break; - prevscore = curscore; - } - for(; i < numchars; i++) - { - dst = order[i]; - if(dst->tex >= 0) continue; - dstscore = scorechar(dst, pad, tw, th, rw, rh, ry); - for(j = i; j < numchars; j++) - { - struct fontchar *fit = order[j]; - if(fit->tex < trial || fit->tex > trial+2) continue; - fitscore = scorechar(fit, pad, tw, th, rw, rh, ry); - if(fitscore < dstscore || (fitscore == dstscore && fit->sdfh > dst->sdfh)) - { - dst = fit; - dstscore = fitscore; - } - } - if(dst->tex < trial || dst->tex > trial+2) break; - if(rw + dst->sdfw > tw) - { - ry += rh + pad; - rw = rh = 0; - } - if(ry + dst->sdfh > th) - { - ry = rw = rh = 0; - numtex++; - } - dst->tex = numtex; - dst->x = rw; - dst->y = ry; - rw += dst->sdfw + pad; - rh = imax(rh, dst->sdfh); - y1 = fmin(y1, dst->top - dst->h); - y2 = fmax(y2, dst->top); - x1 = fmin(x1, dst->left); - x2 = fmax(x2, dst->left + dst->w); - w2 = fmax(w2, dst->w); - h2 = fmax(h2, dst->h); - if(dst != order[i]) --i; - } - } - if(rh > 0) numtex++; - if(sh <= 0) sh = (int)ceil(y2 - y1); - if(sw <= 0) sw = sh/3; - endtime = time(NULL); - writetexs(argv[2], chars, numchars, numtex, tw, th); - writecfg(argv[2], chars, numchars, x1, y1, x2, y2, sw, sh, argc, argv); - for(i = 0; i < numchars; i++) - { - struct fontchar *c = &chars[i]; - FT_Done_Glyph((FT_Glyph)c->glyph); - if(c->sdf) free(c->sdf); - } - FT_Done_FreeType(l); - printf("tessfont: (%g, %g) .. (%g, %g) = (%g, %g) / (%g, %g), %d texs, %d secs\n", x1, y1, x2, y2, x2 - x1, y2 - y1, w2, h2, numtex, (int)(endtime - starttime)); - return EXIT_SUCCESS; -} - diff --git a/source/shared/tools.cpp b/source/shared/tools.cpp index e6b2ac9f9..95c0c3aa5 100644 --- a/source/shared/tools.cpp +++ b/source/shared/tools.cpp @@ -1,6 +1,7 @@ // implementation of generic tools #include "cube.h" +#include "unicode.h" void *operator new(size_t size) { @@ -192,22 +193,58 @@ void getstring(char *text, ucharbuf &p, size_t len) while(*t++); } -void filtertext(char *dst, const char *src, bool colors, bool newlines, bool whitespace, bool forcespace, size_t len) +void filtertext(char *dst, const char *src, uint flags, size_t len, int unilen) { - for(int c = uchar(*src); c; c = uchar(*++src)) + uint c; + size_t s = uni_getchar(src, c); + for(const char *p = src; c; p += s, s = uni_getchar(p, c)) { - if((!colors && c == '\f') || (!newlines && c == '\n')) + if(!(flags&T_COLORS) && c == '\f') { - if(!*++src) break; + if(!*(++p)) break; continue; } - if(!iscubeprint(c)) + if(!(flags&T_NEWLINES) && (c == '\n' || c == '\r')) { - if(!iscubespace(c) || !whitespace) continue; - if(forcespace) c = ' '; + if(!*(p+1)) break; + continue; + } + if(!((flags&T_NAME) ? iscubenamesafe(c) : (!iscubecntrl(c))) && c != '\f' && c != '\n' && c != '\r') + { + if(!iscubespace(c) || !(flags&T_WHITESPACE)) continue; + if(flags&T_FORCESPACE) + { + *dst++ = ' '; + len--; + unilen--; + continue; + } + } + s = min(s, len); // prevent overflow + loopi(s) *dst++ = p[i]; + len -= s; + unilen--; + if(!len || !unilen) break; + } + *dst = '\0'; +} +// like `filtertext` but only strips colors and control characters, used for logs +void filteruni(char *dst, const char *src, size_t len) +{ + uint c; + size_t s = uni_getchar(src, c); + for(const char *p = src; c; p += s, s = uni_getchar(p, c)) + { + if(c == '\f') + { + if(!*(++p)) break; + continue; } - *dst++ = c; - if(!--len) break; + if(iscubecntrl(c) || c == 0xFFFD) continue; + s = min(s, len); // prevent overflow + loopi(s) *dst++ = p[i]; + len -= s; + if(!len) break; } *dst = '\0'; } diff --git a/source/shared/tools.h b/source/shared/tools.h index 314b391e6..4212e64fd 100644 --- a/source/shared/tools.h +++ b/source/shared/tools.h @@ -51,6 +51,13 @@ static inline void swap(T &a, T &b) a = b; b = t; } +template +static inline T exchange(T& a, U&& b) +{ + T old_value = (T&&)a; + a = b; // should be `a = std::forward(b)` or use `std::exchange` directly + return old_value; +} #ifdef max #undef max #endif @@ -1215,14 +1222,14 @@ template struct queue T remove(int offset) { - T val = removing(offset); - if(head+offset >= SIZE) for(int i = head+offset - SIZE + 1; i < tail; i++) data[i-1] = data[i]; - else if(head < tail) for(int i = head+offset + 1; i < tail; i++) data[i-1] = data[i]; + T val = (T&&)(removing(offset)); + if(head+offset >= SIZE) for(int i = head+offset - SIZE + 1; i < tail; i++) data[i-1] = (T&&)(data[i]); + else if(head < tail) for(int i = head+offset + 1; i < tail; i++) data[i-1] = (T&&)(data[i]); else { - for(int i = head+offset + 1; i < SIZE; i++) data[i-1] = data[i]; - data[SIZE-1] = data[0]; - for(int i = 1; i < tail; i++) data[i-1] = data[i]; + for(int i = head+offset + 1; i < SIZE; i++) data[i-1] = (T&&)(data[i]); + data[SIZE-1] = (T&&)(data[0]); + for(int i = 1; i < tail; i++) data[i-1] = (T&&)(data[i]); } tail--; if(tail < 0) tail += SIZE; @@ -1352,47 +1359,134 @@ struct streambuf size_t length() { return s->size(); } }; +// for `filtertext` enum { - CT_PRINT = 1<<0, - CT_SPACE = 1<<1, - CT_DIGIT = 1<<2, - CT_ALPHA = 1<<3, - CT_LOWER = 1<<4, - CT_UPPER = 1<<5, - CT_UNICODE = 1<<6 + T_NONE = 0, + T_COLORS = 1<<0, + T_NEWLINES = 1<<1, + T_WHITESPACE = 1<<2, + T_FORCESPACE = 1<<3, + T_NAME = 1<<4 }; -extern const uchar cubectype[256]; -static inline int iscubeprint(uchar c) { return cubectype[c]&CT_PRINT; } -static inline int iscubespace(uchar c) { return cubectype[c]&CT_SPACE; } -static inline int iscubealpha(uchar c) { return cubectype[c]&CT_ALPHA; } -static inline int iscubealnum(uchar c) { return cubectype[c]&(CT_ALPHA|CT_DIGIT); } -static inline int iscubelower(uchar c) { return cubectype[c]&CT_LOWER; } -static inline int iscubeupper(uchar c) { return cubectype[c]&CT_UPPER; } -static inline int iscubepunct(uchar c) { return cubectype[c] == CT_PRINT; } -static inline int cube2uni(uchar c) -{ - extern const int cube2unichars[256]; - return cube2unichars[c]; -} -static inline uchar uni2cube(int c) -{ - extern const int uni2cubeoffsets[8]; - extern const uchar uni2cubechars[]; - return uint(c) <= 0x7FF ? uni2cubechars[uni2cubeoffsets[c>>8] + (c&0xFF)] : 0; -} -static inline uchar cubelower(uchar c) + +static inline int iscubespace(uint c) { return c == ' ' || c == '\n' || c == '\r' || c == '\t' ? 1 : 0; } +static inline int iscubealpha(uint c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ? 1 : 0; } +static inline int iscubealnum(uint c) { return (c >= '0' && c <= '9') || iscubealpha(c) ? 1 : 0; } +static inline int iscubelower(uint c) { return (c >= 'a' && c <= 'z') ? 1 : 0; } +static inline int iscubeupper(uint c) { return (c >= 'A' && c <= 'Z') ? 1 : 0; } +static inline int iscubecntrl(uint c) { return (c <= 0x08 || c == 0x0B || (c >= 0x0E && c <= 0x1F) || (c >= 0x7F && c <= 0x9F)) ? 1 : 0; } +static inline int iscubepunct(uint c) { return (c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') ? 1 : 0; } +// a subset of console characters that are allowed in player names +// the default font should contain glyphs for all of these +static inline int iscubenamesafe(uint c) { - extern const uchar cubelowerchars[256]; - return cubelowerchars[c]; + return (c >= 0x0021 && c <= 0x007E) // ASCII + + || (c >= 0x00A1 && c <= 0x00AC) + || (c >= 0x00AE && c <= 0x00FF) // Latin-1 Supplement + + || (c >= 0x0100 && c <= 0x0131) + || (c >= 0x0134 && c <= 0x0148) + || (c >= 0x014A && c <= 0x017E) // Latin Extended-A (exclude 0x131, 0x132, 0x149 and 0x17F) + + || (c >= 0x0218 && c <= 0x021B) // Latin Extended-B: Romanian + + || (c == 0x0386) + || (c >= 0x0388 && c <= 0x038A) + || (c == 0x038C) + || (c >= 0x038E && c <= 0x03A1) + || (c >= 0x03A3 && c <= 0x03CE) // Greek and Coptic + + || (c >= 0x0400 && c <= 0x045F) // Cyrillic + + || (c >= 0x1EA0 && c <= 0x1EF9) // Latin Extended Additional: Vietnamese + ; } -static inline uchar cubeupper(uchar c) +// used to detect duplicate names +// source: https://www.unicode.org/Public/security/latest/confusables.txt +static inline uint homoglyph(uint c) { - extern const uchar cubeupperchars[256]; - return cubeupperchars[c]; + switch(c) + { + case 0x60: + case 0xb4: return 0x27; + case 0xb8: return 0x2c; + case 0x417: return 0x33; + case 0x431: return 0x36; + case 0x391: + case 0x410: return 0x41; + case 0x392: + case 0x412: return 0x42; + case 0x421: return 0x43; + case 0x395: + case 0x415: return 0x45; + case 0x397: + case 0x41d: return 0x48; + case 0x408: return 0x4a; + case 0x39a: + case 0x41a: return 0x4b; + case 0x39c: + case 0x41c: return 0x4d; + case 0x39d: return 0x4e; + case 0x30: + case 0x39f: + case 0x41e: return 0x4f; + case 0x3a1: + case 0x420: return 0x50; + case 0x405: return 0x53; + case 0x3a4: + case 0x422: return 0x54; + case 0x3a7: + case 0x425: return 0x58; + case 0x3a5: + case 0x423: return 0x59; + case 0x396: return 0x5a; + case 0x3b1: + case 0x430: return 0x61; + case 0x42c: return 0x62; + case 0x441: return 0x63; + case 0x435: return 0x65; + case 0x131: + case 0x3b9: + case 0x456: return 0x69; + case 0x458: return 0x6a; + case 0x7c: + case 0x31: + case 0x49: + case 0x399: + case 0x406: return 0x6c; + case 0x3bf: + case 0x3c3: + case 0x43e: return 0x6f; + case 0x3c1: + case 0x440: return 0x70; + case 0x433: return 0x72; + case 0x455: return 0x73; + case 0x3c5: return 0x75; + case 0x3bd: return 0x76; + case 0xd7: + case 0x445: return 0x78; + case 0x3b3: + case 0x443: return 0x79; + case 0x150: return 0xd6; + case 0x3b2: return 0xdf; + case 0x11a: return 0x114; + case 0x11b: return 0x115; + case 0x3ba: + case 0x43a: return 0x138; + case 0x21a: return 0x162; + case 0x413: return 0x393; + case 0x41f: return 0x3a0; + case 0x424: return 0x3a6; + case 0xb5: return 0x3bc; + case 0x43f: return 0x3c0; + case 0x419: return 0x40d; + case 0x45d: return 0x439; + } + return c; } -extern size_t decodeutf8(uchar *dst, size_t dstlen, const uchar *src, size_t srclen, size_t *carry = NULL); -extern size_t encodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry = NULL); + extern int cubecasecmp(const char *s1, const char *s2, int n = INT_MAX); static inline bool cubecaseequal(const char *s1, const char *s2, int n = INT_MAX) { return !cubecasecmp(s1, s2, n); } extern char *cubecasefind(const char *haystack, const char *needle); @@ -1416,8 +1510,7 @@ extern stream *openzipfile(const char *filename, const char *mode); extern stream *openfile(const char *filename, const char *mode); extern stream *opentempfile(const char *filename, const char *mode); extern stream *opengzfile(const char *filename, const char *mode, stream *file = NULL, int level = Z_BEST_COMPRESSION); -extern stream *openutf8file(const char *filename, const char *mode, stream *file = NULL); -extern char *loadfile(const char *fn, size_t *size, bool utf8 = true); +extern char *loadfile(const char *fn, size_t *size); extern bool listdir(const char *dir, bool rel, const char *ext, vector &files); extern int listfiles(const char *dir, const char *ext, vector &files); extern int listzipfiles(const char *dir, const char *ext, vector &files); @@ -1441,8 +1534,10 @@ extern void sendstring(const char *t, packetbuf &p); extern void sendstring(const char *t, vector &p); extern void getstring(char *t, ucharbuf &p, size_t len); template static inline void getstring(char (&t)[N], ucharbuf &p) { getstring(t, p, N); } -extern void filtertext(char *dst, const char *src, bool colors, bool newlines, bool whitespace, bool forcespace, size_t len); -template static inline void filtertext(char (&dst)[N], const char *src, bool colors = false, bool newlines = true, bool whitespace = true, bool forcespace = false) { filtertext(dst, src, colors, newlines, whitespace, forcespace, N-1); } +extern void filtertext(char *dst, const char *src, uint flags, size_t len, int unilen = -1); +template static inline void filtertext(char (&dst)[N], const char *src, uint flags) { filtertext(dst, src, flags, N-1, N-1); } +extern void filteruni(char *dst, const char *src, size_t len); +template static inline void filteruni(char (&dst)[N], const char *src) { filteruni(dst, src, N-1); } struct ipmask { diff --git a/source/shared/unicode.h b/source/shared/unicode.h new file mode 100644 index 000000000..7ddea6a84 --- /dev/null +++ b/source/shared/unicode.h @@ -0,0 +1,186 @@ +// UTF-8 support + +#include + +// reads the next character into `codepoint` and returns the number of bytes it takes up +// `str` must be null-terminated +static inline int uni_getchar(const char *str, unsigned int& codepoint) +{ + const unsigned char *p = (const unsigned char *)str; + if(p[0] <= 0x7F) + { + codepoint = p[0]; + return 1; + } + if(p[0] >> 5 == 0x06 && p[1] >> 6 == 0x02) + { + codepoint = ((p[0] & 0x1F) << 6) | (p[1] & 0x3F); + if(codepoint < 0x80) codepoint = 0xFFFD; + return 2; + } + if(p[0] >> 4 == 0x0E && p[1] >> 6 == 0x02 && p[2] >> 6 == 0x02) + { + codepoint = ((p[0] & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F); + if(codepoint < 0x800) codepoint = 0xFFFD; + return 3; + } + if(p[0] >> 3 == 0x1E && p[1] >> 6 == 0x02 && p[2] >> 6 == 0x02 && p[3] >> 6 == 0x02) + { + codepoint = ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); + if(codepoint < 0x10000 || codepoint > 0x10FFFF) codepoint = 0xFFFD; + return 4; + } + codepoint = 0xFFFD; + return 1; +} + +// returns the number of bytes composing the previous char in the string +static inline int uni_prevchar(const char *str, int i) +{ + if(i <= 0) return 0; + const unsigned char *p = (const unsigned char *)str; + if(p[i-1] <= 0x7F) return 1; + if(i >= 2 && p[i-2] >> 5 == 0x06 && p[i-1] >> 6 == 0x02) return 2; + if(i >= 3 && p[i-3] >> 4 == 0x0E && p[i-2] >> 6 == 0x02 && p[i-1] >> 6 == 0x02) return 3; + if(i >= 4 && p[i-4] >> 3 == 0x1E && p[i-3] >> 6 == 0x02 && p[i-2] >> 6 == 0x02 && p[i-1] >> 6 == 0x02) return 4; + return 1; +} + +// encodes a single codepoint +static inline int uni_code2str(unsigned int codepoint, char *dst) +{ + if(codepoint <= 0x007F) + { + dst[0] = codepoint; + dst[1] = '\0'; + return 1; + } + if(codepoint <= 0x07FF) + { + dst[0] = 0xC0 | (codepoint >> 6); + dst[1] = 0x80 | (codepoint & 0x3F); + dst[2] = '\0'; + return 2; + } + if(codepoint <= 0xFFFF) + { + dst[0] = 0xE0 | (codepoint >> 12); + dst[1] = 0x80 | ((codepoint >> 6) & 0x3F); + dst[2] = 0x80 | (codepoint & 0x3F); + dst[3] = '\0'; + return 3; + } + if(codepoint <= 0x10FFFF) + { + dst[0] = 0xF0 | (codepoint >> 18); + dst[1] = 0x80 | ((codepoint >> 12) & 0x3F); + dst[2] = 0x80 | ((codepoint >> 6) & 0x3F); + dst[3] = 0x80 | (codepoint & 0x3F); + dst[4] = '\0'; + return 4; + } + dst[0] = '\0'; + return 0; +} + +static inline unsigned int uni_strlen(const char *str) +{ + unsigned int _c, len = 0; + while(*str) + { + str += uni_getchar(str, _c); + ++len; + } + return len; +} + +// returns the codepoint of the character at index `ix` +static inline unsigned int uni_charat(const char *str, int ix) +{ + unsigned int c; + if(ix >= 0) + { + for(int i = 0; i < ix; ++i) + { + if(!*str) return 0; + str += uni_getchar(str, c); + } + if(!*str) return 0; + uni_getchar(str, c); + return c; + } + + const unsigned int len = strlen(str); + unsigned int b = 0; + for(int i = 0; i > ix; --i) + { + b += uni_prevchar(str, len-b); + } + uni_getchar(str+len-b, c); + return c; +} + +// offset of the character at index `ix` +static inline int uni_offset(const char *str, unsigned int ix) +{ + const char *p = str; + unsigned int _c; + for(unsigned int i = 0; i < ix; ++i) + { + if(!*p) break; + p += uni_getchar(p, _c); + } + return (p - str); +} +// same but starting from the end of the string (str[-ix]) +static inline int uni_negoffset(const char *str, unsigned int ix) +{ + const char *p = str + strlen(str); + for(unsigned int i = 0; i < ix; ++i) + { + if(p <= str) break; + p -= uni_prevchar(str, p - str); + } + return (p - str); +} + +// returns the index of the unicode character at offset `off` +static inline int uni_index(const char *str, unsigned int off) +{ + const char *p = str, *end = str + strlen(str); + unsigned int _c; + unsigned int ret = 0; + while(ret < off) + { + if(p >= end || p >= str + off) return ret; + p += uni_getchar(p, _c); + ++ret; + } + return ret; +} + +// only supports ASCII +static inline char uni_charlower(char c) +{ + if(c >= 'A' && c <= 'Z') return 'a' + (c - 'A'); + return c; +} +static inline char uni_charupper(char c) +{ + if(c >= 'a' && c <= 'z') return 'A' + (c - 'a'); + return c; +} +static inline void uni_strlower(const char *src, char *dst) +{ + for(const char *p = src; *p; ++p) + { + *dst++ = uni_charlower(*p); + } +} +static inline void uni_strupper(const char *src, char *dst) +{ + for(const char *p = src; *p; ++p) + { + *dst++ = uni_charupper(*p); + } +} \ No newline at end of file