0030782: Visualization, Font_FTFont - use predefined fallback fonts for extended...
authorkgv <kgv@opencascade.com>
Sun, 16 Jun 2019 10:09:49 +0000 (13:09 +0300)
committerbugmaster <bugmaster@opencascade.com>
Wed, 19 Jun 2019 16:42:24 +0000 (19:42 +0300)
Font_FTFont now uses fallback fonts for characters from unsupported Unicode subsets,
managed by Font_FTFont::ToUseUnicodeSubsetFallback()
and Font_FontMgr::ToUseUnicodeSubsetFallback() option, enabled by default.
The fallback list includes common font families for Chinese, Korean and Japanese languages.

Font_FTFont::RenderGlyph() now supports FT_PIXEL_MODE_MONO input format used by some CJK fonts.
OpenGl_Font::createTexture() now limits single texture size to circa 4096 glyphs.

test/testgrid now expects test scripts being in UTF-8 encoding in sync with "DRAWEXE -f script.tcl".

AIS::InitFaceLength() - fixed usage of uninitialized result.

14 files changed:
src/AIS/AIS.cxx
src/DrawResources/TestCommands.tcl
src/Font/FILES
src/Font/Font_BRepFont.cxx
src/Font/Font_FTFont.cxx
src/Font/Font_FTFont.hxx
src/Font/Font_FontMgr.cxx
src/Font/Font_FontMgr.hxx
src/Font/Font_NameOfFont.hxx
src/Font/Font_UnicodeSubset.hxx [new file with mode: 0644]
src/OpenGl/OpenGl_Font.cxx
src/OpenGl/OpenGl_Font.hxx
src/ViewerTest/ViewerTest_ObjectCommands.cxx
tests/3rdparty/fonts/C2 [new file with mode: 0644]

