Skip to content

Commit 55a0f1d

Browse files
committed
Implementation of slug text rendering.
1 parent 31d3e57 commit 55a0f1d

File tree

11 files changed

+1223
-0
lines changed

11 files changed

+1223
-0
lines changed

src/applications/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ if(NOT OSGEARTH_BUILD_PLATFORM_IPHONE)
6464
add_subdirectory(osgearth_infinitescroll)
6565
add_subdirectory(osgearth_video)
6666
add_subdirectory(osgearth_heatmap)
67+
add_subdirectory(osgearth_slugtext)
6768

6869
if(OSGEARTH_BUILD_LEGACY_CONTROLS_API)
6970
add_subdirectory(osgearth_cluster)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
add_osgearth_app(
2+
TARGET osgearth_slugtext
3+
SOURCES osgearth_slugtext.cpp
4+
FOLDER Examples)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/* osgEarth
2+
* Copyright 2025 Pelican Mapping
3+
* MIT License
4+
*/
5+
6+
// Demo application for SlugText rendering.
7+
// Renders text using the Slug algorithm (Lengyel 2017) which produces
8+
// pixel-accurate results at any scale, rotation, or perspective.
9+
//
10+
// Usage: osgearth_slugtext [--font <path>] [--text <string>]
11+
12+
#include <osgViewer/Viewer>
13+
#include <osgEarth/SlugText>
14+
#include <osgEarth/GLUtils>
15+
#include <osg/MatrixTransform>
16+
#include <osg/Camera>
17+
#include <iostream>
18+
19+
using namespace osgEarth;
20+
21+
int usage(const char* name, const char* msg)
22+
{
23+
std::cerr << "Error: " << msg << std::endl;
24+
std::cerr << "Usage: " << name << " [--font <path.ttf>] [--text <string>]" << std::endl;
25+
return -1;
26+
}
27+
28+
osg::Camera* createHUDCamera(int width, int height)
29+
{
30+
osg::Camera* camera = new osg::Camera();
31+
camera->setProjectionMatrix(osg::Matrix::ortho2D(0, width, 0, height));
32+
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
33+
camera->setViewMatrix(osg::Matrix::identity());
34+
camera->setClearMask(GL_DEPTH_BUFFER_BIT);
35+
camera->setRenderOrder(osg::Camera::POST_RENDER);
36+
camera->setAllowEventFocus(false);
37+
return camera;
38+
}
39+
40+
int main(int argc, char** argv)
41+
{
42+
osg::ArgumentParser arguments(&argc, argv);
43+
osgEarth::initialize(arguments);
44+
45+
std::string fontPath;
46+
if (!arguments.read("--font", fontPath))
47+
{
48+
// Try common font paths
49+
#ifdef _WIN32
50+
fontPath = "C:/Windows/Fonts/arial.ttf";
51+
#elif __APPLE__
52+
fontPath = "/Library/Fonts/Arial.ttf";
53+
#else
54+
fontPath = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";
55+
#endif
56+
}
57+
58+
std::string text;
59+
if (!arguments.read("--text", text))
60+
{
61+
text = "Hello, Slug!";
62+
}
63+
64+
osgViewer::Viewer viewer(arguments);
65+
66+
int width = 1280, height = 720;
67+
osg::Group* root = new osg::Group();
68+
69+
// Create an orthographic HUD camera for 2D text display
70+
osg::Camera* hud = createHUDCamera(width, height);
71+
root->addChild(hud);
72+
73+
// Large text
74+
{
75+
auto* mt = new osg::MatrixTransform();
76+
mt->setMatrix(osg::Matrix::translate(50, height - 100, 0));
77+
auto* st = new SlugText(text, fontPath, 48.0f);
78+
st->setColor(osg::Vec4(1, 1, 1, 1));
79+
mt->addChild(st);
80+
hud->addChild(mt);
81+
}
82+
83+
// Medium text
84+
{
85+
auto* mt = new osg::MatrixTransform();
86+
mt->setMatrix(osg::Matrix::translate(50, height - 180, 0));
87+
auto* st = new SlugText("Resolution Independent Text", fontPath, 32.0f);
88+
st->setColor(osg::Vec4(0.8f, 0.9f, 1.0f, 1.0f));
89+
mt->addChild(st);
90+
hud->addChild(mt);
91+
}
92+
93+
// Small text
94+
{
95+
auto* mt = new osg::MatrixTransform();
96+
mt->setMatrix(osg::Matrix::translate(50, height - 240, 0));
97+
auto* st = new SlugText("Pixel Perfect at Any Scale", fontPath, 16.0f);
98+
st->setColor(osg::Vec4(1.0f, 0.9f, 0.7f, 1.0f));
99+
mt->addChild(st);
100+
hud->addChild(mt);
101+
}
102+
103+
// 3D perspective text (rotated)
104+
{
105+
auto* mt = new osg::MatrixTransform();
106+
osg::Matrix m;
107+
m.makeRotate(osg::DegreesToRadians(15.0), osg::Vec3(0, 0, 1));
108+
m.setTrans(osg::Vec3(50, height - 350, 0));
109+
mt->setMatrix(m);
110+
auto* st = new SlugText("Rotated Text", fontPath, 36.0f);
111+
st->setColor(osg::Vec4(0.5f, 1.0f, 0.5f, 1.0f));
112+
mt->addChild(st);
113+
hud->addChild(mt);
114+
}
115+
116+
// Very large text to show resolution independence
117+
{
118+
auto* mt = new osg::MatrixTransform();
119+
mt->setMatrix(osg::Matrix::translate(50, 80, 0));
120+
auto* st = new SlugText("BIG", fontPath, 200.0f);
121+
st->setColor(osg::Vec4(1.0f, 0.5f, 0.3f, 0.5f));
122+
mt->addChild(st);
123+
hud->addChild(mt);
124+
}
125+
126+
viewer.setSceneData(root);
127+
128+
#ifndef OSG_GL3_AVAILABLE
129+
viewer.setRealizeOperation(new osgEarth::GL3RealizeOperation());
130+
#endif
131+
132+
return viewer.run();
133+
}

