@ -26,22 +26,39 @@
# include "Renderer.h"
# include <Corrade/Containers/StridedArrayView.h>
# include <Corrade/Containers/EnumSet.hpp>
# include <Corrade/Containers/GrowableArray.h>
# include <Corrade/Containers/Optional.h>
# include <Corrade/Containers/StridedArrayView.h>
# include <Corrade/Containers/StringView.h>
# include <Corrade/Containers/Triple.h>
# include "Magnum/Math/Functions.h"
# include "Magnum/Math/FunctionsBatch.h"
# include "Magnum/Math/Range.h"
# include "Magnum/Text/AbstractFont.h"
# include "Magnum/Text/AbstractGlyphCache.h"
# include "Magnum/Text/AbstractShaper.h"
# include "Magnum/Text/Alignment.h"
# include "Magnum/Text/Direction.h"
# include "Magnum/Text/Implementation/rendererState.h"
/* Somehow on GCC 4.8 to 7 the {} passed as a default argument for
ArrayView < const FeatureRange > causes " error: elements of array 'const class
Magnum : : Text : : FeatureRange [ 0 ] ' have incomplete type " . GCC 9 is fine, no
idea about version 8 , but including the definition for it as well to be
safe . Similar problem happens with MSVC STL , where the initializer_list is
implemented as a ( begin , end ) pair and size ( ) is a difference of those two
pointers . Which needs to know the type size to calculate the actual element
count . */
# if (defined(CORRADE_TARGET_GCC) && __GNUC__ <= 8) || defined(CORRADE_TARGET_DINKUMWARE)
# include "Magnum/Text/Feature.h"
# endif
# ifdef MAGNUM_TARGET_GL
# include <string>
# include <tuple>
# include <vector>
# include <Corrade/Containers/Array.h>
# include <Corrade/Containers/ArrayViewStl.h> /** @todo remove once Renderer is STL-free */
# include <Corrade/Containers/StringStl.h> /** @todo remove once Renderer is STL-free */
@ -50,11 +67,711 @@
# include "Magnum/GL/Extensions.h"
# include "Magnum/GL/Mesh.h"
# include "Magnum/Shaders/GenericGL.h"
# include "Magnum/Text/AbstractShaper.h"
# endif
namespace Magnum { namespace Text {
Debug & operator < < ( Debug & debug , const RendererCoreFlag value ) {
debug < < " Text::RendererCoreFlag " < < Debug : : nospace ;
switch ( value ) {
/* LCOV_EXCL_START */
# define _c(v) case RendererCoreFlag::v: return debug << "::" #v;
_c ( GlyphClusters )
# undef _c
/* LCOV_EXCL_STOP */
}
return debug < < " ( " < < Debug : : nospace < < Debug : : hex < < UnsignedByte ( value ) < < Debug : : nospace < < " ) " ;
}
Debug & operator < < ( Debug & debug , const RendererCoreFlags value ) {
return Containers : : enumSetDebugOutput ( debug , value , " Text::RendererCoreFlags{} " , {
RendererCoreFlag : : GlyphClusters
} ) ;
}
namespace {
struct Glyph {
Vector2 position ;
UnsignedInt id ;
} ;
struct GlyphCluster {
Vector2 position ;
UnsignedInt id ;
UnsignedInt cluster ;
} ;
auto defaultGlyphAllocatorFor ( const RendererCoreFlags flags ) - > void ( * ) ( void * , UnsignedInt , Containers : : StridedArrayView1D < Vector2 > & , Containers : : StridedArrayView1D < UnsignedInt > & , Containers : : StridedArrayView1D < UnsignedInt > * , Containers : : StridedArrayView1D < Vector2 > & ) {
if ( flags > = RendererCoreFlag : : GlyphClusters )
return [ ] ( void * const state , const UnsignedInt glyphCount , Containers : : StridedArrayView1D < Vector2 > & glyphPositions , Containers : : StridedArrayView1D < UnsignedInt > & glyphIds , Containers : : StridedArrayView1D < UnsignedInt > * const glyphClusters , Containers : : StridedArrayView1D < Vector2 > & glyphAdvances ) {
Containers : : Array < char > & data = * static_cast < Containers : : Array < char > * > ( state ) ;
/* The array may not be fully used yet, or it might have been reset
back to empty . Append only if the desired capacity is more than
what ' s there . */
const std : : size_t existingSize = glyphPositions . size ( ) ;
const std : : size_t desiredByteSize = ( existingSize + glyphCount ) * sizeof ( GlyphCluster ) ;
if ( desiredByteSize > data . size ( ) ) {
/* Using arrayAppend() as it reallocates with a growth
strategy , arrayResize ( ) would take the size literally */
arrayAppend ( data , NoInit , desiredByteSize - data . size ( ) ) ;
}
/* The new capacity is the actual array size, not just
` desiredByteSize ` . If the array got enlarged by exactly the
requested ` size ` , it ' ll be the same as ` desiredByteSize ` . If the
array was larger , such as after a clear ( ) , the capacity will
again use up all of it . */
const Containers : : StridedArrayView1D < GlyphCluster > glyphs = Containers : : arrayCast < GlyphCluster > ( data ) ;
glyphPositions = glyphs . slice ( & GlyphCluster : : position ) ;
glyphIds = glyphs . slice ( & GlyphCluster : : id ) ;
* glyphClusters = glyphs . slice ( & GlyphCluster : : cluster ) ;
/* As IDs and clusters are right after each other and have the same
size as a Vector2 , we can abuse them to store advances . Those
are guaranteed to be always filled only once advances are no
longer needed , so that ' s fine - - but we need to ensure that we
point to the new memory , not to the existing , where it ' d
overwrite existing IDs and clusters . */
glyphAdvances = Containers : : arrayCast < Vector2 > ( glyphs . slice ( & GlyphCluster : : id ) . exceptPrefix ( existingSize ) ) ;
} ;
else
return [ ] ( void * const state , const UnsignedInt glyphCount , Containers : : StridedArrayView1D < Vector2 > & glyphPositions , Containers : : StridedArrayView1D < UnsignedInt > & glyphIds , Containers : : StridedArrayView1D < UnsignedInt > * , Containers : : StridedArrayView1D < Vector2 > & glyphAdvances ) {
Containers : : Array < char > & data = * static_cast < Containers : : Array < char > * > ( state ) ;
/* The array may not be fully used yet, or it might have been reset
back to empty . Append only if the desired capacity is more than
what ' s there . Unlike above we don ' t have any place to alias
advances with , so append them at the end . */
const std : : size_t desiredByteSize = glyphPositions . size ( ) * sizeof ( Glyph ) + glyphCount * ( sizeof ( Glyph ) + sizeof ( Vector2 ) ) ;
if ( desiredByteSize > data . size ( ) ) {
/* Using arrayAppend() as it reallocates with a growth
strategy , arrayResize ( ) would take the size literally */
arrayAppend ( data , NoInit , desiredByteSize - data . size ( ) ) ;
}
/* Calculate the new capacity from the actual array size. Compared
to the above , we need to make sure the unused space at the end
is correctly divided between the Glyph and the Vector2 for
advances . */
const std : : size_t newCapacity = glyphPositions . size ( ) + ( data . size ( ) - glyphPositions . size ( ) * sizeof ( Glyph ) ) / ( sizeof ( Glyph ) + sizeof ( Vector2 ) ) ;
const std : : size_t newSize = newCapacity - glyphPositions . size ( ) ;
const Containers : : StridedArrayView1D < Glyph > glyphs = Containers : : arrayCast < Glyph > ( data . prefix ( newCapacity * sizeof ( Glyph ) ) ) ;
glyphPositions = glyphs . slice ( & Glyph : : position ) ;
glyphIds = glyphs . slice ( & Glyph : : id ) ;
/* Don't take just the suffix for advances as the size may not be
divisible by sizeof ( Vector2 ) , especially after clear ( ) */
glyphAdvances = Containers : : arrayCast < Vector2 > ( data . sliceSize ( newCapacity * sizeof ( Glyph ) , newSize * sizeof ( Vector2 ) ) ) ;
} ;
}
struct TextRun {
Float scale ;
UnsignedInt end ;
} ;
void defaultRunAllocator ( void * const state , const UnsignedInt runCount , Containers : : StridedArrayView1D < Float > & runScales , Containers : : StridedArrayView1D < UnsignedInt > & runEnds ) {
Containers : : Array < char > & data = * static_cast < Containers : : Array < char > * > ( state ) ;
const std : : size_t newSize = runScales . size ( ) + runCount ;
const std : : size_t desiredByteSize = newSize * sizeof ( TextRun ) ;
if ( desiredByteSize > data . size ( ) ) {
/* Using arrayAppend() as it reallocates with a growth strategy,
arrayResize ( ) would take the size literally */
arrayAppend ( data , NoInit , desiredByteSize - data . size ( ) ) ;
}
const Containers : : StridedArrayView1D < TextRun > runs = Containers : : arrayCast < TextRun > ( data ) ;
runScales = runs . slice ( & TextRun : : scale ) ;
runEnds = runs . slice ( & TextRun : : end ) ;
}
}
RendererCore : : AllocatorState : : AllocatorState ( const AbstractGlyphCache & glyphCache , void ( * glyphAllocator ) ( void * , UnsignedInt , Containers : : StridedArrayView1D < Vector2 > & , Containers : : StridedArrayView1D < UnsignedInt > & , Containers : : StridedArrayView1D < UnsignedInt > * , Containers : : StridedArrayView1D < Vector2 > & ) , void * glyphAllocatorState , void ( * const runAllocator ) ( void * , UnsignedInt , Containers : : StridedArrayView1D < Float > & , Containers : : StridedArrayView1D < UnsignedInt > & ) , void * runAllocatorState , RendererCoreFlags flags ) : State { glyphCache ,
glyphAllocator ? glyphAllocator : defaultGlyphAllocatorFor ( flags ) ,
glyphAllocator ? glyphAllocatorState : & glyphData ,
runAllocator ? runAllocator : defaultRunAllocator ,
runAllocator ? runAllocatorState : & runData , flags } { }
RendererCore : : RendererCore ( const AbstractGlyphCache & glyphCache , void ( * glyphAllocator ) ( void * , UnsignedInt , Containers : : StridedArrayView1D < Vector2 > & , Containers : : StridedArrayView1D < UnsignedInt > & , Containers : : StridedArrayView1D < UnsignedInt > * , Containers : : StridedArrayView1D < Vector2 > & ) , void * glyphAllocatorState , void ( * runAllocator ) ( void * , UnsignedInt , Containers : : StridedArrayView1D < Float > & , Containers : : StridedArrayView1D < UnsignedInt > & ) , void * runAllocatorState , const RendererCoreFlags flags ) : _state {
/* If either allocator is left at the default, create a state that includes
the data arrays for use by the internal allocators . If both are
user - specified , there ' s no need to have them as they ' re unused . */
glyphAllocator & & runAllocator ?
Containers : : pointer < State > ( glyphCache , glyphAllocator , glyphAllocatorState , runAllocator , runAllocatorState , flags ) :
Containers : : pointer < AllocatorState > ( glyphCache , glyphAllocator , glyphAllocatorState , runAllocator , runAllocatorState , flags ) } { }
RendererCore : : RendererCore ( NoCreateT ) noexcept { }
RendererCore : : RendererCore ( RendererCore & & ) noexcept = default ;
RendererCore : : ~ RendererCore ( ) = default ;
RendererCore & RendererCore : : operator = ( RendererCore & & ) noexcept = default ;
const AbstractGlyphCache & RendererCore : : glyphCache ( ) const {
return _state - > glyphCache ;
}
RendererCoreFlags RendererCore : : flags ( ) const {
return _state - > flags ;
}
UnsignedInt RendererCore : : glyphCount ( ) const {
return _state - > glyphCount ;
}
UnsignedInt RendererCore : : glyphCapacity ( ) const {
return _state - > glyphPositions . size ( ) ;
}
UnsignedInt RendererCore : : runCount ( ) const {
return _state - > runCount ;
}
UnsignedInt RendererCore : : runCapacity ( ) const {
return _state - > runScales . size ( ) ;
}
bool RendererCore : : isRendering ( ) const {
return _state - > rendering ;
}
UnsignedInt RendererCore : : renderingGlyphCount ( ) const {
return _state - > renderingGlyphCount ;
}
UnsignedInt RendererCore : : renderingRunCount ( ) const {
return _state - > renderingRunCount ;
}
Vector2 RendererCore : : cursor ( ) const {
return _state - > cursor ;
}
RendererCore & RendererCore : : setCursor ( const Vector2 & cursor ) {
State & state = * _state ;
CORRADE_ASSERT ( ! state . rendering ,
" Text::RendererCore::setCursor(): rendering in progress " , * this ) ;
state . cursor = cursor ;
return * this ;
}
Alignment RendererCore : : alignment ( ) const {
return _state - > alignment ;
}
RendererCore & RendererCore : : setAlignment ( const Alignment alignment ) {
State & state = * _state ;
CORRADE_ASSERT ( ! state . rendering ,
" Text::RendererCore::setAlignment(): rendering in progress " , * this ) ;
state . alignment = alignment ;
return * this ;
}
Float RendererCore : : lineAdvance ( ) const {
return _state - > lineAdvance ;
}
RendererCore & RendererCore : : setLineAdvance ( const Float advance ) {
State & state = * _state ;
CORRADE_ASSERT ( ! state . rendering ,
" Text::RendererCore::setLineAdvance(): rendering in progress " , * this ) ;
state . lineAdvance = advance ;
return * this ;
}
LayoutDirection RendererCore : : layoutDirection ( ) const {
return _state - > layoutDirection ;
}
RendererCore & RendererCore : : setLayoutDirection ( const LayoutDirection direction ) {
State & state = * _state ;
CORRADE_ASSERT ( ! state . rendering ,
" Text::RendererCore::setLayoutDirection(): rendering in progress " , * this ) ;
CORRADE_ASSERT ( direction = = LayoutDirection : : HorizontalTopToBottom ,
" Text::RendererCore::setLayoutDirection(): only " < < LayoutDirection : : HorizontalTopToBottom < < " is supported right now, got " < < direction , * this ) ;
state . layoutDirection = direction ;
return * this ;
}
Containers : : StridedArrayView1D < const Vector2 > RendererCore : : glyphPositions ( ) const {
const State & state = * _state ;
return state . glyphPositions . prefix ( state . glyphCount ) ;
}
Containers : : StridedArrayView1D < const UnsignedInt > RendererCore : : glyphIds ( ) const {
const State & state = * _state ;
return state . glyphIds . prefix ( state . glyphCount ) ;
}
Containers : : StridedArrayView1D < const UnsignedInt > RendererCore : : glyphClusters ( ) const {
const State & state = * _state ;
CORRADE_ASSERT ( state . flags & RendererCoreFlag : : GlyphClusters ,
" Text::RendererCore::glyphClusters(): glyph clusters not enabled " , { } ) ;
return state . glyphClusters . prefix ( state . glyphCount ) ;
}
Containers : : StridedArrayView1D < const Float > RendererCore : : runScales ( ) const {
const State & state = * _state ;
return state . runScales . prefix ( state . runCount ) ;
}
Containers : : StridedArrayView1D < const UnsignedInt > RendererCore : : runEnds ( ) const {
const State & state = * _state ;
return state . runEnds . prefix ( state . runCount ) ;
}
Range1Dui RendererCore : : glyphsForRuns ( const Range1Dui & runRange ) const {
const State & state = * _state ;
CORRADE_ASSERT ( runRange . min ( ) < = state . renderingRunCount & &
runRange . max ( ) < = state . renderingRunCount ,
/* The Vector2ui is to avoid double {}s in the printed value */
/** @todo fix the printer itself, maybe? */
" Text::RendererCore::glyphsForRuns(): runs " < < Debug : : packed < < ( Vector2ui { runRange . min ( ) , runRange . max ( ) } ) < < " out of range for " < < state . renderingRunCount < < " runs " , { } ) ;
return { runRange . min ( ) ? state . runEnds [ runRange . min ( ) - 1 ] : 0 ,
runRange . max ( ) ? state . runEnds [ runRange . max ( ) - 1 ] : 0 } ;
}
void RendererCore : : allocateGlyphs (
# ifndef CORRADE_NO_ASSERT
const char * const messagePrefix ,
# endif
const UnsignedInt totalGlyphCount )
{
State & state = * _state ;
/* This function should only be called if we need more memory or from
clear ( ) with everything empty */
CORRADE_INTERNAL_DEBUG_ASSERT ( totalGlyphCount > state . glyphPositions . size ( ) | | ( state . glyphCount = = 0 & & state . renderingGlyphCount = = 0 & & totalGlyphCount = = 0 ) ) ;
/* Sliced copies of the views for the allocator to update. As this is
called from add ( ) , all glyph contents until ` state . renderingGlyphCount `
should be preserved , not just ` state . glyphCount ` . */
Containers : : StridedArrayView1D < Vector2 > glyphPositions =
state . glyphPositions . prefix ( state . renderingGlyphCount ) ;
Containers : : StridedArrayView1D < UnsignedInt > glyphIds =
state . glyphIds . prefix ( state . renderingGlyphCount ) ;
Containers : : StridedArrayView1D < UnsignedInt > glyphClusters = state . flags & RendererCoreFlag : : GlyphClusters ?
state . glyphClusters . prefix ( state . renderingGlyphCount ) : nullptr ;
/* Advances are just temporary and thus we don't need to preserve existing
contents . But the allocator may still want to know where it ' s coming
from so give it a non - null empty view if possible */
Containers : : StridedArrayView1D < Vector2 > glyphAdvances =
state . glyphAdvances . prefix ( 0 ) ;
/* While this function gets total glyph count, the allocator gets glyph
count to grow by instead */
state . glyphAllocator ( state . glyphAllocatorState ,
totalGlyphCount - state . renderingGlyphCount ,
glyphPositions ,
glyphIds ,
state . flags & RendererCoreFlag : : GlyphClusters ? & glyphClusters : nullptr ,
glyphAdvances ) ;
/* Take the smallest size of all as the new capacity. Again the advances
don ' t preserve the previous contents so they ' re just the new size . Add
the existing glyph count to that instead of subtracting glyph count from
all others to avoid an underflow . */
std : : size_t minCapacity = Math : : min ( {
glyphPositions . size ( ) ,
glyphIds . size ( ) ,
state . renderingGlyphCount + glyphAdvances . size ( ) } ) ;
/* These assertions are present even for the builtin allocator but
shouldn ' t fire . If they do , the whole thing is broken , but it ' s better
to blow up with a nice message than with some strange OOB error later */
if ( state . flags & RendererCoreFlag : : GlyphClusters ) {
minCapacity = Math : : min ( minCapacity , glyphClusters . size ( ) ) ;
CORRADE_ASSERT ( minCapacity > = totalGlyphCount ,
messagePrefix < < " expected allocated glyph positions, IDs and clusters to have at least " < < totalGlyphCount < < " elements and advances " < < totalGlyphCount - state . renderingGlyphCount < < " but got " < < glyphPositions . size ( ) < < Debug : : nospace < < " , " < < glyphIds . size ( ) < < Debug : : nospace < < " , " < < glyphClusters . size ( ) < < " and " < < glyphAdvances . size ( ) , ) ;
} else {
CORRADE_ASSERT ( minCapacity > = totalGlyphCount ,
messagePrefix < < " expected allocated glyph positions and IDs to have at least " < < totalGlyphCount < < " elements and advances " < < totalGlyphCount - state . renderingGlyphCount < < " but got " < < glyphPositions . size ( ) < < Debug : : nospace < < " , " < < glyphIds . size ( ) < < " and " < < glyphAdvances . size ( ) , ) ;
}
/* Keep just the minimal size for all, which is the new capacity */
state . glyphPositions = glyphPositions . prefix ( minCapacity ) ;
state . glyphIds = glyphIds . prefix ( minCapacity ) ;
if ( state . flags & RendererCoreFlag : : GlyphClusters )
state . glyphClusters = glyphClusters . prefix ( minCapacity ) ;
/* Again the advances are just the size alone, not the full capacity */
state . glyphAdvances = glyphAdvances . prefix ( minCapacity - state . renderingGlyphCount ) ;
}
void RendererCore : : allocateRuns (
# ifndef CORRADE_NO_ASSERT
const char * const messagePrefix ,
# endif
const UnsignedInt totalRunCount )
{
State & state = * _state ;
/* This function should only be called if we need more memory or from
clear ( ) with everything empty */
CORRADE_INTERNAL_DEBUG_ASSERT ( totalRunCount > state . runScales . size ( ) | | ( state . runCount = = 0 & & state . renderingRunCount = = 0 & & totalRunCount = = 0 ) ) ;
/* Sliced copies of the views for the allocator to update. As this is
called from add ( ) , all run contents until ` state . renderingRunCount `
should be preserved , not just ` state . runCount ` . */
Containers : : StridedArrayView1D < Float > runScales =
state . runScales . prefix ( state . renderingRunCount ) ;
Containers : : StridedArrayView1D < UnsignedInt > runEnds =
state . runEnds . prefix ( state . renderingRunCount ) ;
/* While this function gets total run count, the allocator gets run count
to grow by instead */
state . runAllocator ( state . runAllocatorState ,
totalRunCount - state . renderingRunCount ,
runScales ,
runEnds ) ;
/* Take the smallest size of all as the new capacity */
const std : : size_t minCapacity = Math : : min (
runScales . size ( ) ,
runEnds . size ( ) ) ;
/* These assertions are present even for the builtin allocator but
shouldn ' t fire . If they do , the whole thing is broken , but it ' s better
to blow up with a nice message than with some strange OOB error later */
CORRADE_ASSERT ( minCapacity > = totalRunCount ,
messagePrefix < < " expected allocated run scales and ends to have at least " < < totalRunCount < < " elements but got " < < runScales . size ( ) < < " and " < < runEnds . size ( ) , ) ;
/* Keep just the minimal size for all, which is the new capacity */
state . runScales = runScales . prefix ( minCapacity ) ;
state . runEnds = runEnds . prefix ( minCapacity ) ;
}
RendererCore & RendererCore : : reserve ( const UnsignedInt glyphCapacity , const UnsignedInt runCapacity ) {
State & state = * _state ;
if ( state . glyphPositions . size ( ) < glyphCapacity )
allocateGlyphs (
# ifndef CORRADE_NO_ASSERT
" Text::RendererCore::reserve(): " ,
# endif
glyphCapacity ) ;
if ( state . runScales . size ( ) < runCapacity )
allocateRuns (
# ifndef CORRADE_NO_ASSERT
" Text::RendererCore::reserve(): " ,
# endif
runCapacity ) ;
return * this ;
}
RendererCore & RendererCore : : clear ( ) {
State & state = * _state ;
/* Reset the glyph / run count to 0 and call the allocators, requesting 0
glyphs and runs as well . It may make use of that to refresh itself . */
state . glyphCount = 0 ;
state . renderingGlyphCount = 0 ;
state . runCount = 0 ;
state . renderingRunCount = 0 ;
allocateGlyphs (
# ifndef CORRADE_NO_ASSERT
" " , /* Asserts won't happen as returned sizes will be always >= 0 */
# endif
0 ) ;
allocateRuns (
# ifndef CORRADE_NO_ASSERT
" " , /* Asserts won't happen as returned sizes will be always >= 0 */
# endif
0 ) ;
/* All in-progress rendering, both for the block and for the line, should
be cleaned up */
state . rendering = false ;
state . resolvedAlignment = { } ;
state . renderingLineStart = { } ;
state . renderingLineCursor = { } ;
state . renderingLineAdvance = { } ;
state . blockRunBegin = { } ;
state . blockRectangle = { } ;
state . lineGlyphBegin = { } ;
state . lineRectangle = { } ;
return * this ;
}
void RendererCore : : resetInternal ( ) {
State & state = * _state ;
/* Keep in sync with the initializers in the State struct */
state . alignment = Alignment : : MiddleCenter ;
state . layoutDirection = LayoutDirection : : HorizontalTopToBottom ;
state . cursor = { } ;
state . lineAdvance = { } ;
}
RendererCore & RendererCore : : reset ( ) {
clear ( ) ;
/* Reset also all other settable state to defaults */
resetInternal ( ) ;
return * this ;
}
void RendererCore : : alignAndFinishLine ( ) {
State & state = * _state ;
CORRADE_INTERNAL_DEBUG_ASSERT ( state . lineGlyphBegin ! = state . renderingGlyphCount & & state . resolvedAlignment ) ;
const Range2D alignedLineRectangle = alignRenderedLine (
state . lineRectangle ,
state . layoutDirection ,
* state . resolvedAlignment ,
state . glyphPositions . slice ( state . lineGlyphBegin , state . renderingGlyphCount ) ) ;
/* Extend the block rectangle with final line bounds */
state . blockRectangle = Math : : join ( state . blockRectangle , alignedLineRectangle ) ;
/* New line starts after all existing glyphs and is empty */
state . lineGlyphBegin = state . renderingGlyphCount ;
state . lineRectangle = { } ;
}
RendererCore & RendererCore : : add ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const UnsignedInt begin , const UnsignedInt end , const Containers : : ArrayView < const FeatureRange > features ) {
State & state = * _state ;
/* Mark as rendering in progress if not already */
state . rendering = true ;
/* Query ID of shaper font in the cache for performing glyph ID mapping */
const Containers : : Optional < UnsignedInt > glyphCacheFontId = state . glyphCache . findFont ( shaper . font ( ) ) ;
CORRADE_ASSERT ( glyphCacheFontId ,
" Text::RendererCore::add(): shaper font not found among " < < state . glyphCache . fontCount ( ) < < " fonts in associated glyph cache " , * this ) ;
/* Scaling factor, line advance taken from the font if not specified
externally . Currently assuming just horizontal layout direction , so the
line advance is vertical . */
/** @todo update once allowing other directions */
const AbstractFont & font = shaper . font ( ) ;
const Float scale = size / font . size ( ) ;
CORRADE_INTERNAL_DEBUG_ASSERT ( state . layoutDirection = = LayoutDirection : : HorizontalTopToBottom ) ;
if ( state . renderingLineAdvance = = Vector2 { } ) {
if ( state . lineAdvance ! = 0.0f )
state . renderingLineAdvance = Vector2 : : yAxis ( - state . lineAdvance ) ;
else
state . renderingLineAdvance = Vector2 : : yAxis ( - font . lineHeight ( ) * scale ) ;
}
Containers : : StringView line = text . slice ( begin , end ) ;
while ( line ) {
const Containers : : StringView lineEnd = line . findOr ( ' \n ' , line . end ( ) ) ;
/* Comparing like this to avoid an unnecessary memcmp(). If we reach
the end of the input text , it ' s * not * an end of the line , because
the next add ( ) call may continue with it . */
const bool isEndOfLine = lineEnd . size ( ) = = 1 & & lineEnd [ 0 ] = = ' \n ' ;
/* If the line is not empty and produced some glyphs, render them */
if ( const UnsignedInt glyphCount = lineEnd . begin ( ) ! = line . begin ( ) ? shaper . shape ( text , line . begin ( ) - text . begin ( ) , lineEnd . begin ( ) - text . begin ( ) , features ) : 0 ) {
/* If we need to add more glyphs than what's in the capacity,
allocate more */
if ( state . glyphPositions . size ( ) < state . renderingGlyphCount + glyphCount ) {
allocateGlyphs (
# ifndef CORRADE_NO_ASSERT
" Text::RendererCore::add(): " ,
# endif
state . renderingGlyphCount + glyphCount ) ;
# ifdef CORRADE_GRACEFUL_ASSERT
/* For testing only -- if allocation failed, bail */
if ( state . glyphPositions . size ( ) < state . renderingGlyphCount + glyphCount )
return * this ;
# endif
}
const Containers : : StridedArrayView1D < Vector2 > glyphOffsetsPositions = state . glyphPositions . sliceSize ( state . renderingGlyphCount , glyphCount ) ;
/* The glyph advance array may be aliasing IDs and clusters. Pick
only a suffix of the same size as the remaining capacity - - that
memory is guaranteed to be unused yet . */
const std : : size_t remainingCapacity = state . glyphPositions . size ( ) - state . renderingGlyphCount ;
const Containers : : StridedArrayView1D < Vector2 > glyphAdvances = state . glyphAdvances . sliceSize ( state . glyphAdvances . size ( ) - remainingCapacity , glyphCount ) ;
shaper . glyphOffsetsAdvancesInto (
glyphOffsetsPositions ,
glyphAdvances ) ;
/* Render line glyph positions, aliasing the offsets */
const Range2D rectangle = renderLineGlyphPositionsInto (
shaper . font ( ) ,
size ,
state . layoutDirection ,
glyphOffsetsPositions ,
glyphAdvances ,
state . renderingLineCursor ,
glyphOffsetsPositions ) ;
/* Retrieve the glyph IDs and clusters, convert the glyph IDs to
cache - global . Do it only after finalizing the positions so the
glyphAdvances array can alias the IDs . */
const Containers : : StridedArrayView1D < UnsignedInt > glyphIds = state . glyphIds . sliceSize ( state . renderingGlyphCount , glyphCount ) ;
shaper . glyphIdsInto ( glyphIds ) ;
state . glyphCache . glyphIdsInto ( * glyphCacheFontId , glyphIds , glyphIds ) ;
if ( state . flags & RendererCoreFlag : : GlyphClusters )
shaper . glyphClustersInto ( state . glyphClusters . sliceSize ( state . renderingGlyphCount , glyphCount ) ) ;
/* If we're aligning based on glyph bounds, calculate a rectangle
from scratch instead of using a rectangle based on advances and
font metrics . Join the resulting rectangle with one that ' s
maintained for the line so far . */
state . lineRectangle = Math : : join ( state . lineRectangle ,
( UnsignedByte ( state . alignment ) & Implementation : : AlignmentGlyphBounds ) ?
glyphQuadBounds ( state . glyphCache , scale , glyphOffsetsPositions , glyphIds ) :
rectangle ) ;
state . renderingGlyphCount + = glyphCount ;
}
/* If the alignment isn't resolved yet and the shaper detected any
usable direction ( or we ' re at the end of the line where we need
it ) , resolve it . If there ' s no usable direction detected yet , maybe
it will be next time . */
if ( ! state . resolvedAlignment ) {
/* In this case it may happen that we query direction on a shaper
for which shape ( ) wasn ' t called yet , for example if shaping a
text starting with \ n and the previous text shaping gave back
ShapeDirection : : Unspecified as well . In such case it likely
returns ShapeDirection : : Unspecified too . */
const ShapeDirection shapeDirection = shaper . direction ( ) ;
if ( shapeDirection ! = ShapeDirection : : Unspecified | | isEndOfLine )
state . resolvedAlignment = alignmentForDirection (
state . alignment ,
state . layoutDirection ,
shapeDirection ) ;
}
/* If a newline follows, wrap up the existing line. This can happen
independently of whether any glyphs were processed in this
iteration , as add ( ) can be called with a string that starts with a
\ n , for example . */
if ( isEndOfLine ) {
/* If there are any glyphs on the current line, either added right
above or being there from the previous add ( ) call , align them .
If alignment based on bounds is requested , calculate a special
rectangle for it . */
if ( state . lineGlyphBegin ! = state . renderingGlyphCount )
alignAndFinishLine ( ) ;
/* Move the cursor for the next line */
state . renderingLineStart + = state . renderingLineAdvance ;
state . renderingLineCursor = state . renderingLineStart ;
}
/* For the next iteration cut away everything that got processed,
including the \ n */
line = line . suffix ( lineEnd . end ( ) ) ;
}
/* Final alignment of the whole block happens in render() below */
/* Save the whole thing as a new run, if any glyphs were added at all.
Right now it ' s just a single run each time add ( ) is called , but
eventually it might get split by lines or by shaping direction . */
if ( ( state . renderingRunCount ? state . runEnds [ state . renderingRunCount - 1 ] : 0 ) < state . renderingGlyphCount ) {
if ( state . runScales . size ( ) < = state . renderingRunCount ) {
allocateRuns (
# ifndef CORRADE_NO_ASSERT
" Text::RendererCore::add(): " ,
# endif
state . renderingRunCount + 1 ) ;
# ifdef CORRADE_GRACEFUL_ASSERT
/* For testing only -- if allocation failed, bail */
if ( state . runScales . size ( ) < = state . renderingRunCount )
return * this ;
# endif
}
state . runScales [ state . renderingRunCount ] = scale ;
state . runEnds [ state . renderingRunCount ] = state . renderingGlyphCount ;
+ + state . renderingRunCount ;
}
return * this ;
}
RendererCore & RendererCore : : add ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const UnsignedInt begin , const UnsignedInt end ) {
return add ( shaper , size , text , begin , end , { } ) ;
}
RendererCore & RendererCore : : add ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const UnsignedInt begin , const UnsignedInt end , const std : : initializer_list < FeatureRange > features ) {
return add ( shaper , size , text , begin , end , Containers : : arrayView ( features ) ) ;
}
RendererCore & RendererCore : : add ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const Containers : : ArrayView < const FeatureRange > features ) {
return add ( shaper , size , text , 0 , text . size ( ) , features ) ;
}
RendererCore & RendererCore : : add ( AbstractShaper & shaper , const Float size , const Containers : : StringView text ) {
return add ( shaper , size , text , { } ) ;
}
RendererCore & RendererCore : : add ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const std : : initializer_list < FeatureRange > features ) {
return add ( shaper , size , text , Containers : : arrayView ( features ) ) ;
}
Containers : : Pair < Range2D , Range1Dui > RendererCore : : render ( ) {
State & state = * _state ;
/* If the alignment still isn't resolved at this point, it means it either
stayed at ShapeDirection : : Unspecified for all text added so far , or
there wasn ' t any text actually added . Go with whatever
alignmentForDirection ( ) picks , then . Also , state . resolvedAlignment is
going to get reset right at the end , but let ' s just write there
temporarily , the logic is easier that way . */
if ( ! state . resolvedAlignment )
state . resolvedAlignment = alignmentForDirection (
state . alignment ,
state . layoutDirection ,
ShapeDirection : : Unspecified ) ;
/* Align the last unfinished line. In most cases there will be, unless the
last text passed to add ( ) was ending with a \ n . */
if ( state . lineGlyphBegin ! = state . renderingGlyphCount )
alignAndFinishLine ( ) ;
/* Align the block. Now it's respecting the alignment relative to the
origin , move everything relative to the actual desired cursor . Could
probably do that in the alignRendered * ( ) by passing a specially crafted
rect that ' s shifted by the cursor , but that ' d become a testing nightmare
with vertical text or when per - line advance is implemented . So just do
that after . */
const Containers : : StridedArrayView1D < Vector2 > blockGlyphPositions = state . glyphPositions . slice ( state . blockRunBegin ? state . runEnds [ state . blockRunBegin - 1 ] : 0 , state . renderingRunCount ? state . runEnds [ state . renderingRunCount - 1 ] : 0 ) ;
const Range2D alignedBlockRectangle = alignRenderedBlock (
state . blockRectangle ,
state . layoutDirection ,
* state . resolvedAlignment ,
blockGlyphPositions ) ;
for ( Vector2 & i : blockGlyphPositions )
i + = state . cursor ;
/* Reset all block-related state, marking the renderer as not in progress
anymore . Line - related state should be reset after the alignLine ( ) above
already . */
const UnsignedInt blockRunBegin = state . blockRunBegin ;
state . rendering = false ;
state . resolvedAlignment = { } ;
state . renderingLineStart = { } ;
state . renderingLineCursor = { } ;
state . renderingLineAdvance = { } ;
CORRADE_INTERNAL_DEBUG_ASSERT ( state . lineGlyphBegin = = state . renderingGlyphCount & &
state . lineRectangle = = Range2D { } ) ;
state . glyphCount = state . renderingGlyphCount ;
state . runCount = state . renderingRunCount ;
state . blockRunBegin = state . runCount ;
state . blockRectangle = { } ;
return { alignedBlockRectangle . translated ( state . cursor ) , { blockRunBegin , state . runCount } } ;
}
Containers : : Pair < Range2D , Range1Dui > RendererCore : : render ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const Containers : : ArrayView < const FeatureRange > features ) {
add ( shaper , size , text , features ) ;
return render ( ) ;
}
Containers : : Pair < Range2D , Range1Dui > RendererCore : : render ( AbstractShaper & shaper , const Float size , const Containers : : StringView text ) {
return render ( shaper , size , text , { } ) ;
}
Containers : : Pair < Range2D , Range1Dui > RendererCore : : render ( AbstractShaper & shaper , const Float size , const Containers : : StringView text , const std : : initializer_list < FeatureRange > features ) {
return render ( shaper , size , text , Containers : : arrayView ( features ) ) ;
}
Range2D renderLineGlyphPositionsInto ( const AbstractFont & font , const Float size , const LayoutDirection direction , const Containers : : StridedArrayView1D < const Vector2 > & glyphOffsets , const Containers : : StridedArrayView1D < const Vector2 > & glyphAdvances , Vector2 & cursor , const Containers : : StridedArrayView1D < Vector2 > & glyphPositions ) {
CORRADE_ASSERT ( glyphAdvances . size ( ) = = glyphOffsets . size ( ) & &
glyphPositions . size ( ) = = glyphOffsets . size ( ) ,