index fa84613..d9129b0 100644 (file)
@@ -783,6 +783,7 @@ Standard_Boolean AIS::GetPlaneFromFace(const TopoDS_Face&            aFace,
   BRepAdaptor_Surface surf1( aFace );
   Handle( Adaptor3d_HSurface ) surf2;
   Standard_Boolean isOffset = Standard_False;
+  Offset = 0.0;
 
   if (surf1.GetType() == GeomAbs_OffsetSurface)
   {
@@ -801,7 +802,6 @@ Standard_Boolean AIS::GetPlaneFromFace(const TopoDS_Face&            aFace,
   {
     aPlane = surf2->Plane();
     aSurfType = AIS_KOS_Plane;
-    Offset = 0.;
     Result = Standard_True;
   }
 
@@ -817,7 +817,6 @@ Standard_Boolean AIS::GetPlaneFromFace(const TopoDS_Face&            aFace,
       gp_Pln thePlane( LinePos, LineDir ^ ExtrusionDir);
       aPlane = thePlane;
       aSurfType = AIS_KOS_Plane;
-      Offset = 0.;
       Result = Standard_True;
     }
   }
@@ -826,7 +825,6 @@ Standard_Boolean AIS::GetPlaneFromFace(const TopoDS_Face&            aFace,
   {
     aSurf = (Handle( Geom_OffsetSurface )::DownCast( aSurf ))->Surface();
     aPlane = (Handle( Geom_Plane )::DownCast( aSurf ))->Pln();
-    Offset = 0.0e0;
   }
   if (Result == Standard_False)
   {
@@ -839,7 +837,6 @@ Standard_Boolean AIS::GetPlaneFromFace(const TopoDS_Face&            aFace,
         TheType == STANDARD_TYPE(Geom_ToroidalSurface))
       {
         aSurf = (Handle( Geom_OffsetSurface )::DownCast( aSurf ))->Surface();
-        Offset = 0.0e0;
       }
       else
       {
@@ -899,20 +896,18 @@ gp_Pnt AIS::ProjectPointOnLine( const gp_Pnt & aPoint, const gp_Lin & aLine )
 //function : InitFaceLength
 //purpose  : 
 //=======================================================================
-void AIS::InitFaceLength (const TopoDS_Face& aFace,
-                          gp_Pln               & aPlane,
-                          Handle(Geom_Surface) & aSurface,
-                          AIS_KindOfSurface    & aSurfaceType,
-                          Standard_Real        & anOffset)
+void AIS::InitFaceLength (const TopoDS_Face& theFace,
+                          gp_Pln& thePlane,
+                          Handle(Geom_Surface)& theSurface,
+                          AIS_KindOfSurface& theSurfaceType,
+                          Standard_Real& theOffset)
 {
-  AIS::GetPlaneFromFace( aFace, aPlane, aSurface, aSurfaceType, anOffset );
-  
-  if (Abs( anOffset ) > Precision::Confusion())
-    {
-      aSurface = new Geom_OffsetSurface( aSurface, anOffset );
-      anOffset = 0.0e0;
-    }
-  
+  if (AIS::GetPlaneFromFace (theFace, thePlane, theSurface, theSurfaceType, theOffset)
+   && Abs (theOffset) > Precision::Confusion())
+  {
+    theSurface = new Geom_OffsetSurface (theSurface, theOffset);
+    theOffset = 0.0e0;
+  }
 }
 
 //=======================================================================
index bd79782..d3626b1 100644 (file)
@@ -1298,23 +1298,23 @@ proc _run_test {scriptsdir group gridname casefile echo} {
         # execute test scripts 
         if { [file exists $scriptsdir/$group/begin] } {
             puts "Executing $scriptsdir/$group/begin..."; flush stdout
-            uplevel source $scriptsdir/$group/begin
+            uplevel source -encoding utf-8 $scriptsdir/$group/begin
         }
         if { [file exists $scriptsdir/$group/$gridname/begin] } {
             puts "Executing $scriptsdir/$group/$gridname/begin..."; flush stdout
-            uplevel source $scriptsdir/$group/$gridname/begin
+            uplevel source -encoding utf-8 $scriptsdir/$group/$gridname/begin
         }
 
         puts "Executing $casefile..."; flush stdout
-        uplevel source $casefile
+        uplevel source -encoding utf-8 $casefile
 
         if { [file exists $scriptsdir/$group/$gridname/end] } {
             puts "Executing $scriptsdir/$group/$gridname/end..."; flush stdout
-            uplevel source $scriptsdir/$group/$gridname/end
+            uplevel source -encoding utf-8 $scriptsdir/$group/$gridname/end
         }
         if { [file exists $scriptsdir/$group/end] } {
             puts "Executing $scriptsdir/$group/end..."; flush stdout
-            uplevel source $scriptsdir/$group/end
+            uplevel source -encoding utf-8 $scriptsdir/$group/end
         }
     } res] {
         puts "Tcl Exception: $res"
index bf614b5..c8440e4 100644 (file)
@@ -17,3 +17,4 @@ Font_SystemFont.cxx
 Font_SystemFont.hxx
 Font_TextFormatter.hxx
 Font_TextFormatter.cxx
+Font_UnicodeSubset.hxx
index 571ea13..2e2779a 100755 (executable)
@@ -407,7 +407,7 @@ Standard_Boolean Font_BRepFont::renderGlyph (const Standard_Utf32Char theChar,
 {
   theShape.Nullify();
   if (!loadGlyph (theChar)
-   || myFTFace->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
+   || myActiveFTFace->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
   {
     return Standard_False;
   }
@@ -416,8 +416,7 @@ Standard_Boolean Font_BRepFont::renderGlyph (const Standard_Utf32Char theChar,
     return !theShape.IsNull();
   }
 
-  FT_Outline& anOutline = myFTFace->glyph->outline;
-
+  const FT_Outline& anOutline = myActiveFTFace->glyph->outline;
   if (!anOutline.n_contours)
     return Standard_False;
 
index 22a1297..2fa393c 100755 (executable)
@@ -21,6 +21,8 @@
 #include <Message.hxx>
 #include <Message_Messenger.hxx>
 
+#include <algorithm>
+
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
@@ -33,9 +35,12 @@ IMPLEMENT_STANDARD_RTTIEXT(Font_FTFont,Standard_Transient)
 Font_FTFont::Font_FTFont (const Handle(Font_FTLibrary)& theFTLib)
 : myFTLib       (theFTLib),
   myFTFace      (NULL),
+  myActiveFTFace(NULL),
+  myFontAspect  (Font_FontAspect_Regular),
   myWidthScaling(1.0),
   myLoadFlags   (FT_LOAD_NO_HINTING | FT_LOAD_TARGET_NORMAL),
-  myUChar       (0U)
+  myUChar       (0U),
+  myToUseUnicodeSubsetFallback (Font_FontMgr::ToUseUnicodeSubsetFallback())
 {
   if (myFTLib.IsNull())
   {
@@ -66,6 +71,7 @@ void Font_FTFont::Release()
     FT_Done_Face (myFTFace);
     myFTFace = NULL;
   }
+  myActiveFTFace = NULL;
   myBuffer.Nullify();
 }
 
@@ -135,6 +141,7 @@ bool Font_FTFont::Init (const Handle(NCollection_Buffer)& theData,
 
     FT_Set_Transform (myFTFace, &aMat, 0);
   }
+  myActiveFTFace = myFTFace;
   return true;
 }
 
@@ -161,6 +168,7 @@ Handle(Font_FTFont) Font_FTFont::FindAndCreate (const TCollection_AsciiString& t
     Handle(Font_FTFont) aFont = new Font_FTFont();
     if (aFont->Init (aPath, aParams))
     {
+      aFont->myFontAspect = aFontAspect;
       return aFont;
     }
   }
@@ -177,16 +185,16 @@ bool Font_FTFont::FindAndInit (const TCollection_AsciiString& theFontName,
                                Font_StrictLevel theStrictLevel)
 {
   Font_FTFontParams aParams = theParams;
-  Font_FontAspect aFontAspect = theFontAspect;
+  myFontAspect = theFontAspect;
   Handle(Font_FontMgr) aFontMgr = Font_FontMgr::GetInstance();
-  if (Handle(Font_SystemFont) aRequestedFont = aFontMgr->FindFont (theFontName.ToCString(), theStrictLevel, aFontAspect))
+  if (Handle(Font_SystemFont) aRequestedFont = aFontMgr->FindFont (theFontName.ToCString(), theStrictLevel, myFontAspect))
   {
     if (aRequestedFont->IsSingleStrokeFont())
     {
       aParams.IsSingleStrokeFont = true;
     }
 
-    const TCollection_AsciiString& aPath = aRequestedFont->FontPathAny (aFontAspect, aParams.ToSynthesizeItalic);
+    const TCollection_AsciiString& aPath = aRequestedFont->FontPathAny (myFontAspect, aParams.ToSynthesizeItalic);
     return Init (aPath, aParams);
   }
   Release();
@@ -194,6 +202,45 @@ bool Font_FTFont::FindAndInit (const TCollection_AsciiString& theFontName,
 }
 
 // =======================================================================
+// function : findAndInitFallback
+// purpose  :
+// =======================================================================
+bool Font_FTFont::findAndInitFallback (Font_UnicodeSubset theSubset)
+{
+  if (!myFallbackFaces[theSubset].IsNull())
+  {
+    return myFallbackFaces[theSubset]->IsValid();
+  }
+
+  myFallbackFaces[theSubset] = new Font_FTFont (myFTLib);
+  myFallbackFaces[theSubset]->myToUseUnicodeSubsetFallback = false; // no recursion
+
+  Handle(Font_FontMgr) aFontMgr = Font_FontMgr::GetInstance();
+  if (Handle(Font_SystemFont) aRequestedFont = aFontMgr->FindFallbackFont (theSubset, myFontAspect))
+  {
+    Font_FTFontParams aParams = myFontParams;
+    aParams.IsSingleStrokeFont = aRequestedFont->IsSingleStrokeFont();
+
+    const TCollection_AsciiString& aPath = aRequestedFont->FontPathAny (myFontAspect, aParams.ToSynthesizeItalic);
+    if (myFallbackFaces[theSubset]->Init (aPath, aParams))
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Font_FTFont, using fallback font '") + aRequestedFont->FontName() + "'"
+                                      + " for symbols unsupported by '" + myFTFace->family_name + "'", Message_Trace);
+    }
+  }
+  return myFallbackFaces[theSubset]->IsValid();
+}
+
+// =======================================================================
+// function : HasSymbol
+// purpose  :
+// =======================================================================
+bool Font_FTFont::HasSymbol (Standard_Utf32Char theUChar) const
+{
+  return FT_Get_Char_Index (myFTFace, theUChar) != 0;
+}
+
+// =======================================================================
 // function : loadGlyph
 // purpose  :
 // =======================================================================
@@ -206,9 +253,26 @@ bool Font_FTFont::loadGlyph (const Standard_Utf32Char theUChar)
 
   myGlyphImg.Clear();
   myUChar = 0;
-  if (theUChar == 0
-   || FT_Load_Char (myFTFace, theUChar, FT_Int32(myLoadFlags)) != 0
-   || myFTFace->glyph == NULL)
+  myActiveFTFace = myFTFace;
+  if (theUChar == 0)
+  {
+    return false;
+  }
+
+  if (myToUseUnicodeSubsetFallback
+  && !HasSymbol (theUChar))
+  {
+    // try using fallback
+    const Font_UnicodeSubset aSubset = CharSubset (theUChar);
+    if (findAndInitFallback (aSubset)
+     && myFallbackFaces[aSubset]->HasSymbol (theUChar))
+    {
+      myActiveFTFace = myFallbackFaces[aSubset]->myFTFace;
+    }
+  }
+
+  if (FT_Load_Char (myActiveFTFace, theUChar, FT_Int32(myLoadFlags)) != 0
+   || myActiveFTFace->glyph == NULL)
   {
     return false;
   }
@@ -225,26 +289,67 @@ bool Font_FTFont::RenderGlyph (const Standard_Utf32Char theUChar)
 {
   myGlyphImg.Clear();
   myUChar = 0;
+  myActiveFTFace = myFTFace;
+
+  if (theUChar != 0
+  &&  myToUseUnicodeSubsetFallback
+  && !HasSymbol (theUChar))
+  {
+    // try using fallback
+    const Font_UnicodeSubset aSubset = CharSubset (theUChar);
+    if (findAndInitFallback (aSubset)
+     && myFallbackFaces[aSubset]->HasSymbol (theUChar))
+    {
+      myActiveFTFace = myFallbackFaces[aSubset]->myFTFace;
+    }
+  }
+
   if (theUChar == 0
-   || FT_Load_Char (myFTFace, theUChar, FT_Int32(myLoadFlags | FT_LOAD_RENDER)) != 0
-   || myFTFace->glyph == NULL
-   || myFTFace->glyph->format != FT_GLYPH_FORMAT_BITMAP)
+   || FT_Load_Char (myActiveFTFace, theUChar, FT_Int32(myLoadFlags | FT_LOAD_RENDER)) != 0
+   || myActiveFTFace->glyph == NULL
+   || myActiveFTFace->glyph->format != FT_GLYPH_FORMAT_BITMAP)
   {
     return false;
   }
 
-  FT_Bitmap aBitmap = myFTFace->glyph->bitmap;
-  if (aBitmap.pixel_mode != FT_PIXEL_MODE_GRAY
-   || aBitmap.buffer == NULL || aBitmap.width == 0 || aBitmap.rows == 0)
+  FT_Bitmap aBitmap = myActiveFTFace->glyph->bitmap;
+  if (aBitmap.buffer == NULL || aBitmap.width == 0 || aBitmap.rows == 0)
   {
     return false;
   }
-  if (!myGlyphImg.InitWrapper (Image_Format_Alpha, aBitmap.buffer,
-                               aBitmap.width, aBitmap.rows, Abs (aBitmap.pitch)))
+
+  if (aBitmap.pixel_mode == FT_PIXEL_MODE_GRAY)
+  {
+    if (!myGlyphImg.InitWrapper (Image_Format_Alpha, aBitmap.buffer,
+                                 aBitmap.width, aBitmap.rows, Abs (aBitmap.pitch)))
+    {
+      return false;
+    }
+    myGlyphImg.SetTopDown (aBitmap.pitch > 0);
+  }
+  else if (aBitmap.pixel_mode == FT_PIXEL_MODE_MONO)
+  {
+    if (!myGlyphImg.InitTrash (Image_Format_Gray, aBitmap.width, aBitmap.rows))
+    {
+      return false;
+    }
+
+    myGlyphImg.SetTopDown (aBitmap.pitch > 0);
+    const int aNumOfBytesInRow = aBitmap.width / 8 + (aBitmap.width % 8 ? 1 : 0);
+    for (int aRow = 0; aRow < (int )aBitmap.rows; ++aRow)
+    {
+      for (int aCol = 0; aCol < (int )aBitmap.width; ++aCol)
+      {
+        const int aBitOn = aBitmap.buffer[aNumOfBytesInRow * aRow + aCol / 8] & (0x80 >> (aCol % 8));
+        *myGlyphImg.ChangeRawValue (aRow, aCol) = aBitOn ? 255 : 0;
+      }
+    }
+  }
+  else
   {
     return false;
   }
-  myGlyphImg.SetTopDown (aBitmap.pitch > 0);
+
   myUChar = theUChar;
   return true;
 }
@@ -253,24 +358,58 @@ bool Font_FTFont::RenderGlyph (const Standard_Utf32Char theUChar)
 // function : GlyphMaxSizeX
 // purpose  :
 // =======================================================================
-unsigned int Font_FTFont::GlyphMaxSizeX() const
+unsigned int Font_FTFont::GlyphMaxSizeX (bool theToIncludeFallback) const
 {
-  float aWidth = (FT_IS_SCALABLE(myFTFace) != 0)
-               ? float(myFTFace->bbox.xMax - myFTFace->bbox.xMin) * (float(myFTFace->size->metrics.x_ppem) / float(myFTFace->units_per_EM))
-               : fromFTPoints<float> (myFTFace->size->metrics.max_advance);
-  return (unsigned int)(aWidth + 0.5f);
+  if (!theToIncludeFallback)
+  {
+    float aWidth = (FT_IS_SCALABLE(myFTFace) != 0)
+                 ? float(myFTFace->bbox.xMax - myFTFace->bbox.xMin) * (float(myFTFace->size->metrics.x_ppem) / float(myFTFace->units_per_EM))
+                 : fromFTPoints<float> (myFTFace->size->metrics.max_advance);
+    return (unsigned int)(aWidth + 0.5f);
+  }
+
+  unsigned int aWidth = GlyphMaxSizeX (false);
+  if (theToIncludeFallback)
+  {
+    for (Standard_Integer aFontIter = 0; aFontIter < Font_UnicodeSubset_NB; ++aFontIter)
+    {
+      if (!myFallbackFaces[aFontIter].IsNull()
+        && myFallbackFaces[aFontIter]->IsValid())
+      {
+        aWidth = std::max (aWidth, myFallbackFaces[aFontIter]->GlyphMaxSizeX (false));
+      }
+    }
+  }
+  return aWidth;
 }
 
 // =======================================================================
 // function : GlyphMaxSizeY
 // purpose  :
 // =======================================================================
-unsigned int Font_FTFont::GlyphMaxSizeY() const
+unsigned int Font_FTFont::GlyphMaxSizeY (bool theToIncludeFallback) const
 {
-  float aHeight = (FT_IS_SCALABLE(myFTFace) != 0)
-                ? float(myFTFace->bbox.yMax - myFTFace->bbox.yMin) * (float(myFTFace->size->metrics.y_ppem) / float(myFTFace->units_per_EM))
-                : fromFTPoints<float> (myFTFace->size->metrics.height);
-  return (unsigned int)(aHeight + 0.5f);
+  if (!theToIncludeFallback)
+  {
+    float aHeight = (FT_IS_SCALABLE(myFTFace) != 0)
+                  ? float(myFTFace->bbox.yMax - myFTFace->bbox.yMin) * (float(myFTFace->size->metrics.y_ppem) / float(myFTFace->units_per_EM))
+                  : fromFTPoints<float> (myFTFace->size->metrics.height);
+    return (unsigned int)(aHeight + 0.5f);
+  }
+
+  unsigned int aHeight = GlyphMaxSizeY (false);
+  if (theToIncludeFallback)
+  {
+    for (Standard_Integer aFontIter = 0; aFontIter < Font_UnicodeSubset_NB; ++aFontIter)
+    {
+      if (!myFallbackFaces[aFontIter].IsNull()
+        && myFallbackFaces[aFontIter]->IsValid())
+      {
+        aHeight = std::max (aHeight, myFallbackFaces[aFontIter]->GlyphMaxSizeY (false));
+      }
+    }
+  }
+  return aHeight;
 }
 
 // =======================================================================
@@ -322,18 +461,22 @@ float Font_FTFont::AdvanceY (Standard_Utf32Char theUChar,
   return AdvanceY (theUCharNext);
 }
 
+// =======================================================================
+// function : getKerning
+// purpose  :
+// =======================================================================
 bool Font_FTFont::getKerning (FT_Vector& theKern,
                               Standard_Utf32Char theUCharCurr,
                               Standard_Utf32Char theUCharNext) const
 {
   theKern.x = 0;
   theKern.y = 0;
-  if (theUCharNext != 0 && FT_HAS_KERNING(myFTFace) != 0)
+  if (theUCharNext != 0 && FT_HAS_KERNING(myActiveFTFace) != 0)
   {
-    const FT_UInt aCharCurr = FT_Get_Char_Index (myFTFace, theUCharCurr);
-    const FT_UInt aCharNext = FT_Get_Char_Index (myFTFace, theUCharNext);
+    const FT_UInt aCharCurr = FT_Get_Char_Index (myActiveFTFace, theUCharCurr);
+    const FT_UInt aCharNext = FT_Get_Char_Index (myActiveFTFace, theUCharNext);
     if (aCharCurr == 0 || aCharNext == 0
-     || FT_Get_Kerning (myFTFace, aCharCurr, aCharNext, FT_KERNING_UNFITTED, &theKern) != 0)
+     || FT_Get_Kerning (myActiveFTFace, aCharCurr, aCharNext, FT_KERNING_UNFITTED, &theKern) != 0)
     {
       theKern.x = 0;
       theKern.y = 0;
@@ -357,7 +500,7 @@ float Font_FTFont::AdvanceX (Standard_Utf32Char theUCharNext) const
 
   FT_Vector aKern;
   getKerning (aKern, myUChar, theUCharNext);
-  return myWidthScaling * fromFTPoints<float> (myFTFace->glyph->advance.x + aKern.x);
+  return myWidthScaling * fromFTPoints<float> (myActiveFTFace->glyph->advance.x + aKern.x);
 }
 
 // =======================================================================
@@ -373,29 +516,41 @@ float Font_FTFont::AdvanceY (Standard_Utf32Char theUCharNext) const
 
   FT_Vector aKern;
   getKerning (aKern, myUChar, theUCharNext);
-  return fromFTPoints<float> (myFTFace->glyph->advance.y + aKern.y);
+  return fromFTPoints<float> (myActiveFTFace->glyph->advance.y + aKern.y);
 }
 
 // =======================================================================
 // function : GlyphsNumber
 // purpose  :
 // =======================================================================
-Standard_Integer Font_FTFont::GlyphsNumber() const
+Standard_Integer Font_FTFont::GlyphsNumber (bool theToIncludeFallback) const
 {
-  return myFTFace->num_glyphs;
+  Standard_Integer aNbGlyphs = myFTFace->num_glyphs;
+  if (theToIncludeFallback)
+  {
+    for (Standard_Integer aFontIter = 0; aFontIter < Font_UnicodeSubset_NB; ++aFontIter)
+    {
+      if (!myFallbackFaces[aFontIter].IsNull()
+        && myFallbackFaces[aFontIter]->IsValid())
+      {
+        aNbGlyphs += myFallbackFaces[aFontIter]->GlyphsNumber (false);
+      }
+    }
+  }
+  return aNbGlyphs;
 }
 
 // =======================================================================
-// function : theRect
+// function : GlyphRect
 // purpose  :
 // =======================================================================
 void Font_FTFont::GlyphRect (Font_Rect& theRect) const
 {
-  const FT_Bitmap& aBitmap = myFTFace->glyph->bitmap;
-  theRect.Left   = float(myFTFace->glyph->bitmap_left);
-  theRect.Top    = float(myFTFace->glyph->bitmap_top);
-  theRect.Right  = float(myFTFace->glyph->bitmap_left + (int )aBitmap.width);
-  theRect.Bottom = float(myFTFace->glyph->bitmap_top  - (int )aBitmap.rows);
+  const FT_Bitmap& aBitmap = myActiveFTFace->glyph->bitmap;
+  theRect.Left   = float(myActiveFTFace->glyph->bitmap_left);
+  theRect.Top    = float(myActiveFTFace->glyph->bitmap_top);
+  theRect.Right  = float(myActiveFTFace->glyph->bitmap_left + (int )aBitmap.width);
+  theRect.Bottom = float(myActiveFTFace->glyph->bitmap_top  - (int )aBitmap.rows);
 }
 
 // =======================================================================
index bcabf53..4ba5743 100755 (executable)
@@ -19,6 +19,7 @@
 #include <Font_FontAspect.hxx>
 #include <Font_Rect.hxx>
 #include <Font_StrictLevel.hxx>
+#include <Font_UnicodeSubset.hxx>
 #include <Graphic3d_HorizontalTextAlignment.hxx>
 #include <Graphic3d_VerticalTextAlignment.hxx>
 #include <Image_PixMap.hxx>
@@ -33,10 +34,10 @@ class Font_FTLibrary;
 //! Font initialization parameters.
 struct Font_FTFontParams
 {
-  unsigned int PointSize;          //!< face size in points (1/72 inch)
-  unsigned int Resolution;         //!< resolution of the target device in dpi for FT_Set_Char_Size()
-  bool         ToSynthesizeItalic; //!< generate italic style (e.g. for font family having no italic style); FALSE by default
-  bool         IsSingleStrokeFont; //!< single-stroke (one-line) font, FALSE by default
+  unsigned int PointSize;                  //!< face size in points (1/72 inch)
+  unsigned int Resolution;                 //!< resolution of the target device in dpi for FT_Set_Char_Size()
+  bool         ToSynthesizeItalic;         //!< generate italic style (e.g. for font family having no italic style); FALSE by default
+  bool         IsSingleStrokeFont;         //!< single-stroke (one-line) font, FALSE by default
 
   //! Empty constructor.
   Font_FTFontParams() : PointSize (0), Resolution (72u), ToSynthesizeItalic (false), IsSingleStrokeFont (false) {}
@@ -68,6 +69,69 @@ public:
                                                             const Font_FTFontParams&  theParams,
                                                             const Font_StrictLevel    theStrictLevel = Font_StrictLevel_Any);
 
+  //! Return TRUE if specified character is within subset of modern CJK characters.
+  static bool IsCharFromCJK (Standard_Utf32Char theUChar)
+  {
+    return (theUChar >= 0x03400 && theUChar <= 0x04DFF)
+        || (theUChar >= 0x04E00 && theUChar <= 0x09FFF)
+        || (theUChar >= 0x0F900 && theUChar <= 0x0FAFF)
+        || (theUChar >= 0x20000 && theUChar <= 0x2A6DF)
+        || (theUChar >= 0x2F800 && theUChar <= 0x2FA1F)
+        // Hiragana and Katakana (Japanese) are NOT part of CJK, but CJK fonts usually include these symbols
+        || IsCharFromHiragana (theUChar)
+        || IsCharFromKatakana (theUChar);
+  }
+
+  //! Return TRUE if specified character is within subset of Hiragana (Japanese).
+  static bool IsCharFromHiragana (Standard_Utf32Char theUChar)
+  {
+    return (theUChar >= 0x03040 && theUChar <= 0x0309F);
+  }
+
+  //! Return TRUE if specified character is within subset of Katakana (Japanese).
+  static bool IsCharFromKatakana (Standard_Utf32Char theUChar)
+  {
+    return (theUChar >= 0x030A0 && theUChar <= 0x030FF);
+  }
+
+  //! Return TRUE if specified character is within subset of modern Korean characters (Hangul).
+  static bool IsCharFromKorean (Standard_Utf32Char theUChar)
+  {
+    return (theUChar >= 0x01100 && theUChar <= 0x011FF)
+        || (theUChar >= 0x03130 && theUChar <= 0x0318F)
+        || (theUChar >= 0x0AC00 && theUChar <= 0x0D7A3);
+  }
+
+  //! Return TRUE if specified character is within subset of Arabic characters.
+  static bool IsCharFromArabic (Standard_Utf32Char theUChar)
+  {
+    return (theUChar >= 0x00600 && theUChar <= 0x006FF);
+  }
+
+  //! Return TRUE if specified character should be displayed in Right-to-Left order.
+  static bool IsCharRightToLeft (Standard_Utf32Char theUChar)
+  {
+    return IsCharFromArabic(theUChar);
+  }
+
+  //! Determine Unicode subset for specified character
+  static Font_UnicodeSubset CharSubset (Standard_Utf32Char theUChar)
+  {
+    if (IsCharFromCJK (theUChar))
+    {
+      return Font_UnicodeSubset_CJK;
+    }
+    else if (IsCharFromKorean (theUChar))
+    {
+      return Font_UnicodeSubset_Korean;
+    }
+    else if (IsCharFromArabic (theUChar))
+    {
+      return Font_UnicodeSubset_Arabic;
+    }
+    return Font_UnicodeSubset_Western;
+  }
+
 public:
 
   //! Create uninitialized instance.
@@ -119,6 +183,13 @@ public:
                                     const Font_FTFontParams& theParams,
                                     Font_StrictLevel theStrictLevel = Font_StrictLevel_Any);
 
+  //! Return flag to use fallback fonts in case if used font does not include symbols from specific Unicode subset; TRUE by default.
+  //! @sa Font_FontMgr::ToUseUnicodeSubsetFallback()
+  Standard_Boolean ToUseUnicodeSubsetFallback() const { return myToUseUnicodeSubsetFallback; }
+
+  //! Set if fallback fonts should be used in case if used font does not include symbols from specific Unicode subset.
+  void SetUseUnicodeSubsetFallback (Standard_Boolean theToFallback) { myToUseUnicodeSubsetFallback = theToFallback; }
+
   //! Return TRUE if this is single-stroke (one-line) font, FALSE by default.
   //! Such fonts define single-line glyphs instead of closed contours, so that they are rendered incorrectly by normal software.
   bool IsSingleStrokeFont() const { return myFontParams.IsSingleStrokeFont; }
@@ -136,10 +207,10 @@ public:
   Standard_EXPORT bool RenderGlyph (const Standard_Utf32Char theChar);
 
   //! @return maximal glyph width in pixels (rendered to bitmap).
-  Standard_EXPORT unsigned int GlyphMaxSizeX() const;
+  Standard_EXPORT unsigned int GlyphMaxSizeX (bool theToIncludeFallback = false) const;
 
   //! @return maximal glyph height in pixels (rendered to bitmap).
-  Standard_EXPORT unsigned int GlyphMaxSizeY() const;
+  Standard_EXPORT unsigned int GlyphMaxSizeY (bool theToIncludeFallback = false) const;
 
   //! @return vertical distance from the horizontal baseline to the highest character coordinate.
   Standard_EXPORT float Ascender() const;
@@ -163,6 +234,9 @@ public:
     myWidthScaling = theScaleFactor;
   }
 
+  //! Return TRUE if font contains specified symbol (excluding fallback list).
+  Standard_EXPORT bool HasSymbol (Standard_Utf32Char theUChar) const;
+
   //! Compute horizontal advance to the next character with kerning applied when applicable.
   //! Assuming text rendered horizontally.
   //! @param theUCharNext the next character to compute advance from current one
@@ -187,8 +261,9 @@ public:
   Standard_EXPORT float AdvanceY (Standard_Utf32Char theUChar,
                                   Standard_Utf32Char theUCharNext);
 
-  //! @return glyphs number in this font.
-  Standard_EXPORT Standard_Integer GlyphsNumber() const;
+  //! Return glyphs number in this font.
+  //! @param theToIncludeFallback if TRUE then the number will include fallback list
+  Standard_EXPORT Standard_Integer GlyphsNumber (bool theToIncludeFallback = false) const;
 
   //! Retrieve glyph bitmap rectangle
   Standard_EXPORT void GlyphRect (Font_Rect& theRect) const;
@@ -262,18 +337,25 @@ protected:
                                    Standard_Utf32Char theUCharCurr,
                                    Standard_Utf32Char theUCharNext) const;
 
+  //! Initialize fallback font.
+  Standard_EXPORT bool findAndInitFallback (Font_UnicodeSubset theSubset);
+
 protected:
 
   Handle(Font_FTLibrary)     myFTLib;        //!< handle to the FT library object
   Handle(NCollection_Buffer) myBuffer;       //!< memory buffer
+  Handle(Font_FTFont)        myFallbackFaces[Font_UnicodeSubset_NB]; //!< fallback fonts
   FT_Face                    myFTFace;       //!< FT face object
+  FT_Face                    myActiveFTFace; //!< active FT face object (the main of fallback)
   TCollection_AsciiString    myFontPath;     //!< font path
   Font_FTFontParams          myFontParams;   //!< font initialization parameters
+  Font_FontAspect            myFontAspect;   //!< font initialization aspect
   float                      myWidthScaling; //!< scale glyphs along X-axis
   int32_t                    myLoadFlags;    //!< default load flags
 
   Image_PixMap               myGlyphImg;     //!< cached glyph plane
   Standard_Utf32Char         myUChar;        //!< currently loaded unicode character
+  Standard_Boolean           myToUseUnicodeSubsetFallback; //!< use default fallback fonts for extended Unicode sub-sets (Korean, CJK, etc.)
 
 };
 
index 034b33c..3fef8bd 100644 (file)
@@ -194,6 +194,16 @@ Handle(Font_FontMgr) Font_FontMgr::GetInstance()
 }
 
 // =======================================================================
