/*
This file is part of Magnum .
Copyright © 2010 , 2011 , 2012 , 2013 , 2014 , 2015 , 2016 , 2017 , 2018 , 2019 ,
2020 , 2021 , 2022 , 2023 , 2024 , 2025
Vladimír Vondruš < mosra @ centrum . cz >
Permission is hereby granted , free of charge , to any person obtaining a
copy of this software and associated documentation files ( the " Software " ) ,
to deal in the Software without restriction , including without limitation
the rights to use , copy , modify , merge , publish , distribute , sublicense ,
and / or sell copies of the Software , and to permit persons to whom the
Software is furnished to do so , subject to the following conditions :
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING
FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE .
*/
# include <pybind11/pybind11.h>
# include <Corrade/Containers/StringStl.h> /** @todo drop once we have our string casters */
# include <Magnum/ImageView.h>
# include <Magnum/GL/Buffer.h>
# include <Magnum/GL/Mesh.h>
# include <Magnum/GL/Texture.h>
# include <Magnum/Text/AbstractFont.h>
# include <Magnum/Text/DistanceFieldGlyphCacheGL.h>
# include <Magnum/Text/Renderer.h>
# include "corrade/pluginmanager.h"
# include "magnum/bootstrap.h"
# ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we need to convert
backslashes to forward slashes as all Corrade and Magnum APIs expect
forward */
# include <Corrade/Utility/Path.h>
# endif
namespace magnum {
namespace {
/* For some reason having ...Args as the second (and not last) template
argument does not work . So I ' m listing all variants used more than once . */
template < class R , R ( Text : : AbstractFont : : * f ) ( ) const > R checkOpened ( Text : : AbstractFont & self ) {
if ( ! self . isOpened ( ) ) {
PyErr_SetString ( PyExc_AssertionError , " no file opened " ) ;
throw py : : error_already_set { } ;
}
return ( self . * f ) ( ) ;
}
template < class R , class Arg1 , R ( Text : : AbstractFont : : * f ) ( Arg1 ) > R checkOpened ( Text : : AbstractFont & self , Arg1 arg1 ) {
if ( ! self . isOpened ( ) ) {
PyErr_SetString ( PyExc_AssertionError , " no file opened " ) ;
throw py : : error_already_set { } ;
}
return ( self . * f ) ( arg1 ) ;
}
template < class R , R ( Text : : AbstractFont : : * f ) ( UnsignedInt ) > R checkOpenedBounds ( Text : : AbstractFont & self , UnsignedInt glyph ) {
if ( ! self . isOpened ( ) ) {
PyErr_SetString ( PyExc_AssertionError , " no file opened " ) ;
throw py : : error_already_set { } ;
}
if ( glyph > = self . glyphCount ( ) ) {
PyErr_SetNone ( PyExc_IndexError ) ;
throw py : : error_already_set { } ;
}
return ( self . * f ) ( glyph ) ;
}
}
void text ( py : : module_ & m ) {
m . doc ( ) = " Text rendering " ;
/* AbstractFont depends on this */
py : : module_ : : import ( " corrade.pluginmanager " ) ;
# ifndef MAGNUM_BUILD_STATIC
/* These are a part of the same module in the static build, no need to
import ( also can ' t import because there it ' s _magnum . * ) */
py : : module_ : : import ( " magnum.gl " ) ;
# endif
/* Glyph caches */
py : : class_ < Text : : AbstractGlyphCache > { m , " AbstractGlyphCache " , " Base for glyph caches " }
/** @todo features */
. def_property_readonly ( " format " , & Text : : AbstractGlyphCache : : format , " Glyph cache format " )
. def_property_readonly ( " processed_format " , & Text : : AbstractGlyphCache : : processedFormat , " Processed glyph cache format " )
. def_property_readonly ( " size " , & Text : : AbstractGlyphCache : : size , " Glyph cache texture size " )
. def_property_readonly ( " processed_size " , & Text : : AbstractGlyphCache : : processedSize , " Processed glyph cache texture size " )
. def_property_readonly ( " padding " , & Text : : AbstractGlyphCache : : padding , " Glyph padding " )
/** @todo font / glyph iteration and population */
/** @todo image, processedImage, setProcessedImage, once needed for
anything */
;
py : : class_ < Text : : GlyphCacheGL , Text : : AbstractGlyphCache > { m , " GlyphCacheGL " , " OpenGL implementation of a glyph cache " }
. def ( py : : init < PixelFormat , const Vector2i & , const Vector2i & > ( ) , " Constructor " , py : : arg ( " format " ) , py : : arg ( " size " ) , py : : arg ( " padding " ) = Vector2i { 1 } )
/* The default behavior when returning a reference seems to be that it
increfs the originating instance and decrefs it again after the
variable gets deleted . This is verified in test_text_gl . py to be
extra sure . */
. def_property_readonly ( " texture " , & Text : : GlyphCacheGL : : texture , " Cache texture " ) ;
py : : class_ < Text : : DistanceFieldGlyphCacheGL , Text : : GlyphCacheGL > { m , " DistanceFieldGlyphCacheGL " , " OpenGL glyph cache with distance field rendering " }
. def ( py : : init < const Vector2i & , const Vector2i & , UnsignedInt > ( ) , " Constructor " , py : : arg ( " size " ) , py : : arg ( " processed_size " ) , py : : arg ( " radius " ) ) ;
/* Font */
py : : class_ < Text : : AbstractFont , PluginManager : : PyPluginHolder < Text : : AbstractFont > , PluginManager : : AbstractPlugin > abstractFont { m , " AbstractFont " , " Interface for font plugins " } ;
abstractFont
/** @todo features */
. def_property_readonly ( " is_opened " , & Text : : AbstractFont : : isOpened , " Whether any file is opened " )
. def ( " open_data " , [ ] ( Text : : AbstractFont & self , Containers : : ArrayView < const char > data , Float size ) {
/** @todo log redirection -- but we'd need assertions to not be
part of that so when it dies , the user can still see why */
if ( self . openData ( data , size ) ) return ;
PyErr_SetString ( PyExc_RuntimeError , " opening data failed " ) ;
throw py : : error_already_set { } ;
} , " Open raw data " , py : : arg ( " data " ) , py : : arg ( " size " ) )
. def ( " open_file " , [ ] ( Text : : AbstractFont & self , const std : : string & filename , Float size ) {
/** @todo log redirection -- but we'd need assertions to not be
part of that so when it dies , the user can still see why */
if ( self . openFile (
# ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we
need to convert backslashes to forward slashes as all
Corrade and Magnum APIs expect forward */
Utility : : Path : : fromNativeSeparators ( filename )
# else
filename
# endif
, size ) ) return ;
PyErr_Format ( PyExc_RuntimeError , " opening %s failed " , filename . data ( ) ) ;
throw py : : error_already_set { } ;
} , " Open a file " , py : : arg ( " filename " ) , py : : arg ( " size " ) )
. def ( " close " , & Text : : AbstractFont : : close , " Close currently opened file " )
. def_property_readonly ( " size " , checkOpened < Float , & Text : : AbstractFont : : size > , " Font size " )
. def_property_readonly ( " ascent " , checkOpened < Float , & Text : : AbstractFont : : ascent > , " Font ascent " )
. def_property_readonly ( " descent " , checkOpened < Float , & Text : : AbstractFont : : descent > , " Font descent " )
. def_property_readonly ( " line_height " , checkOpened < Float , & Text : : AbstractFont : : lineHeight > , " Line height " )
. def_property_readonly ( " glyph_count " , checkOpened < UnsignedInt , & Text : : AbstractFont : : glyphCount > , " Total count of glyphs in the font " )
. def ( " glyph_id " , checkOpened < UnsignedInt , char32_t , & Text : : AbstractFont : : glyphId > , " Glyph ID for given character " , py : : arg ( " character " ) )
. def ( " glyph_size " , checkOpenedBounds < Vector2 , & Text : : AbstractFont : : glyphSize > , " Glyph size in pixels " , py : : arg ( " glyph " ) )
. def ( " glyph_advance " , checkOpenedBounds < Vector2 , & Text : : AbstractFont : : glyphAdvance > , " Glyph advance in pixels " , py : : arg ( " glyph " ) )
. def ( " fill_glyph_cache " , [ ] ( Text : : AbstractFont & self , Text : : AbstractGlyphCache & cache , const std : : string & characters ) {
if ( ! self . isOpened ( ) ) {
PyErr_SetString ( PyExc_AssertionError , " no file opened " ) ;
throw py : : error_already_set { } ;
}
return self . fillGlyphCache ( cache , characters ) ;
} , " Fill glyph cache with given character set " , py : : arg ( " cache " ) , py : : arg ( " characters " ) )
/** @todo createGlyphCache() */
/** @todo layout and AbstractLayouter, once needed for anything */
;
corrade : : plugin ( abstractFont ) ;
py : : class_ < PluginManager : : Manager < Text : : AbstractFont > , PluginManager : : AbstractManager > fontManager { m , " FontManager " , " Manager for font plugins " } ;
corrade : : manager ( fontManager ) ;
py : : enum_ < Text : : Alignment > { m , " Alignment " , " Text rendering alignment " }
. value ( " LINE_LEFT " , Text : : Alignment : : LineLeft )
. value ( " LINE_CENTER " , Text : : Alignment : : LineCenter )
. value ( " LINE_RIGHT " , Text : : Alignment : : LineRight )
. value ( " MIDDLE_LEFT " , Text : : Alignment : : MiddleLeft )
. value ( " MIDDLE_CENTER " , Text : : Alignment : : MiddleCenter )
. value ( " MIDDLE_RIGHT " , Text : : Alignment : : MiddleRight )
. value ( " TOP_LEFT " , Text : : Alignment : : TopLeft )
. value ( " TOP_CENTER " , Text : : Alignment : : TopCenter )
. value ( " TOP_RIGHT " , Text : : Alignment : : TopRight )
. value ( " LINE_CENTER_INTEGRAL " , Text : : Alignment : : LineCenterIntegral )
. value ( " MIDDLE_LEFT_INTEGRAL " , Text : : Alignment : : MiddleLeftIntegral )
. value ( " MIDDLE_CENTER_INTEGRAL " , Text : : Alignment : : MiddleCenterIntegral )
. value ( " MIDDLE_RIGHT_INTEGRAL " , Text : : Alignment : : MiddleRightIntegral ) ;
/** @todo any reason to expose a 3D renderer? it isn't any different
currently */
py : : class_ < Text : : Renderer2D > renderer2D { m , " Renderer2D " , " 2D text renderer " } ;
renderer2D
. def ( py : : init < Text : : AbstractFont & , const Text : : GlyphCacheGL & , Float , Text : : Alignment > ( ) , " Constructor " , py : : arg ( " font " ) , py : : arg ( " cache " ) , py : : arg ( " size " ) , py : : arg ( " alignment " ) = Text : : Alignment : : LineLeft )
. def_property_readonly ( " capacity " , & Text : : Renderer2D : : capacity , " Capacity for rendered glyphs " )
. def_property_readonly ( " rectangle " , & Text : : Renderer2D : : rectangle , " Rectangle spanning the rendered text " )
/** @todo are the buffers useful for anything? */
/* The default behavior when returning a reference seems to be that it
increfs the originating instance and decrefs it again after the
variable gets deleted . This is verified in test_text . py to be extra
sure . */
. def_property_readonly ( " mesh " , & Text : : Renderer2D : : mesh , " Mesh " )
. def ( " reserve " , & Text : : Renderer2D : : reserve , " Reserve capacity for renderered glyphs " , py : : arg ( " glyph_count " ) , py : : arg ( " vertex_buffer_usage " ) = GL : : BufferUsage : : StaticDraw , py : : arg ( " index_buffer_usage " ) = GL : : BufferUsage : : StaticDraw )
. def ( " render " , static_cast < void ( Text : : Renderer2D : : * ) ( const std : : string & ) > ( & Text : : Renderer2D : : render ) , " Render text " , py : : arg ( " text " ) ) ;
}
}
# ifndef MAGNUM_BUILD_STATIC
/* TODO: remove declaration when https://github.com/pybind/pybind11/pull/1863
is released */
extern " C " PYBIND11_EXPORT PyObject * PyInit_text ( ) ;
PYBIND11_MODULE ( text , m ) {
magnum : : text ( m ) ;
}
# endif