src/osgEarth/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ find_package(GDAL REQUIRED)
1212
find_package(SQLite3 REQUIRED)
1313

1414
# optional
15+
find_package(Freetype QUIET)
1516
find_package(geos QUIET)
1617
find_package(blend2d QUIET)
1718
find_package(spdlog QUIET)
@@ -79,6 +80,7 @@ set(TARGET_GLSL
7980
WireLines.glsl
8081
PhongLighting.glsl
8182
PointDrawable.glsl
83+
SlugText.glsl
8284
Text.glsl
8385
Text_legacy.glsl
8486
ContourMap.glsl
@@ -383,6 +385,8 @@ SET(TARGET_H
383385
Skins
384386
Sky
385387
SkyView
388+
SlugFont
389+
SlugText
386390
SpatialReference
387391
StarData
388392
StateSetCache
@@ -727,6 +731,8 @@ set(TARGET_SRC
727731
Skins.cpp
728732
Sky.cpp
729733
SkyView.cpp
734+
SlugFont.cpp
735+
SlugText.cpp
730736
SpatialReference.cpp
731737
StateSetCache.cpp
732738
Status.cpp
@@ -887,6 +893,13 @@ if(blosc_FOUND)
887893
target_link_libraries(${LIB_NAME} PRIVATE blosc_${shared_or_static})
888894
endif()
889895

896+
# FreeType (for SlugFont)
897+
if(FREETYPE_FOUND)
898+
message(STATUS "Found FreeType")
899+
target_include_directories(${LIB_NAME} PRIVATE ${FREETYPE_INCLUDE_DIRS})
900+
target_link_libraries(${LIB_NAME} PRIVATE ${FREETYPE_LIBRARIES})
901+
endif()
902+
890903
# GEOS support?
891904
if(geos_FOUND)
892905
message(STATUS "Found geos")

src/osgEarth/Shaders

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace osgEarth { namespace Util
3232
std::string WireLines;
3333
std::string PointDrawable;
3434
std::string PhongLighting;
35+
std::string SlugText;
3536
std::string Text, TextLegacy;
3637
std::string ContourMap;
3738
std::string GeodeticGraticule;

src/osgEarth/Shaders.cpp.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ namespace osgEarth { namespace Util
8080
PhongLighting = "PhongLighting.glsl";
8181
_sources[PhongLighting] = @PhongLighting.glsl@;
8282

83+
// Slug Text
84+
SlugText = "SlugText.glsl";
85+
_sources[SlugText] = @SlugText.glsl@;
86+
8387
// Text
8488
Text = "Text.glsl";
8589
_sources[Text] = @Text.glsl@;

src/osgEarth/SlugFont

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* osgEarth
2+
* Copyright 2025 Pelican Mapping
3+
* MIT License
4+
*/
5+
#ifndef OSGEARTH_SLUG_FONT_H
6+
#define OSGEARTH_SLUG_FONT_H 1
7+
8+
#include <osgEarth/Common>
9+
#include <osg/Referenced>
10+
#include <osg/Texture2D>
11+
#include <map>
12+
#include <vector>
13+
#include <string>
14+
#include <cstdint>
15+
16+
namespace osgEarth
17+
{
18+
/**
19+
* Extracts quadratic Bezier glyph outlines from a TrueType font via FreeType
20+
* and packs them into GPU textures for use with the Slug rendering algorithm
21+
* (Lengyel 2017).
22+
*
23+
* Two textures are produced:
24+
* - Curve texture (RGBA16F): Bezier control points
25+
* - Band texture (RGBA16UI): Band headers and curve indices
26+
*/
27+
class OSGEARTH_EXPORT SlugFont : public osg::Referenced
28+
{
29+
public:
30+
SlugFont(const std::string& fontPath);
31+
32+
struct GlyphData
33+
{
34+
// Texture location of this glyph's band data
35+
int bandTexX = 0, bandTexY = 0;
36+
// Band max indexes (horizontal, vertical)
37+
int bandMaxX = 0, bandMaxY = 0;
38+
// Band transform: scale.xy, offset.xy
39+
float bandScaleX = 0, bandScaleY = 0, bandOffsetX = 0, bandOffsetY = 0;
40+
// Glyph metrics (in em units, normalized to [0,1])
41+
float bearingX = 0, bearingY = 0, width = 0, height = 0, advance = 0;
42+
};
43+
44+
const GlyphData* getGlyph(uint32_t codepoint) const;
45+
osg::Texture2D* getCurveTexture() const { return _curveTexture.get(); }
46+
osg::Texture2D* getBandTexture() const { return _bandTexture.get(); }
47+
bool valid() const { return _valid; }
48+
49+
static osg::ref_ptr<SlugFont> get(const std::string& fontPath);
50+
51+
struct CurveData {
52+
float x1, y1, x2, y2, x3, y3;
53+
};
54+
55+
protected:
56+
virtual ~SlugFont();
57+
58+
private:
59+
void load(const std::string& fontPath);
60+
void processGlyph(void* face, uint32_t codepoint);
61+
void buildTextures();
62+
63+
struct BandEntry {
64+
int curveCount;
65+
std::vector<int> curveIndices;
66+
};
67+
68+
struct GlyphBuildData {
69+
GlyphData metrics;
70+
std::vector<CurveData> curves;
71+
std::vector<BandEntry> hbands;
72+
std::vector<BandEntry> vbands;
73+
};
74+
75+
std::map<uint32_t, GlyphBuildData> _glyphBuild;
76+
std::map<uint32_t, GlyphData> _glyphs;
77+
osg::ref_ptr<osg::Texture2D> _curveTexture;
78+
osg::ref_ptr<osg::Texture2D> _bandTexture;
79+
bool _valid;
80+
81+
static const int BAND_TEX_WIDTH = 4096;
82+
};
83+
}
84+
85+
#endif // OSGEARTH_SLUG_FONT_H

0 commit comments

Comments
 (0)