+// function : ToUseUnicodeSubsetFallback
+// purpose  :
+// =======================================================================
+Standard_Boolean& Font_FontMgr::ToUseUnicodeSubsetFallback()
+{
+  static Standard_Boolean TheToUseUnicodeSubsetFallback = true;
+  return TheToUseUnicodeSubsetFallback;
+}
+
+// =======================================================================
 // function : addFontAlias
 // purpose  :
 // =======================================================================
@@ -239,6 +249,7 @@ Font_FontMgr::Font_FontMgr()
   Handle(Font_FontAliasSequence) anIris  = new Font_FontAliasSequence();
   Handle(Font_FontAliasSequence) aCJK    = new Font_FontAliasSequence();
   Handle(Font_FontAliasSequence) aKorean = new Font_FontAliasSequence();
+  Handle(Font_FontAliasSequence) anArab  = new Font_FontAliasSequence();
 
   // best matches - pre-installed on Windows, some of them are pre-installed on macOS,
   // and sometimes them can be found installed on other systems (by user)
@@ -289,6 +300,15 @@ Font_FontMgr::Font_FontMgr()
   aKorean->Append (Font_FontAlias ("noto serif cjk jp")); // Linux
   aKorean->Append (Font_FontAlias ("noto sans cjk jp")); // Linux
 
+#if defined(_WIN32)
+  anArab->Append (Font_FontAlias ("times new roman"));
+#elif defined(__APPLE__)
+  anArab->Append (Font_FontAlias ("decotype naskh"));
+#elif defined(__ANDROID__)
+  anArab->Append (Font_FontAlias ("droid arabic naskh"));
+  anArab->Append (Font_FontAlias ("noto naskh arabic"));
+#endif
+
   addFontAlias ("mono",              aMono);
   addFontAlias ("courier",           aMono);                      // Font_NOF_ASCII_MONO
   addFontAlias ("monospace",         aMono);                      // Font_NOF_MONOSPACE
@@ -308,6 +328,7 @@ Font_FontMgr::Font_FontMgr()
   addFontAlias ("korean",            aKorean);                    // Font_NOF_KOREAN
   addFontAlias ("cjk",               aCJK);                       // Font_NOF_CJK
   addFontAlias ("nsimsun",           aCJK);
+  addFontAlias ("arabic",            anArab);                     // Font_NOF_ARABIC
   addFontAlias (Font_NOF_SYMBOL_MONO,          aWinDin);
   addFontAlias (Font_NOF_ASCII_SCRIPT_SIMPLEX, aScript);
 
@@ -687,6 +708,24 @@ Handle(Font_SystemFont) Font_FontMgr::GetFont (const TCollection_AsciiString& th
 }
 
 // =======================================================================
+// function : FindFallbackFont
+// purpose  :
+// =======================================================================
+Handle(Font_SystemFont) Font_FontMgr::FindFallbackFont (Font_UnicodeSubset theSubset,
+                                                        Font_FontAspect theFontAspect) const
+{
+  Font_FontAspect aFontAspect = theFontAspect;
+  switch (theSubset)
+  {
+    case Font_UnicodeSubset_Western: return FindFont (Font_NOF_SANS_SERIF, Font_StrictLevel_Aliases, aFontAspect);
+    case Font_UnicodeSubset_Korean:  return FindFont (Font_NOF_KOREAN,     Font_StrictLevel_Aliases, aFontAspect);
+    case Font_UnicodeSubset_CJK:     return FindFont (Font_NOF_CJK,        Font_StrictLevel_Aliases, aFontAspect);
+    case Font_UnicodeSubset_Arabic:  return FindFont (Font_NOF_ARABIC,     Font_StrictLevel_Aliases, aFontAspect);
+  }
+  return Handle(Font_SystemFont)();
+}
+
+// =======================================================================
 // function : FindFont
 // purpose  :
 // =======================================================================
@@ -770,7 +809,8 @@ Handle(Font_SystemFont) Font_FontMgr::FindFont (const TCollection_AsciiString& t
     }
   }
 
-  if (aFont.IsNull())
+  if (aFont.IsNull()
+   && theStrictLevel == Font_StrictLevel_Any)
   {
     // try finding ANY font in case if even default fallback alias myFallbackAlias cannot be found
     aFont = myFontMap.Find (TCollection_AsciiString());
index e38af97..8f6bee2 100644 (file)
@@ -22,6 +22,7 @@
 #include <Font_FontAspect.hxx>
 #include <Font_NListOfSystemFont.hxx>
 #include <Font_StrictLevel.hxx>
+#include <Font_UnicodeSubset.hxx>
 #include <NCollection_DataMap.hxx>
 #include <NCollection_IndexedMap.hxx>
 #include <NCollection_Shared.hxx>
@@ -55,6 +56,9 @@ public:
     return "invalid";
   }
 
+  //! Return flag to use fallback fonts in case if used font does not include symbols from specific Unicode subset; TRUE by default.
+  Standard_EXPORT static Standard_Boolean& ToUseUnicodeSubsetFallback();
+
 public:
 
   //! Return the list of available fonts.
@@ -106,7 +110,14 @@ public:
   {
     return FindFont (theFontName, Font_StrictLevel_Any, theFontAspect);
   }
-  
+
+  //! Tries to find fallback font for specified Unicode subset.
+  //! Returns NULL in case when fallback font is not found in the system.
+  //! @param theSubset     [in] Unicode subset
+  //! @param theFontAspect [in] font aspect to find
+  Standard_EXPORT Handle(Font_SystemFont) FindFallbackFont (Font_UnicodeSubset theSubset,
+                                                            Font_FontAspect theFontAspect) const;
+
   //! Read font file and retrieve information from it.
   Standard_EXPORT Handle(Font_SystemFont) CheckFont (const Standard_CString theFontPath) const;
   
index c63d869..cbbd2b3 100644 (file)
@@ -16,8 +16,9 @@
 #define  Font_NOF_MONOSPACE             "monospace"
 #define  Font_NOF_SERIF                 "serif"
 #define  Font_NOF_SANS_SERIF            "sans-serif"
-#define  Font_NOF_CJK                   "cjk"
-#define  Font_NOF_KOREAN                "korean"
+#define  Font_NOF_CJK                   "cjk"        // Font_UnicodeSubset_CJK
+#define  Font_NOF_KOREAN                "korean"     // Font_UnicodeSubset_Korean
+#define  Font_NOF_ARABIC                "arabic"     // Font_UnicodeSubset_Arabic
 
 #define  Font_NOF_ASCII_MONO            "Courier"
 #define  Font_NOF_ASCII_SIMPLEX         "Times-Roman"
diff --git a/src/Font/Font_UnicodeSubset.hxx b/src/Font/Font_UnicodeSubset.hxx
new file mode 100644 (file)
index 0000000..72256a2
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Font_UnicodeSubset_HeaderFile
+#define _Font_UnicodeSubset_HeaderFile
+
+//! Enumeration defining Unicode subsets.
+enum Font_UnicodeSubset
+{
+  Font_UnicodeSubset_Western, //!< western letters
+  Font_UnicodeSubset_Korean,  //!< modern Korean letters
+  Font_UnicodeSubset_CJK,     //!< Chinese characters (Chinese, Japanese, Korean and Vietnam)
+  Font_UnicodeSubset_Arabic,  //!< Arabic  characters
+};
+
+enum { Font_UnicodeSubset_NB = Font_UnicodeSubset_Arabic };
+
+#endif // _Font_UnicodeSubset_HeaderFile
index 0692e87..34ddf42 100755 (executable)
@@ -21,7 +21,6 @@
 #include <Standard_Assert.hxx>
 #include <TCollection_ExtendedString.hxx>
 
-
 IMPLEMENT_STANDARD_RTTIEXT(OpenGl_Font,OpenGl_Resource)
 
 // =======================================================================
@@ -34,8 +33,6 @@ OpenGl_Font::OpenGl_Font (const Handle(Font_FTFont)&     theFont,
   myFont (theFont),
   myAscender (0.0f),
   myDescender (0.0f),
-  myLineSpacing (0.0f),
-  myTileSizeX (0),
   myTileSizeY (0),
   myLastTileId (-1),
   myTextureFormat (GL_ALPHA)
@@ -105,11 +102,9 @@ bool OpenGl_Font::Init (const Handle(OpenGl_Context)& theCtx)
     return false;
   }
 
-  myAscender    = myFont->Ascender();
-  myDescender   = myFont->Descender();
-  myLineSpacing = myFont->LineSpacing();
-  myTileSizeX   = myFont->GlyphMaxSizeX();
-  myTileSizeY   = myFont->GlyphMaxSizeY();
+  myAscender  = myFont->Ascender();
+  myDescender = myFont->Descender();
+  myTileSizeY = myFont->GlyphMaxSizeY (true);
 
   myLastTileId = -1;
   if (!createTexture (theCtx))
@@ -126,12 +121,16 @@ bool OpenGl_Font::Init (const Handle(OpenGl_Context)& theCtx)
 // =======================================================================
 bool OpenGl_Font::createTexture (const Handle(OpenGl_Context)& theCtx)
 {
-  const Standard_Integer aMaxSize = theCtx->MaxTextureSize();
-
-  Standard_Integer aGlyphsNb = myFont->GlyphsNumber() - myLastTileId + 1;
-
-  const Standard_Integer aTextureSizeX = OpenGl_Context::GetPowerOfTwo (aGlyphsNb * myTileSizeX, aMaxSize);
-  const Standard_Integer aTilesPerRow  = aTextureSizeX / myTileSizeX;
+  // Single font might define very wide range of symbols, with very few of them actually used in text.
+  // Limit single texture with circa 4096 glyphs.
+  static const Standard_Integer THE_MAX_GLYPHS_PER_TEXTURE = 4096;
+
+  myTileSizeY = myFont->GlyphMaxSizeY (true);
+  const Standard_Integer aGlyphsNb = Min (THE_MAX_GLYPHS_PER_TEXTURE, myFont->GlyphsNumber (true) - myLastTileId + 1);
+  const Standard_Integer aMaxTileSizeX = myFont->GlyphMaxSizeX (true);
+  const Standard_Integer aMaxSize      = theCtx->MaxTextureSize();
+  const Standard_Integer aTextureSizeX = OpenGl_Context::GetPowerOfTwo (aGlyphsNb * aMaxTileSizeX, aMaxSize);
+  const Standard_Integer aTilesPerRow  = aTextureSizeX / aMaxTileSizeX;
   const Standard_Integer aTextureSizeY = OpenGl_Context::GetPowerOfTwo (GLint((aGlyphsNb / aTilesPerRow) + 1) * myTileSizeY, aMaxSize);
 
   memset (&myLastTilePx, 0, sizeof(myLastTilePx));
@@ -186,14 +185,18 @@ bool OpenGl_Font::renderGlyph (const Handle(OpenGl_Context)& theCtx,
   const Standard_Integer aTileId = myLastTileId + 1;
   myLastTilePx.Left  = myLastTilePx.Right + 3;
   myLastTilePx.Right = myLastTilePx.Left + (Standard_Integer )anImg.SizeX();
-  if (myLastTilePx.Right >= aTexture->SizeX())
+  if (myLastTilePx.Right > aTexture->SizeX()
+   || (Standard_Integer )anImg.SizeY() > myTileSizeY)
   {
+    myTileSizeY = myFont->GlyphMaxSizeY (true);
+
     myLastTilePx.Left    = 0;
     myLastTilePx.Right   = (Standard_Integer )anImg.SizeX();
     myLastTilePx.Top    += myTileSizeY;
     myLastTilePx.Bottom += myTileSizeY;
 
-    if (myLastTilePx.Bottom >= aTexture->SizeY())
+    if (myLastTilePx.Bottom > aTexture->SizeY()
+     || myLastTilePx.Right  > aTexture->SizeX())
     {
       if (!createTexture (theCtx))
       {
index a588762..3373ac8 100755 (executable)
@@ -106,12 +106,6 @@ public:
     return myDescender;
   }
 
-  //! @return default line spacing (the baseline-to-baseline distance)
-  inline float LineSpacing() const
-  {
-    return myLineSpacing;
-  }
-
   //! Render glyph to texture if not already.
   //! @param theCtx       active context
   //! @param theUChar     unicode symbol to render
@@ -135,8 +129,6 @@ protected:
   Handle(Font_FTFont)     myFont;          //!< FreeType font instance
   Standard_ShortReal      myAscender;      //!< ascender     provided my FT font
   Standard_ShortReal      myDescender;     //!< descender    provided my FT font
-  Standard_ShortReal      myLineSpacing;   //!< line spacing provided my FT font
-  Standard_Integer        myTileSizeX;     //!< tile width
   Standard_Integer        myTileSizeY;     //!< tile height
   Standard_Integer        myLastTileId;    //!< id of last tile
   RectI                   myLastTilePx;
index 5b9560c..c3d2b9c 100644 (file)
@@ -5598,6 +5598,18 @@ static int VFont (Draw_Interpretor& theDI,
       }
       aMgr->SetTraceAliases (toEnable);
     }
+    else if (anArgCase == "-unicodefallback"
+          || anArgCase == "-fallback"
+          || anArgCase == "-touseunicodesubsetfallback")
+    {
+      bool toEnable = true;
+      if (anArgIter + 1 < theArgNb
+       && ViewerTest::ParseOnOff (theArgVec[anArgIter + 1], toEnable))
+      {
+        ++anArgIter;
+      }
+      Font_FontMgr::ToUseUnicodeSubsetFallback() = toEnable;
+    }
     else
     {
       std::cerr << "Warning! Unknown argument '" << anArg << "'\n";
@@ -6541,7 +6553,8 @@ void ViewerTest::ObjectCommands(Draw_Interpretor& theCommands)
                    __FILE__, TextToBRep, group);
   theCommands.Add ("vfont",
                             "vfont [-add pathToFont [fontName] [regular,bold,italic,boldItalic=undefined] [singleStroke]]"
-                   "\n\t\t:        [-strict {any|aliases|strict}] [-find fontName [regular,bold,italic,boldItalic=undefined]] [-verbose {on|off}]",
+                   "\n\t\t:        [-strict {any|aliases|strict}] [-find fontName [regular,bold,italic,boldItalic=undefined]] [-verbose {on|off}]"
+                   "\n\t\t:        [-unicodeFallback {on|off}]",
                    __FILE__, VFont, group);
 
   theCommands.Add ("vvertexmode",
diff --git a/tests/3rdparty/fonts/C2 b/tests/3rdparty/fonts/C2
new file mode 100644 (file)
index 0000000..0ff3c7f
--- /dev/null
@@ -0,0 +1,37 @@
+puts "================"
+puts "0022149: Strings with Japanese characters can not be displayed in 3D viewer"
+puts "================"
+puts ""
+
+pload MODELING VISUALIZATION
+
+dtracelevel trace
+vfont -verbose 1
+vclear
+vinit View1
+vaxo
+vpoint p0 0 0 0
+
+pload MODELING VISUALIZATION
+dtracelevel trace
+vfont -verbose 1
+vclear
+vinit View1
+vtop
+vpoint p00  0  0 0
+vpoint p01  0 10 0
+vpoint p11 10 10 0
+vpoint p10 10  0 0
+vfit
+vzoom 0.8
+vdrawtext t0 "한국어 (Korean) Čeština" -pos  0  0 0 -halign left  -font korean
+vdrawtext t1 "한국어 (Korean) Čeština" -pos 10  1 0 -halign right -font sans
+vdrawtext t2 "简体中文 (Chinese)"    -pos  0  2 0 -halign left  -font cjk
+vdrawtext t3 "简体中文 (Chinese)"    -pos 10  3 0 -halign right -font sans
+vdrawtext t4 "あ (Japanese)"         -pos  0  4 0 -halign left  -font cjk
+vdrawtext t5 "あ (Japanese)"         -pos 10  5 0 -halign right -font sans
+
+vdump $imagedir/${casename}.png
+
+# just print font list
+vfont