Add render scale property to text label 15/321715/16
authorBowon Ryu <bowon.ryu@samsung.com>
Thu, 27 Mar 2025 07:36:41 +0000 (16:36 +0900)
committerBowon Ryu <bowon.ryu@samsung.com>
Wed, 14 May 2025 03:33:54 +0000 (12:33 +0900)
This is implemented to avoid text quality degradation when scaling up with Actor::SetScale().
Renders by scaling up the point size and texture size to the given scale.

* This property is only available in ASYNC_AUTO, ASYNC_MANUAL.
* RenderScale is only valid when it is 1.0f or greater.

Change-Id: I9a11e6aa413c4aada6eb08e0f58b1aafdff7b83c
Signed-off-by: Bowon Ryu <bowon.ryu@samsung.com>
16 files changed:
automated-tests/src/dali-toolkit/utc-Dali-TextLabel-Async.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp
dali-toolkit/devel-api/controls/text-controls/text-label-devel.h
dali-toolkit/internal/controls/text-controls/text-label-impl.cpp
dali-toolkit/internal/graphics/shaders/text-scroller-shader.vert
dali-toolkit/internal/graphics/shaders/text-visual-shader.vert
dali-toolkit/internal/text/async-text/async-text-loader-impl.cpp
dali-toolkit/internal/text/async-text/async-text-loader-impl.h
dali-toolkit/internal/text/async-text/async-text-loader.cpp
dali-toolkit/internal/text/async-text/async-text-loader.h
dali-toolkit/internal/text/async-text/text-loading-task.cpp
dali-toolkit/internal/text/controller/text-controller-impl.h
dali-toolkit/internal/text/controller/text-controller.cpp
dali-toolkit/internal/text/controller/text-controller.h
dali-toolkit/internal/visuals/text/text-visual.cpp
dali-toolkit/public-api/controls/text-controls/text-label.h

index 1ac6b243ec44b4e79437f829fd086e436d5571fb..dc9651c9c7f2b48bd41fe4f1edbddc76e1f592bf 100644 (file)
@@ -2884,4 +2884,108 @@ int UtcDaliToolkitTextLabelAsyncFontVariations(void)
   application.Render();
 
   END_TEST;
-}
\ No newline at end of file
+}
+
+int UtcDaliToolkitTextLabelAsyncRenderScale(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline(" UtcDaliToolkitTextLabelAsyncRenderScale");
+
+  // Include glyphs with advance of 0 for testing.
+  std::string text = "Hello World Render SCA̧LE Test!";
+
+  TextLabel label = TextLabel::New(text);
+  DALI_TEST_CHECK(label);
+
+  // Avoid a crash when core load gl resources.
+  application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE);
+
+  // Set the dpi of AsyncTextLoader and FontClient to be identical.
+  TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+  application.GetScene().Add(label);
+
+  // Connect to the async text rendered signal.
+  ConnectionTracker* testTracker = new ConnectionTracker();
+  DevelTextLabel::AsyncTextRenderedSignal(label).Connect(&TestAsyncTextRendered);
+
+  bool asyncTextRendered = false;
+  label.ConnectSignal(testTracker, "asyncTextRendered", CallbackFunctor(&asyncTextRendered));
+
+  gAsyncTextRenderedCalled = false;
+  gAsyncTextRenderedWidth  = 0.0f;
+  gAsyncTextRenderedHeight = 0.0f;
+
+  float expectedWidth  = 110.0f;
+  float expectedHeight = 50.0f;
+
+  // Case where both original and scaled textures have sufficient control size.
+  label.SetProperty(DevelTextLabel::Property::RENDER_SCALE, 1.045f);
+  label.SetProperty(TextLabel::Property::PIXEL_SNAP_FACTOR, 1.0f);
+  label.SetProperty(TextLabel::Property::PIXEL_SIZE, 20);
+  label.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
+  label.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
+  label.SetProperty(DevelTextLabel::Property::RENDER_MODE, DevelTextLabel::Render::ASYNC_AUTO);
+  label.SetProperty(Actor::Property::SIZE, Vector2(expectedWidth, expectedHeight));
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, ASYNC_TEXT_THREAD_TIMEOUT), true, TEST_LOCATION);
+
+  DALI_TEST_CHECK(gAsyncTextRenderedCalled);
+  DALI_TEST_CHECK(asyncTextRendered);
+  DALI_TEST_EQUALS(expectedWidth, gAsyncTextRenderedWidth, Math::MACHINE_EPSILON_1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(expectedHeight, gAsyncTextRenderedHeight, Math::MACHINE_EPSILON_1000, TEST_LOCATION);
+
+  expectedWidth  = 60.0f;
+  expectedHeight = 50.0f;
+
+  asyncTextRendered = false;
+  gAsyncTextRenderedCalled = false;
+  gAsyncTextRenderedWidth  = 0.0f;
+  gAsyncTextRenderedHeight = 0.0f;
+
+  // Case where the scaled texture exceeds the control size.
+  label.SetProperty(TextLabel::Property::PIXEL_SIZE, 12);
+  label.SetProperty(Actor::Property::SIZE, Vector2(expectedWidth, expectedHeight));
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, ASYNC_TEXT_THREAD_TIMEOUT), true, TEST_LOCATION);
+
+  DALI_TEST_CHECK(gAsyncTextRenderedCalled);
+  DALI_TEST_CHECK(asyncTextRendered);
+  DALI_TEST_EQUALS(expectedWidth, gAsyncTextRenderedWidth, Math::MACHINE_EPSILON_1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(expectedHeight, gAsyncTextRenderedHeight, Math::MACHINE_EPSILON_1000, TEST_LOCATION);
+
+  expectedWidth  = 500.0f;
+  expectedHeight = 50.0f;
+
+  asyncTextRendered = false;
+  gAsyncTextRenderedCalled = false;
+  gAsyncTextRenderedWidth  = 0.0f;
+  gAsyncTextRenderedHeight = 0.0f;
+
+  // Text fit early return case.
+  Property::Map textFitMapSet;
+  textFitMapSet["enable"]       = true;
+  textFitMapSet["minSize"]      = 10.f;
+  textFitMapSet["maxSize"]      = 30.f;
+  textFitMapSet["stepSize"]     = 5.f;
+  textFitMapSet["fontSizeType"] = "pixelSize";
+  label.SetProperty(Toolkit::DevelTextLabel::Property::TEXT_FIT, textFitMapSet);
+  label.SetProperty(Actor::Property::SIZE, Vector2(expectedWidth, expectedHeight));
+
+  application.SendNotification();
+  application.Render();
+
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, ASYNC_TEXT_THREAD_TIMEOUT), true, TEST_LOCATION);
+
+  DALI_TEST_CHECK(gAsyncTextRenderedCalled);
+  DALI_TEST_CHECK(asyncTextRendered);
+  DALI_TEST_EQUALS(expectedWidth, gAsyncTextRenderedWidth, Math::MACHINE_EPSILON_1000, TEST_LOCATION);
+  DALI_TEST_EQUALS(expectedHeight, gAsyncTextRenderedHeight, Math::MACHINE_EPSILON_1000, TEST_LOCATION);
+
+  END_TEST;
+}
index 772a63743cb2ec584767235961015a2879143d6a..5d4c301f7c0f0d8e953fc9f07fd663d3e1fc7189 100644 (file)
@@ -89,6 +89,7 @@ const char* const PROPERTY_NAME_MANUAL_RENDERED       = "manualRendered";
 const char* const PROPERTY_NAME_ASYNC_LINE_COUNT      = "asyncLineCount";
 const char* const PROPERTY_NAME_ELLIPSIS_MODE         = "ellipsisMode";
 const char* const PROPERTY_NAME_FONT_VARIATIONS       = "fontVariations";
+const char* const PROPERTY_NAME_RENDER_SCALE          = "renderScale";
 
 const std::string  DEFAULT_FONT_DIR("/resources/fonts");
 const unsigned int EMOJI_FONT_SIZE = 3840u; // 60 * 64
@@ -378,6 +379,7 @@ int UtcDaliToolkitTextLabelGetPropertyP(void)
   DALI_TEST_CHECK(label.GetPropertyIndex(PROPERTY_NAME_ASYNC_LINE_COUNT) == DevelTextLabel::Property::ASYNC_LINE_COUNT);
   DALI_TEST_CHECK(label.GetPropertyIndex(PROPERTY_NAME_ELLIPSIS_MODE) == DevelTextLabel::Property::ELLIPSIS_MODE);
   DALI_TEST_CHECK(label.GetPropertyIndex(PROPERTY_NAME_FONT_VARIATIONS) == DevelTextLabel::Property::FONT_VARIATIONS);
+  DALI_TEST_CHECK(label.GetPropertyIndex(PROPERTY_NAME_RENDER_SCALE) == DevelTextLabel::Property::RENDER_SCALE);
 
   END_TEST;
 }
@@ -1083,6 +1085,23 @@ int UtcDaliToolkitTextLabelSetPropertyP(void)
   label.SetProperty(DevelTextLabel::Property::ELLIPSIS_MODE, Toolkit::DevelText::Ellipsize::AUTO_SCROLL);
   DALI_TEST_EQUALS(label.GetProperty<int>(DevelTextLabel::Property::ELLIPSIS_MODE), static_cast<int>(Toolkit::DevelText::Ellipsize::AUTO_SCROLL), TEST_LOCATION);
 
+  // Render Scale
+  label.SetProperty(TextLabel::Property::PIXEL_SNAP_FACTOR, 1.0f);
+  DALI_TEST_EQUALS(label.GetProperty<float>(TextLabel::Property::PIXEL_SNAP_FACTOR), 1.0f, TEST_LOCATION);
+
+  label.SetProperty(DevelTextLabel::Property::RENDER_SCALE, 1.045f);
+  DALI_TEST_EQUALS(label.GetProperty<float>(DevelTextLabel::Property::RENDER_SCALE), 1.045f, TEST_LOCATION);
+
+  label.SetProperty(DevelTextLabel::Property::RENDER_SCALE, 1.0f);
+  DALI_TEST_EQUALS(label.GetProperty<float>(DevelTextLabel::Property::RENDER_SCALE), 1.0f, TEST_LOCATION);
+
+  // Invalid value
+  label.SetProperty(DevelTextLabel::Property::RENDER_SCALE, 0.5f);
+  DALI_TEST_EQUALS(label.GetProperty<float>(DevelTextLabel::Property::RENDER_SCALE), 1.0f, TEST_LOCATION);
+
+  label.SetProperty(TextLabel::Property::PIXEL_SNAP_FACTOR, 0.0f);
+  DALI_TEST_EQUALS(label.GetProperty<float>(TextLabel::Property::PIXEL_SNAP_FACTOR), 0.0f, TEST_LOCATION);
+
   // Check font variations property
   Property::Map fontVariationsMapSet;
   Property::Map fontVariationsMapGet;
index ab0bb3a91afa0d6d9a9666d120dc7cde7cbbe89e..c123eb74cbca060558b9de669794add156030168 100644 (file)
@@ -296,6 +296,17 @@ enum Type
    * @note This property can be used only when using variable fonts.
    */
   FONT_VARIATIONS,
+
+  /**
+   * @brief Renders a texture at a given scale.
+   * @details Name "renderScale", type Property::FLOAT.
+   * @note This property is only available in ASYNC_AUTO, ASYNC_MANUAL.
+   * RenderScale is only valid when it is 1.0f or greater.
+   * Renders by scaling up the point size and texture size to the given scale.
+   * However, the size of the text control does not change.
+   * When using Actor::SetScale(), setting RenderScale to the same scale can ensure the rendering quality of the text.
+   */
+  RENDER_SCALE,
 };
 
 } // namespace Property
index 1a1d32dbcd6a942f4884efd7351ff570ddd116b1..e4c796a1388145a0604ac70418ca2d17fbdcd830 100644 (file)
@@ -156,12 +156,14 @@ DALI_DEVEL_PROPERTY_REGISTRATION_READ_ONLY(Toolkit, TextLabel, "asyncLineCount",
 DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit,           TextLabel, "ellipsisMode",                 INTEGER, ELLIPSIS_MODE                  )
 DALI_DEVEL_PROPERTY_REGISTRATION_READ_ONLY(Toolkit, TextLabel, "isScrolling",                  BOOLEAN, IS_SCROLLING                   )
 DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit,           TextLabel, "fontVariations",               MAP,     FONT_VARIATIONS                )
+DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit,           TextLabel, "renderScale",                  FLOAT,   RENDER_SCALE                   )
 
-DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(Toolkit, TextLabel, "textColor",      Color::BLACK,     TEXT_COLOR   )
-DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorRed",   TEXT_COLOR_RED,   TEXT_COLOR, 0)
-DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorGreen", TEXT_COLOR_GREEN, TEXT_COLOR, 1)
-DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorBlue",  TEXT_COLOR_BLUE,  TEXT_COLOR, 2)
-DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorAlpha", TEXT_COLOR_ALPHA, TEXT_COLOR, 3)
+DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(Toolkit, TextLabel, "textColor",       Color::BLACK,     TEXT_COLOR       )
+DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorRed",    TEXT_COLOR_RED,   TEXT_COLOR,     0)
+DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorGreen",  TEXT_COLOR_GREEN, TEXT_COLOR,     1)
+DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorBlue",   TEXT_COLOR_BLUE,  TEXT_COLOR,     2)
+DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorAlpha",  TEXT_COLOR_ALPHA, TEXT_COLOR,     3)
+DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit,              TextLabel, "pixelSnapFactor", FLOAT,            PIXEL_SNAP_FACTOR)
 
 DALI_SIGNAL_REGISTRATION(Toolkit, TextLabel, "anchorClicked",               SIGNAL_ANCHOR_CLICKED                 )
 DALI_SIGNAL_REGISTRATION(Toolkit, TextLabel, "textFitChanged",              SIGNAL_TEXT_FIT_CHANGED               )
@@ -715,6 +717,23 @@ void TextLabel::SetProperty(BaseObject* object, Property::Index index, const Pro
         impl.mIsAsyncRenderNeeded = true;
         break;
       }
+      case Toolkit::DevelTextLabel::Property::RENDER_SCALE:
+      {
+        float renderScale = value.Get<float>();
+        if(renderScale < 1.0f)
+        {
+          DALI_LOG_DEBUG_INFO("RenderScale must be greater than or equal to 1.0f. It will change as follows:%f -> 1.0\n", renderScale);
+          renderScale = 1.0f;
+        }
+
+        if(fabsf(renderScale - impl.mController->GetRenderScale()) > Math::MACHINE_EPSILON_1)
+        {
+          impl.mController->SetRenderScale(renderScale);
+          impl.mIsAsyncRenderNeeded = true;
+          impl.RequestTextRelayout();
+        }
+        break;
+      }
     }
 
     // Request relayout when text update is needed. It's necessary to call it
@@ -1038,6 +1057,11 @@ Property::Value TextLabel::GetProperty(BaseObject* object, Property::Index index
         value = variationsMap;
         break;
       }
+      case Toolkit::DevelTextLabel::Property::RENDER_SCALE:
+      {
+        value = impl.mController->GetRenderScale();
+        break;
+      }
     }
   }
 
@@ -1137,6 +1161,7 @@ void TextLabel::OnInitialize()
 
   TextVisual::SetAsyncTextInterface(mVisual, this);
   TextVisual::SetAnimatableTextColorProperty(mVisual, Toolkit::TextLabel::Property::TEXT_COLOR);
+  self.SetProperty(Toolkit::TextLabel::Property::PIXEL_SNAP_FACTOR, 0.0f);
 
   mController = TextVisual::GetController(mVisual);
   DALI_ASSERT_DEBUG(mController && "Invalid Text Controller")
@@ -1641,10 +1666,10 @@ AsyncTextParameters TextLabel::GetAsyncTextParameters(const Async::RequestType r
   parameters.cutout                      = mController->IsTextCutout();
   parameters.backgroundWithCutoutEnabled = mController->IsBackgroundWithCutoutEnabled();
   parameters.backgroundColorWithCutout   = mController->GetBackgroundColorWithCutout();
-
   Property::Map variationsMap;
   mController->GetVariationsMap(variationsMap);
   parameters.variationsMap = variationsMap;
+  parameters.renderScale   = mController->GetRenderScale();
 
   return parameters;
 }
@@ -1715,16 +1740,11 @@ void TextLabel::SetUpAutoScrolling()
 void TextLabel::AsyncSetupAutoScroll(Text::AsyncTextRenderInfo renderInfo)
 {
   // Pure Virtual from AsyncTextInterface
-  Size verifiedSize(static_cast<float>(renderInfo.width), static_cast<float>(renderInfo.height));
-
-  Size  controlSize = renderInfo.controlSize;
-  float wrapGap     = renderInfo.autoScrollWrapGap;
-
-  PixelData data    = renderInfo.autoScrollPixelData;
-  Texture   texture = Texture::New(Dali::TextureType::TEXTURE_2D,
-                                 data.GetPixelFormat(),
-                                 data.GetWidth(),
-                                 data.GetHeight());
+  Size      verifiedSize = renderInfo.size;
+  Size      controlSize  = renderInfo.controlSize;
+  float     wrapGap      = renderInfo.autoScrollWrapGap;
+  PixelData data         = renderInfo.autoScrollPixelData;
+  Texture   texture      = Texture::New(Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(), data.GetWidth(), data.GetHeight());
   texture.Upload(data);
 
   TextureSet textureSet = TextureSet::New();
index 8a74212432a72f1dad878cf986ade51448cd7162..bba36f46d869038e51c83cf9416a17d17f15785a 100644 (file)
@@ -16,6 +16,8 @@ UNIFORM_BLOCK VertBlock
   UNIFORM highp float uHorizontalAlign;
   UNIFORM highp float uVerticalAlign;
   UNIFORM highp mat4 uMvpMatrix;
+  UNIFORM highp vec3 uScale;
+  UNIFORM highp float pixelSnapFactor;
 };
 
 UNIFORM_BLOCK VisualVertBlock
@@ -39,5 +41,14 @@ void main()
 
   highp vec4 vertexPosition = vec4( ( aPosition + anchorPoint ) * visualSize + visualOffset + origin * uSize.xy, 0.0, 1.0 );
 
+  vec2 snappedPosition = vertexPosition.xy;
+  snappedPosition.x = floor(snappedPosition.x * uScale.x + 0.5) / uScale.x;
+  snappedPosition.y = floor(snappedPosition.y * uScale.y + 0.5) / uScale.y;
+
+  snappedPosition.x = snappedPosition.x + (1.0 - abs(mod(uSize.x, 2.0) - 1.0)) * 0.5;
+  snappedPosition.y = snappedPosition.y + (1.0 - abs(mod(uSize.y, 2.0) - 1.0)) * 0.5;
+
+  vertexPosition.xy = mix(vertexPosition.xy, snappedPosition, pixelSnapFactor);
+
   gl_Position = uMvpMatrix * vertexPosition;
 }
\ No newline at end of file
index d5a529bdbe64c6e450689a5213ccb22c79a4d3d3..f3494fe2696e3578af427df5a975f5e2a7cea706 100644 (file)
@@ -11,6 +11,8 @@ UNIFORM_BLOCK VertBlock
 {
   UNIFORM highp mat4 uMvpMatrix;
   UNIFORM highp vec3 uSize;
+  UNIFORM highp vec3 uScale;
+  UNIFORM highp float pixelSnapFactor;
 };
 
 UNIFORM_BLOCK VisualVertBlock
@@ -28,7 +30,18 @@ vec4 ComputeVertexPosition()
 {
   vec2 visualSize = mix(size * uSize.xy, size, offsetSizeMode.zw ) + extraSize;
   vec2 visualOffset = mix(offset * uSize.xy, offset, offsetSizeMode.xy);
-  return vec4( (aPosition + anchorPoint) * visualSize + visualOffset + origin * uSize.xy, 0.0, 1.0 );
+  vec4 result = vec4( (aPosition + anchorPoint) * visualSize + visualOffset + origin * uSize.xy, 0.0, 1.0 );
+
+  vec2 snappedPosition = result.xy;
+  snappedPosition.x = floor(snappedPosition.x * uScale.x + 0.5) / uScale.x;
+  snappedPosition.y = floor(snappedPosition.y * uScale.y + 0.5) / uScale.y;
+
+  snappedPosition.x = snappedPosition.x + (1.0 - abs(mod(uSize.x, 2.0) - 1.0)) * 0.5;
+  snappedPosition.y = snappedPosition.y + (1.0 - abs(mod(uSize.y, 2.0) - 1.0)) * 0.5;
+
+  result.xy = mix(result.xy, snappedPosition, pixelSnapFactor);
+
+  return result;
 }
 
 void main()
index d2c335d57b0fa70aaa736afedee92e38e6a7a0b4..31af9d6b166d939b8ae9a3ce99a7ff50b684a3dc 100644 (file)
@@ -48,6 +48,12 @@ const float VERTICAL_ALIGNMENT_TABLE[Text::VerticalAlignment::BOTTOM + 1] =
     1.0f  // VerticalAlignment::BOTTOM
 };
 
+float ConvertToEven(float value)
+{
+  int intValue(static_cast<int>(value));
+  return static_cast<float>(intValue + (intValue & 1));
+}
+
 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_ASYNC, false);
 } // namespace
 
@@ -397,8 +403,7 @@ void AsyncTextLoader::Update(AsyncTextParameters& parameters)
   // Validate Fonts.
   ////////////////////////////////////////////////////////////////////////////////
 
-  float scale = parameters.fontSizeScale;
-
+  float                            scale            = parameters.fontSizeScale * parameters.renderScale;
   TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE * scale;
 
   //Get the number of points per one unit of point-size
@@ -928,8 +933,18 @@ AsyncTextRenderInfo AsyncTextLoader::Render(AsyncTextParameters& parameters)
 
   // Set information for creating pixel datas.
   AsyncTextRenderInfo renderInfo;
-  renderInfo.width  = static_cast<uint32_t>(layoutSize.x);
-  renderInfo.height = static_cast<uint32_t>(layoutSize.y);
+
+  bool isRenderScale = parameters.renderScale > 1.0f ? true : false;
+  if(isRenderScale)
+  {
+    float width     = (layoutSize.width / parameters.renderScale) * (layoutSize.width / ((layoutSize.width / parameters.renderScale) * parameters.renderScale));
+    float height    = (layoutSize.height / parameters.renderScale) * (layoutSize.height / ((layoutSize.height / parameters.renderScale) * parameters.renderScale));
+    renderInfo.size = Size(width, height);
+  }
+  else
+  {
+    renderInfo.size = layoutSize;
+  }
 
   // Set the direction of text.
   renderInfo.isTextDirectionRTL = mIsTextDirectionRTL;
@@ -991,23 +1006,32 @@ AsyncTextRenderInfo AsyncTextLoader::Render(AsyncTextParameters& parameters)
 
   if(cutoutEnabled)
   {
-    renderInfo.renderedSize = Size(static_cast<float>(renderInfo.width), static_cast<float>(renderInfo.height));
+    renderInfo.renderedSize = renderInfo.size;
   }
   else
   {
-    renderInfo.renderedSize = Size(parameters.textWidth, parameters.textHeight);
+    float renderedWidth     = isRenderScale ? parameters.renderScaleWidth : parameters.textWidth;
+    float renderedHeight    = isRenderScale ? parameters.renderScaleHeight : parameters.textHeight;
+    renderInfo.renderedSize = Size(renderedWidth, renderedHeight);
   }
 
   return renderInfo;
 }
 
-AsyncTextRenderInfo AsyncTextLoader::RenderText(AsyncTextParameters& parameters)
+AsyncTextRenderInfo AsyncTextLoader::RenderText(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize)
 {
   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_ASYNC_RENDER_TEXT");
 
+  Size textNaturalSize   = naturalSize;
+  bool cachedNaturalSize = useCachedNaturalSize;
+
   if(parameters.requestType == Async::RENDER_CONSTRAINT)
   {
-    Size textNaturalSize = ComputeNaturalSize(parameters);
+    if(!cachedNaturalSize)
+    {
+      textNaturalSize   = ComputeNaturalSize(parameters);
+      cachedNaturalSize = true;
+    }
     // textWidth is widthConstraint
     if(parameters.textWidth > textNaturalSize.width)
     {
@@ -1020,7 +1044,7 @@ AsyncTextRenderInfo AsyncTextLoader::RenderText(AsyncTextParameters& parameters)
     // In case of CONSTRAINT, the natural size has already been calculated.
     // So we can skip Initialize and Update at this stage.
     // Only the layout is newly calculated to obtain the height.
-    bool  layoutOnly = (parameters.requestType == Async::RENDER_CONSTRAINT);
+    bool  layoutOnly = cachedNaturalSize;
     float height     = ComputeHeightForWidth(parameters, parameters.textWidth, layoutOnly);
 
     // textHeight is heightConstraint.
@@ -1039,8 +1063,11 @@ AsyncTextRenderInfo AsyncTextLoader::RenderText(AsyncTextParameters& parameters)
   }
   else
   {
-    Initialize();
-    Update(parameters);
+    if(!cachedNaturalSize)
+    {
+      Initialize();
+      Update(parameters);
+    }
     bool layoutUpdated = false;
     Layout(parameters, layoutUpdated);
   }
@@ -1080,6 +1107,98 @@ float AsyncTextLoader::ComputeHeightForWidth(AsyncTextParameters& parameters, fl
   return layoutSize.height;
 }
 
+Size AsyncTextLoader::SetupRenderScale(AsyncTextParameters& parameters, bool& cachedNaturalSize)
+{
+  if(parameters.isTextFitEnabled || parameters.isTextFitArrayEnabled)
+  {
+    // If text fit, only update the scaled size.
+    parameters.renderScaleWidth  = parameters.textWidth;
+    parameters.renderScaleHeight = parameters.textHeight;
+    parameters.textWidth         = ConvertToEven(ceil(parameters.textWidth * parameters.renderScale));
+    parameters.textHeight        = ConvertToEven(ceil(parameters.textHeight * parameters.renderScale));
+    parameters.minLineSize       = ConvertToEven(ceil(parameters.minLineSize * parameters.renderScale));
+    cachedNaturalSize            = false;
+    return Size::ZERO;
+  }
+
+  float renderScale = parameters.renderScale;
+  // Set render scale to 1.0 to compute the original scale natural size.
+  parameters.renderScale   = 1.0f;
+  Size originalNaturalSize = ComputeNaturalSize(parameters);
+
+  // Restore render scale.
+  parameters.renderScale = renderScale;
+
+  // Check if the original text is ellipsized or not.
+  bool widthEllipsized  = parameters.textWidth < originalNaturalSize.width ? true : false;
+  bool heightEllipsized = parameters.textHeight < originalNaturalSize.height ? true : false;
+
+  // Store the computed natural size to avoid redundant calculations.
+  Size naturalSize  = ComputeNaturalSize(parameters);
+  cachedNaturalSize = true;
+
+  // Update the scaled size.
+  parameters.renderScaleWidth  = parameters.textWidth;
+  parameters.renderScaleHeight = parameters.textHeight;
+  parameters.textWidth         = ConvertToEven(ceil(parameters.textWidth * parameters.renderScale));
+  parameters.textHeight        = ConvertToEven(ceil(parameters.textHeight * parameters.renderScale));
+  parameters.minLineSize       = ConvertToEven(ceil(parameters.minLineSize * parameters.renderScale));
+
+  // The texture in RenderScale needs to be resized because it exceeds the control size.
+  if(!widthEllipsized && naturalSize.width > parameters.textWidth)
+  {
+    float renderScaleGap = ceil(naturalSize.width - parameters.textWidth);
+    if(renderScaleGap > 0.0f)
+    {
+      Vector<GlyphInfo>& glyphs         = mTextModel->mVisualModel->mGlyphs;
+      const Length       numberOfGlyphs = static_cast<Length>(glyphs.Count());
+      if(numberOfGlyphs > 1u)
+      {
+        naturalSize.width -= (renderScaleGap - 1.0f);
+        parameters.textWidth = naturalSize.width;
+
+        uint32_t numberOfAdvance = 0u;
+        float    sumOfGap        = 0.0f;
+        float    gap             = renderScaleGap / static_cast<float>(numberOfGlyphs - 1u);
+
+        // Reduce the advance of all glyphs slightly to fit the width to the control size.
+        // Reducing the advance of the last glyph is pointless.
+        for(Length index = 0u; index < numberOfGlyphs - 1u; index++)
+        {
+          if(glyphs[index].advance > 0.0f)
+          {
+            glyphs[index].advance -= gap;
+            numberOfAdvance++;
+            sumOfGap += gap;
+          }
+        }
+
+        // Remove all remaining gaps.
+        if(numberOfAdvance > 0u && fabsf(renderScaleGap - sumOfGap) > Math::MACHINE_EPSILON_1000)
+        {
+          float remainedGap = (renderScaleGap - sumOfGap) / static_cast<float>(numberOfAdvance);
+          for(Length index = 0u; index < numberOfGlyphs - 1u; index++)
+          {
+            if(glyphs[index].advance > 0.0f)
+            {
+              glyphs[index].advance -= remainedGap;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Adjust the size to ensure same ellipsis behavior as the original text.
+  parameters.textWidth  = widthEllipsized ? std::min(parameters.textWidth, naturalSize.width - 1) : std::max(parameters.textWidth, naturalSize.width);
+  parameters.textHeight = heightEllipsized ? std::min(parameters.textHeight, naturalSize.height - 1) : std::max(parameters.textHeight, naturalSize.height);
+
+  // Update the control size because textWidth and textHeight have been adjusted. (Skip Initialize and Update)
+  mTextModel->mVisualModel->mControlSize = Size(parameters.textWidth, parameters.textHeight);
+
+  return naturalSize;
+}
+
 Size AsyncTextLoader::ComputeNaturalSize(AsyncTextParameters& parameters)
 {
 #ifdef TRACE_ENABLED
@@ -1112,7 +1231,8 @@ Size AsyncTextLoader::ComputeNaturalSize(AsyncTextParameters& parameters)
 
 AsyncTextRenderInfo AsyncTextLoader::GetHeightForWidth(AsyncTextParameters& parameters)
 {
-  float               height = ComputeHeightForWidth(parameters, parameters.textWidth, false);
+  float height = ComputeHeightForWidth(parameters, parameters.textWidth, false);
+
   AsyncTextRenderInfo renderInfo;
   renderInfo.renderedSize.width  = parameters.textWidth;
   renderInfo.renderedSize.height = height;
@@ -1124,7 +1244,10 @@ AsyncTextRenderInfo AsyncTextLoader::GetHeightForWidth(AsyncTextParameters& para
 
 AsyncTextRenderInfo AsyncTextLoader::GetNaturalSize(AsyncTextParameters& parameters)
 {
-  Size                textNaturalSize = ComputeNaturalSize(parameters);
+  Size textNaturalSize   = ComputeNaturalSize(parameters);
+  textNaturalSize.width  = ConvertToEven(textNaturalSize.width);
+  textNaturalSize.height = ConvertToEven(textNaturalSize.height);
+
   AsyncTextRenderInfo renderInfo;
   renderInfo.renderedSize = textNaturalSize;
   renderInfo.requestType  = Async::COMPUTE_NATURAL_SIZE;
@@ -1133,16 +1256,24 @@ AsyncTextRenderInfo AsyncTextLoader::GetNaturalSize(AsyncTextParameters& paramet
   return renderInfo;
 }
 
-AsyncTextRenderInfo AsyncTextLoader::RenderAutoScroll(AsyncTextParameters& parameters)
+AsyncTextRenderInfo AsyncTextLoader::RenderAutoScroll(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize)
 {
   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_ASYNC_RENDER_AUTO_SCROLL");
 
   Size controlSize(parameters.textWidth, parameters.textHeight);
 
   // As relayout of text may not be done at this point natural size is used to get size. Single line scrolling only.
-  Size textNaturalSize = ComputeNaturalSize(parameters);
-  textNaturalSize.width += (parameters.padding.start + parameters.padding.end);
-  textNaturalSize.height += (parameters.padding.top + parameters.padding.bottom);
+  Size textNaturalSize;
+  if(useCachedNaturalSize)
+  {
+    textNaturalSize = naturalSize;
+  }
+  else
+  {
+    textNaturalSize = ComputeNaturalSize(parameters);
+    textNaturalSize.width += (parameters.padding.start + parameters.padding.end);
+    textNaturalSize.height += (parameters.padding.top + parameters.padding.bottom);
+  }
 
   if(parameters.requestType == Async::RENDER_FIXED_WIDTH || parameters.requestType == Async::RENDER_CONSTRAINT)
   {
@@ -1201,10 +1332,12 @@ AsyncTextRenderInfo AsyncTextLoader::RenderAutoScroll(AsyncTextParameters& param
   parameters.textWidth = actualWidth;
 
   // Store the control size and calculated wrap gap in render info.
-  renderInfo.controlSize       = controlSize;
+  bool  isRenderScale          = parameters.renderScale > 1.0f ? true : false;
+  float renderedWidth          = isRenderScale ? parameters.renderScaleWidth : controlSize.width;
+  float renderedHeight         = isRenderScale ? parameters.renderScaleHeight : controlSize.height;
+  renderInfo.controlSize       = Size(renderedWidth, renderedHeight);
+  renderInfo.renderedSize      = Size(renderedWidth, renderedHeight);
   renderInfo.autoScrollWrapGap = wrapGap;
-  renderInfo.renderedSize      = controlSize;
-
   return renderInfo;
 }
 
@@ -1224,11 +1357,18 @@ bool AsyncTextLoader::CheckForTextFit(AsyncTextParameters& parameters, float poi
   return true;
 }
 
-AsyncTextRenderInfo AsyncTextLoader::RenderTextFit(AsyncTextParameters& parameters)
+AsyncTextRenderInfo AsyncTextLoader::RenderTextFit(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize)
 {
+  Size textNaturalSize   = naturalSize;
+  bool cachedNaturalSize = useCachedNaturalSize;
+
   if(parameters.requestType == Async::RENDER_CONSTRAINT)
   {
-    Size textNaturalSize = ComputeNaturalSize(parameters);
+    if(!cachedNaturalSize)
+    {
+      textNaturalSize   = ComputeNaturalSize(parameters);
+      cachedNaturalSize = true;
+    }
     // textWidth is widthConstraint
     if(parameters.textWidth > textNaturalSize.width)
     {
@@ -1241,7 +1381,7 @@ AsyncTextRenderInfo AsyncTextLoader::RenderTextFit(AsyncTextParameters& paramete
     // In case of CONSTRAINT, the natural size has already been calculated.
     // So we can skip Initialize and Update at this stage.
     // Only the layout is newly calculated to obtain the height.
-    bool  layoutOnly = (parameters.requestType == Async::RENDER_CONSTRAINT);
+    bool  layoutOnly = cachedNaturalSize;
     float height     = ComputeHeightForWidth(parameters, parameters.textWidth, layoutOnly);
 
     // textHeight is heightConstraint
index 371fb2cd125d104b1c3784ce38f3ef1baf2753de..90086ace47c55478a601dd16fda17d0fe80f774a 100644 (file)
@@ -94,20 +94,30 @@ public:
 
 
   // Worker thread
+  /**
+   * @copydoc Dali::AsyncTextLoader::SetupRenderScale()
+   */
+  Size SetupRenderScale(AsyncTextParameters& parameters, bool& cachedNaturalSize);
+
+  /**
+   * @copydoc Dali::AsyncTextLoader::ComputeNaturalSize()
+   */
+  Size ComputeNaturalSize(AsyncTextParameters& parameters);
+
   /**
    * @copydoc Dali::AsyncTextLoader::RenderText()
    */
-  AsyncTextRenderInfo RenderText(AsyncTextParameters& parameters);
+  AsyncTextRenderInfo RenderText(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize);
 
   /**
    * @copydoc Dali::AsyncTextLoader::RenderTextFit()
    */
-  AsyncTextRenderInfo RenderTextFit(AsyncTextParameters& parameters);
+  AsyncTextRenderInfo RenderTextFit(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize);
 
   /**
    * @copydoc Dali::AsyncTextLoader::RenderAutoScroll()
    */
-  AsyncTextRenderInfo RenderAutoScroll(AsyncTextParameters& parameters);
+  AsyncTextRenderInfo RenderAutoScroll(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize);
 
   /**
    * @copydoc Dali::AsyncTextLoader::GetNaturalSize()
@@ -157,15 +167,6 @@ private:
    */
   AsyncTextRenderInfo Render(AsyncTextParameters& parameters);
 
-  /**
-   * @brief Compute natural size of text.
-   *
-   * @param[in] parameters All options required to compute size of text.
-   *
-   * @return The natural size of text.
-   */
-  Size ComputeNaturalSize(AsyncTextParameters& parameters);
-
   /**
    * @brief Compute height for width of text.
    *
index 3298d8427ffd1a2ecff935cbca710fbc83e29f77..675d63931b6cadea808c35ec3cf0db8fe9841d38 100644 (file)
@@ -82,19 +82,29 @@ AsyncTextLoader AsyncTextLoader::New()
   return AsyncTextLoader(asyncTextLoaderImpl);
 }
 
-AsyncTextRenderInfo AsyncTextLoader::RenderText(AsyncTextParameters& parameters)
+Size AsyncTextLoader::SetupRenderScale(AsyncTextParameters& parameters, bool& cachedNaturalSize)
 {
-  return GetImplementation(*this).RenderText(parameters);
+  return GetImplementation(*this).SetupRenderScale(parameters, cachedNaturalSize);
 }
 
-AsyncTextRenderInfo AsyncTextLoader::RenderTextFit(AsyncTextParameters& parameters)
+Size AsyncTextLoader::ComputeNaturalSize(AsyncTextParameters& parameters)
 {
-  return GetImplementation(*this).RenderTextFit(parameters);
+  return GetImplementation(*this).ComputeNaturalSize(parameters);
 }
 
-AsyncTextRenderInfo AsyncTextLoader::RenderAutoScroll(AsyncTextParameters& parameters)
+AsyncTextRenderInfo AsyncTextLoader::RenderText(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize)
 {
-  return GetImplementation(*this).RenderAutoScroll(parameters);
+  return GetImplementation(*this).RenderText(parameters, useCachedNaturalSize, naturalSize);
+}
+
+AsyncTextRenderInfo AsyncTextLoader::RenderTextFit(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize)
+{
+  return GetImplementation(*this).RenderTextFit(parameters, useCachedNaturalSize, naturalSize);
+}
+
+AsyncTextRenderInfo AsyncTextLoader::RenderAutoScroll(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize)
+{
+  return GetImplementation(*this).RenderAutoScroll(parameters, useCachedNaturalSize, naturalSize);
 }
 
 AsyncTextRenderInfo AsyncTextLoader::GetNaturalSize(AsyncTextParameters& parameters)
index 4b8df0b56843eb0391816e0334e852767277c185..0c21ec5b1980a26f2d7cabf24e63217e143a799b 100644 (file)
@@ -66,21 +66,20 @@ namespace Async
 struct AsyncTextParameters
 {
   AsyncTextParameters()
-  : requestType{Async::RENDER_FIXED_SIZE},
-    manualRender{false},
-    maxTextureSize{0},
-    text{},
-    fontSize{0.f},
-    textColor{Color::BLACK},
+  : text{},
     fontFamily{},
-    fontWeight{FontWeight::NONE},
-    fontWidth{FontWidth::NONE},
-    fontSlant{FontSlant::NONE},
-    isMultiLine{false},
-    ellipsis{true},
-    enableMarkup{false},
-    removeFrontInset{true},
-    removeBackInset{true},
+    textColor{Color::BLACK},
+    underlineColor{Color::BLACK},
+    strikethroughColor{Color::BLACK},
+    shadowColor{Color::BLACK},
+    outlineColor{Color::WHITE},
+    backgroundColorWithCutout{Color::TRANSPARENT},
+    shadowOffset{},
+    outlineOffset{},
+    padding{0u, 0u, 0u, 0u},
+    variationsMap{},
+    textFitArray{},
+    fontSize{0.f},
     minLineSize{0.f},
     lineSpacing{0.f},
     relativeLineSize{1.f},
@@ -88,48 +87,52 @@ struct AsyncTextParameters
     fontSizeScale{1.f},
     textWidth{0.f},
     textHeight{0.f},
-    padding{0u, 0u, 0u, 0u},
-    horizontalAlignment{Text::HorizontalAlignment::BEGIN},
-    verticalAlignment{Text::VerticalAlignment::TOP},
-    verticalLineAlignment{DevelText::VerticalLineAlignment::TOP},
-    lineWrapMode{Text::LineWrap::WORD},
-    layoutDirection{Dali::LayoutDirection::LEFT_TO_RIGHT},
-    layoutDirectionPolicy{DevelText::MatchLayoutDirection::INHERIT},
-    ellipsisPosition{DevelText::EllipsisPosition::END},
-    ellipsisMode{DevelText::Ellipsize::TRUNCATE},
-    isUnderlineEnabled{false},
-    underlineType{Text::Underline::SOLID},
-    underlineColor{Color::BLACK},
     underlineHeight{0.f},
     dashedUnderlineWidth{2.f},
     dashedUnderlineGap{1.f},
-    isStrikethroughEnabled{false},
-    strikethroughColor{Color::BLACK},
     strikethroughHeight{0.f},
     shadowBlurRadius{0.f},
-    shadowColor{Color::BLACK},
-    shadowOffset{},
-    outlineWidth{0u},
-    outlineColor{Color::WHITE},
     outlineBlurRadius{0.f},
-    outlineOffset{},
-    isTextFitEnabled{false},
     textFitMinSize{10.f},
     textFitMaxSize{100.f},
     textFitStepSize{1.f},
-    isTextFitArrayEnabled{false},
-    textFitArray{},
-    isAutoScrollEnabled{false},
-    autoScrollStopMode{TextLabel::AutoScrollStopMode::FINISH_LOOP},
+    autoScrollLoopDelay{0.0f},
+    renderScale{1.0f},
+    renderScaleWidth{0.f},
+    renderScaleHeight{0.f},
+    maxTextureSize{0},
     autoScrollSpeed{1},
     autoScrollLoopCount{1},
-    autoScrollLoopDelay{0.0f},
     autoScrollGap{0},
+    outlineWidth{0u},
+    requestType{Async::RENDER_FIXED_SIZE},
+    horizontalAlignment{Text::HorizontalAlignment::BEGIN},
+    verticalAlignment{Text::VerticalAlignment::TOP},
+    lineWrapMode{Text::LineWrap::WORD},
+    underlineType{Text::Underline::SOLID},
+    layoutDirection{Dali::LayoutDirection::LEFT_TO_RIGHT},
+    verticalLineAlignment{DevelText::VerticalLineAlignment::TOP},
+    layoutDirectionPolicy{DevelText::MatchLayoutDirection::INHERIT},
+    ellipsisPosition{DevelText::EllipsisPosition::END},
+    ellipsisMode{DevelText::Ellipsize::TRUNCATE},
+    autoScrollStopMode{TextLabel::AutoScrollStopMode::FINISH_LOOP},
+    fontWeight{FontWeight::NONE},
+    fontWidth{FontWidth::NONE},
+    fontSlant{FontSlant::NONE},
+    manualRender{false},
+    isMultiLine{false},
+    ellipsis{true},
+    enableMarkup{false},
+    removeFrontInset{true},
+    removeBackInset{true},
+    isUnderlineEnabled{false},
+    isStrikethroughEnabled{false},
+    isTextFitEnabled{false},
+    isTextFitArrayEnabled{false},
+    isAutoScrollEnabled{false},
     isAutoScrollMaxTextureExceeded{false},
     cutout{false},
-    backgroundWithCutoutEnabled{false},
-    backgroundColorWithCutout{Color::TRANSPARENT},
-    variationsMap{}
+    backgroundWithCutoutEnabled{false}
   {
   }
 
@@ -137,85 +140,82 @@ struct AsyncTextParameters
   {
   }
 
-  Async::RequestType requestType;
-  bool               manualRender : 1;
-
-  int         maxTextureSize; ///< The maximum size of texture.
-  std::string text;           ///< The text to be rendered encoded in utf8.
-  float       fontSize;       ///< The font's size (in points).
-  Vector4     textColor;      ///< The default text's color. Default is white.
-
-  std::string fontFamily;     ///< The font's family.
-  FontWeight  fontWeight;     ///< The font's weight.
-  FontWidth   fontWidth;      ///< The font's width.
-  FontSlant   fontSlant;      ///< The font's slant.
-
-  bool isMultiLine      : 1; ///< Whether the multi-line layout is enabled.
-  bool ellipsis         : 1; ///< Whether the ellipsis layout option is enabled.
-  bool enableMarkup     : 1; ///< Whether the mark-up processor is enabled.
-  bool removeFrontInset : 1; ///< Whether to ignore xBearing of the first glyph. Default is true.
-  bool removeBackInset  : 1; ///< Whether to ignore advance of the last glyph. Default is true.
-
-  float minLineSize;      ///< The line's minimum size (in pixels).
-  float lineSpacing;      ///< The default extra space between lines in points. (in pixels).
-  float relativeLineSize; ///< The relative height of the line (a factor that will be multiplied by text height).
-  float characterSpacing; ///< The space between characters.
-  float fontSizeScale;    ///< The font's size scale.
-
-  float textWidth;        ///< The width in pixels of the boundaries where the text is going to be laid-out.
-  float textHeight;       ///< The height in pixels of the boundaries where the text is going to be laid-out.
-  Extents  padding;       ///< The padding of the boundaries where the text is going to be laid-out.
+  std::string text;       ///< The text to be rendered encoded in utf8.
+  std::string fontFamily; ///< The font's family.
+
+  Vector4 textColor;                 ///< The default text's color. Default is white.
+  Vector4 underlineColor;
+  Vector4 strikethroughColor;
+  Vector4 shadowColor;
+  Vector4 outlineColor;
+  Vector4 backgroundColorWithCutout; ///< Background color with cutout.
+
+  Vector2 shadowOffset;
+  Vector2 outlineOffset;
+
+  Extents padding;       ///< The padding of the boundaries where the text is going to be laid-out.
 
+  Property::Map variationsMap; ///< The map for variable fonts. it might be replaced by variable map run.
+  std::vector<DevelTextLabel::FitOption> textFitArray;
+
+  float fontSize;             ///< The font's size (in points).
+  float minLineSize;          ///< The line's minimum size (in pixels).
+  float lineSpacing;          ///< The default extra space between lines in points. (in pixels).
+  float relativeLineSize;     ///< The relative height of the line (a factor that will be multiplied by text height).
+  float characterSpacing;     ///< The space between characters.
+  float fontSizeScale;        ///< The font's size scale.
+  float textWidth;            ///< The width in pixels of the boundaries where the text is going to be laid-out.
+  float textHeight;           ///< The height in pixels of the boundaries where the text is going to be laid-out.
+  float underlineHeight;
+  float dashedUnderlineWidth;
+  float dashedUnderlineGap;
+  float strikethroughHeight;
+  float shadowBlurRadius;
+  float outlineBlurRadius;
+  float textFitMinSize;
+  float textFitMaxSize;
+  float textFitStepSize;
+  float autoScrollLoopDelay;
+  float renderScale;          ///< The render scale.
+  float renderScaleWidth;     ///< The requested original textWidth when using render scale.
+  float renderScaleHeight;    ///< The requested original textHeight when using render scale.
+
+  int maxTextureSize;      ///< The maximum size of texture.
+  int autoScrollSpeed;     ///< auto scroll properties.
+  int autoScrollLoopCount;
+  int autoScrollGap;
+
+  uint16_t outlineWidth; ///< The width of the outline, if it is greater than 1, it is enabled.
+
+  Async::RequestType requestType;
   Text::HorizontalAlignment::Type        horizontalAlignment;   ///< The horizontal alignment: one of {BEGIN, CENTER, END}.
   Text::VerticalAlignment::Type          verticalAlignment;     ///< The vertical alignment: one of {TOP, CENTER, BOTTOM}.
-  DevelText::VerticalLineAlignment::Type verticalLineAlignment; ///< The vertical line alignment: one of {TOP, MIDDLE, BOTTOM}.
   Text::LineWrap::Mode                   lineWrapMode;          ///< The line wrap mode: one of {WORD, CHARACTER, HYPHENATION, MIXED}.
+  Text::Underline::Type                  underlineType;         ///< The type of underline: one of {SOLID, DASHED, DOUBLE}.
   Dali::LayoutDirection::Type            layoutDirection;       ///< The layout direction: one of {LEFT_TO_RIGHT, RIGHT_TO_LEFT}.
+  DevelText::VerticalLineAlignment::Type verticalLineAlignment; ///< The vertical line alignment: one of {TOP, MIDDLE, BOTTOM}.
   DevelText::MatchLayoutDirection        layoutDirectionPolicy; ///< The policy used to set the text layout direction : one of {INHERIT, LOCALE, CONTENTS}.
   DevelText::EllipsisPosition::Type      ellipsisPosition;      ///< The position of the ellipsis glyph: one of {END, START, MIDDLE}.
   DevelText::Ellipsize::Mode             ellipsisMode;          ///< The mode of the ellipsis: one of {TRUNCATE, AUTO_SCROLL}.
-
-  bool                  isUnderlineEnabled : 1;     ///< Underline properties
-  Text::Underline::Type underlineType;
-  Vector4               underlineColor;
-  float                 underlineHeight;
-  float                 dashedUnderlineWidth;
-  float                 dashedUnderlineGap;
-
-  bool                  isStrikethroughEnabled : 1; ///< Strikethrough properties
-  Vector4               strikethroughColor;
-  float                 strikethroughHeight;
-
-  float                 shadowBlurRadius;           ///< Shadow properties
-  Vector4               shadowColor;
-  Vector2               shadowOffset;
-
-  uint16_t              outlineWidth;               ///< Outline properties
-  Vector4               outlineColor;
-  float                 outlineBlurRadius;
-  Vector2               outlineOffset;
-
-  bool                  isTextFitEnabled : 1;       ///< TextFit
-  float                 textFitMinSize;
-  float                 textFitMaxSize;
-  float                 textFitStepSize;
-
-  bool                                   isTextFitArrayEnabled : 1; ///< TextFitArray
-  std::vector<DevelTextLabel::FitOption> textFitArray;
-
-  bool                                   isAutoScrollEnabled : 1;   ///< Auto scroll
-  TextLabel::AutoScrollStopMode::Type    autoScrollStopMode;
-  int                                    autoScrollSpeed;
-  int                                    autoScrollLoopCount;
-  float                                  autoScrollLoopDelay;
-  int                                    autoScrollGap;
-  bool                                   isAutoScrollMaxTextureExceeded : 1;
-
-  bool          cutout                      : 1; ///< Cutout enabled flag
-  bool          backgroundWithCutoutEnabled : 1; ///< Background with cutout enabled flag.
-  Vector4       backgroundColorWithCutout;    ///< Background color with cutout.
-
-  Property::Map variationsMap;          ///< The map for variable fonts. it might be replaced by variable map run.
+  TextLabel::AutoScrollStopMode::Type    autoScrollStopMode;    ///< The auto scroll stop mode: one of {FINISH_LOOP, IMMEDIATE}.
+  FontWeight                             fontWeight;            ///< The font's weight.
+  FontWidth                              fontWidth;             ///< The font's width.
+  FontSlant                              fontSlant;             ///< The font's slant.
+
+  bool manualRender                   : 1; ///< Whether the manual rendered or not.
+  bool isMultiLine                    : 1; ///< Whether the multi-line layout is enabled.
+  bool ellipsis                       : 1; ///< Whether the ellipsis layout option is enabled.
+  bool enableMarkup                   : 1; ///< Whether the mark-up processor is enabled.
+  bool removeFrontInset               : 1; ///< Whether to ignore xBearing of the first glyph. Default is true.
+  bool removeBackInset                : 1; ///< Whether to ignore advance of the last glyph. Default is true.
+  bool isUnderlineEnabled             : 1; ///< Underline enabeld flag.
+  bool isStrikethroughEnabled         : 1; ///< Strikethrough enabeld flag.
+  bool isTextFitEnabled               : 1; ///< TextFit enabeld flag.
+  bool isTextFitArrayEnabled          : 1; ///< TextFitArray enabeld flag.
+  bool isAutoScrollEnabled            : 1; ///< Auto scroll enabeld flag.
+  bool isAutoScrollMaxTextureExceeded : 1; ///< Whether the auto scroll texture size exceeds the maximum texture width.
+  bool cutout                         : 1; ///< Cutout enabled flag.
+  bool backgroundWithCutoutEnabled    : 1; ///< Background with cutout enabled flag.
 };
 
 struct AsyncTextRenderInfo
@@ -227,8 +227,7 @@ struct AsyncTextRenderInfo
     overlayStylePixelData(),
     maskPixelData(),
     autoScrollPixelData(),
-    width(0u),
-    height(0u),
+    size(),
     controlSize(),
     renderedSize(),
     lineCount(0),
@@ -252,8 +251,7 @@ struct AsyncTextRenderInfo
   PixelData          overlayStylePixelData;
   PixelData          maskPixelData;
   PixelData          autoScrollPixelData;
-  uint32_t           width;
-  uint32_t           height;
+  Size               size;
   Size               controlSize;
   Size               renderedSize;
   int                lineCount;
@@ -352,32 +350,65 @@ public:
    */
   bool IsModuleClearNeeded();
 
+  /**
+   * @brief Setup render scale.
+   * Sets the control size to be rendered to fit the given scale.
+   * The scaled rendering result cannot be exactly the same as the original.
+   * However, we guarantee the ellipsis result.
+   * If the original is ellipsised, the scaled result will always be ellipsised.
+   * If the original is not ellipsised, the scaled result will not be ellipsised.
+   * Occasionally, the scaled result exceeds the size of the control.
+   * Since we need to ensure the size of the control, we slightly reduce the glyph's advance to adjust the total width to fit the control size.
+   * While this may cause rendering quality issues at smaller point sizes, there is almost no noticeable difference at moderate sizes of 20pt or larger.
+   *
+   * @param[in] parameters All options required to compute size of text.
+   * @param[out] cachedNaturalSize Whether the natural size has been calculated.
+   *
+   * @return The natural size of text.
+   */
+  Size SetupRenderScale(AsyncTextParameters& parameters, bool& cachedNaturalSize);
+
+  /**
+   * @brief Compute natural size of text.
+   *
+   * @param[in] parameters All options required to compute size of text.
+   *
+   * @return The natural size of text.
+   */
+  Size ComputeNaturalSize(AsyncTextParameters& parameters);
+
   /**
    * @brief Renders text into a pixel buffer.
    *
    * @param[in] parameters All options required to render text.
+   * @param[in] useCachedNaturalSize Indicates whether to use the provided natural size or calculate it internally.
+   * @param[in] naturalSize The natural size of the text to be used if useCachedNaturalSize is true.
    *
    * @return An AsyncTextRenderInfo.
    */
-  AsyncTextRenderInfo RenderText(AsyncTextParameters& parameters);
+  AsyncTextRenderInfo RenderText(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize);
 
   /**
    * @brief Renders text into a pixel buffer.
    *
    * @param[in] parameters All options required to render text.
+   * @param[in] useCachedNaturalSize Indicates whether to use the provided natural size or calculate it internally.
+   * @param[in] naturalSize The natural size of the text to be used if useCachedNaturalSize is true.
    *
    * @return An AsyncTextRenderInfo.
    */
-  AsyncTextRenderInfo RenderTextFit(AsyncTextParameters& parameters);
+  AsyncTextRenderInfo RenderTextFit(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize);
 
   /**
    * @brief Renders text into a pixel buffer.
    *
    * @param[in] parameters All options required to render text.
+   * @param[in] useCachedNaturalSize Indicates whether to use the provided natural size or calculate it internally.
+   * @param[in] naturalSize The natural size of the text to be used if useCachedNaturalSize is true.
    *
    * @return An AsyncTextRenderInfo.
    */
-  AsyncTextRenderInfo RenderAutoScroll(AsyncTextParameters& parameters);
+  AsyncTextRenderInfo RenderAutoScroll(AsyncTextParameters& parameters, bool useCachedNaturalSize, const Size& naturalSize);
 
   /**
    * @brief Gets the natural size of text.
index 1a4c13ad3d3bdba3ee20bda72773e0ff6c448b3f..c33420ce883669018349ac1f72a8bb88d3e41814 100644 (file)
@@ -93,11 +93,29 @@ void TextLoadingTask::Load()
     case Text::Async::RENDER_FIXED_WIDTH:
     case Text::Async::RENDER_CONSTRAINT:
     {
+      // To avoid duplicate calculation, we can skip Initialize and Update.
+      Size naturalSize       = Size::ZERO;
+      bool cachedNaturalSize = false;
+
+      if(mParameters.renderScale > 1.0f)
+      {
+#ifdef TRACE_ENABLED
+        if(gTraceFilter && gTraceFilter->IsTraceEnabled())
+        {
+          DALI_LOG_RELEASE_INFO("SetupRenderScale : %f\n", mParameters.renderScale);
+        }
+#endif
+        naturalSize = mLoader.SetupRenderScale(mParameters, cachedNaturalSize);
+      }
+
       if(mParameters.ellipsis && !mParameters.isMultiLine && mParameters.ellipsisMode == DevelText::Ellipsize::AUTO_SCROLL)
       {
-        Text::AsyncTextRenderInfo naturalSizeInfo;
-        naturalSizeInfo = mLoader.GetNaturalSize(mParameters);
-        if(mParameters.textWidth < naturalSizeInfo.renderedSize.width)
+        if(!cachedNaturalSize)
+        {
+          naturalSize       = mLoader.ComputeNaturalSize(mParameters);
+          cachedNaturalSize = true;
+        }
+        if(mParameters.textWidth < naturalSize.width)
         {
 #ifdef TRACE_ENABLED
           if(gTraceFilter && gTraceFilter->IsTraceEnabled())
@@ -106,7 +124,7 @@ void TextLoadingTask::Load()
           }
 #endif
           mParameters.isAutoScrollEnabled = true;
-          mRenderInfo = mLoader.RenderAutoScroll(mParameters);
+          mRenderInfo = mLoader.RenderAutoScroll(mParameters, cachedNaturalSize, naturalSize);
         }
         else
         {
@@ -116,7 +134,7 @@ void TextLoadingTask::Load()
             DALI_LOG_RELEASE_INFO("RenderText, Ellipsize::AUTO_SCROLL\n");
           }
 #endif
-          mRenderInfo = mLoader.RenderText(mParameters);
+          mRenderInfo = mLoader.RenderText(mParameters, cachedNaturalSize, naturalSize);
         }
       }
       else if(mParameters.isAutoScrollEnabled && !mParameters.isMultiLine)
@@ -127,7 +145,7 @@ void TextLoadingTask::Load()
           DALI_LOG_RELEASE_INFO("RenderAutoScroll\n");
         }
 #endif
-        mRenderInfo = mLoader.RenderAutoScroll(mParameters);
+        mRenderInfo = mLoader.RenderAutoScroll(mParameters, cachedNaturalSize, naturalSize);
       }
       else if(mParameters.isTextFitEnabled || mParameters.isTextFitArrayEnabled)
       {
@@ -137,7 +155,7 @@ void TextLoadingTask::Load()
           DALI_LOG_RELEASE_INFO("RenderTextFit\n");
         }
 #endif
-        mRenderInfo = mLoader.RenderTextFit(mParameters);
+        mRenderInfo = mLoader.RenderTextFit(mParameters, cachedNaturalSize, naturalSize);
       }
       else
       {
@@ -147,7 +165,7 @@ void TextLoadingTask::Load()
           DALI_LOG_RELEASE_INFO("RenderText\n");
         }
 #endif
-        mRenderInfo = mLoader.RenderText(mParameters);
+        mRenderInfo = mLoader.RenderText(mParameters, cachedNaturalSize, naturalSize);
       }
       break;
     }
index 25bac7d2f8e28c27f7fb638f27c9f93c36dd8397..a538a509fb477e4951b58b526ae11643a2f52114 100644 (file)
@@ -378,6 +378,7 @@ public:
     mTextFitLineSize(0.f),
     mFontSizeScale(DEFAULT_FONT_SIZE_SCALE),
     mDisabledColorOpacity(DEFAULT_DISABLED_COLOR_OPACITY),
+    mRenderScale(1.0f),
     mFontSizeScaleEnabled(true),
     mTextFitEnabled(false),
     mTextFitChanged(false),
@@ -1120,6 +1121,7 @@ public:
   float mTextFitLineSize;              ///< This is the LineSize that is the standard when performing TextFit.
   float mFontSizeScale;                ///< Scale value for Font Size. Default 1.0
   float mDisabledColorOpacity;         ///< Color opacity when disabled.
+  float mRenderScale;                  ///< The render scale. Default 1.0
   bool  mFontSizeScaleEnabled : 1;     ///< Whether the font size scale is enabled.
   bool  mTextFitEnabled : 1;           ///< Whether the text's fit is enabled.
   bool  mTextFitChanged : 1;           ///< Whether the text fit property has changed.
index 37332eaf8b75b40d0204148a1365a28655cb849b..89e25471d851ee1a8b7faf477547e0607ac7936b 100644 (file)
@@ -1593,6 +1593,16 @@ void Controller::SetEllipsisMode(Toolkit::DevelText::Ellipsize::Mode ellipsisMod
   mImpl->mEllipsisMode = ellipsisMode;
 }
 
+void Controller::SetRenderScale(const float renderScale)
+{
+  mImpl->mRenderScale = renderScale;
+}
+
+float Controller::GetRenderScale() const
+{
+  return mImpl->mRenderScale;
+}
+
 void Controller::SetCharacterSpacing(float characterSpacing)
 {
   mImpl->mModel->mVisualModel->SetCharacterSpacing(characterSpacing);
index 42ff2121eeb3870c6e1b5bc535d512e4c3f45562..abbb582b610bd32da923724ae2430ab3641f786a 100644 (file)
@@ -1878,6 +1878,18 @@ public: // Queries & retrieves.
    */
   void SetEllipsisMode(Toolkit::DevelText::Ellipsize::Mode ellipsisMode);
 
+  /**
+   * @brief Sets the render scale
+   * @param[in] renderScale The render scale
+   */
+  void SetRenderScale(const float renderScale);
+
+  /**
+   * @brief Retrieves the render scale
+   * @return The value of the render scale
+   */
+  float GetRenderScale() const;
+
   /**
    * @brief Retrieves ignoreSpaceAfterText value from model
    * @return The value of ignoreSpaceAfterText
index 8b484905392fa4320eec04813cb235b54b5e59f2..9bd71fd29f46ca701ccb78d27b01d82449f698d3 100644 (file)
@@ -791,7 +791,15 @@ void TextVisual::LoadComplete(bool loadingSuccess, const TextInformation& textIn
 
     // Calculate the size of the visual that can fit the text.
     // The size of the text after it has been laid-out, size of pixel data buffer.
-    Size layoutSize(static_cast<float>(renderInfo.width), static_cast<float>(renderInfo.height));
+    Size layoutSize = renderInfo.size;
+
+    // Set textWidth, textHeight to the original size requested for rendering.
+    bool isRenderScale = parameters.renderScale > 1.0f ? true : false;
+    if(isRenderScale)
+    {
+      parameters.textWidth  = parameters.renderScaleWidth;
+      parameters.textHeight = parameters.renderScaleHeight;
+    }
 
     // Calculate the offset for vertical alignment only, as the layout engine will do the horizontal alignment.
     Vector2 alignmentOffset;
@@ -824,7 +832,8 @@ void TextVisual::LoadComplete(bool loadingSuccess, const TextInformation& textIn
       // This affects font rendering quality.
       // It need to be integerized.
       visualTransformOffset.x = roundf(parameters.padding.start + alignmentOffset.x);
-      visualTransformOffset.y = roundf(parameters.padding.top + alignmentOffset.y);
+      visualTransformOffset.y = isRenderScale ? roundf((layoutSize.y + parameters.padding.top + alignmentOffset.y) * 2.0f) * 0.5f - layoutSize.y :
+                                                roundf(parameters.padding.top + alignmentOffset.y);
     }
 
     SetRequireRender(renderInfo.isCutout);
@@ -850,7 +859,7 @@ void TextVisual::LoadComplete(bool loadingSuccess, const TextInformation& textIn
     const int maxTextureSize = Dali::GetMaxTextureSize();
 
     // No tiling required. Use the default renderer.
-    if(renderInfo.height < static_cast<uint32_t>(maxTextureSize))
+    if(renderInfo.size.height < static_cast<float>(maxTextureSize))
     {
       // Filter mode needs to be set to linear to produce better quality while scaling.
       Sampler sampler = Sampler::New();
@@ -895,8 +904,8 @@ void TextVisual::LoadComplete(bool loadingSuccess, const TextInformation& textIn
       Sampler sampler = Sampler::New();
       sampler.SetFilterMode(FilterMode::LINEAR, FilterMode::LINEAR);
 
-      int verifiedWidth  = static_cast<int>(renderInfo.width);
-      int verifiedHeight = static_cast<int>(renderInfo.height);
+      int verifiedWidth  = static_cast<int>(renderInfo.size.width);
+      int verifiedHeight = static_cast<int>(renderInfo.size.height);
 
       // Set information for creating textures.
       TilingInfo info(verifiedWidth, maxTextureSize);
index 2d91933ea9b5f89538400bd6270526488dd0d666..a9f65e1dfed1ada4f297e83e07919d5480970107 100644 (file)
@@ -342,6 +342,22 @@ public:
        * @see TEXT_COLOR
        */
       TEXT_COLOR_ALPHA,
+
+      /**
+       * @brief A pixel snap factor.
+       * @details Name "pixelSnapFactor", type Property::FLOAT.
+       * @note A factor of pixel snap, it should be 0.0 ~ 1.0.
+       * Controls the degree of pixel snapping applied to the visual position.
+       * A value of 0.0 means no snapping is applied (original position is preserved), while 1.0 applies full pixel alignment.
+       * Intermediate values blend smoothly between the original and snapped positions using linear interpolation (mix) in the vertex shader.
+       * Typical usage:
+       * To ensure both smooth animations and sharp visual alignment,
+       * transition the pixelSnapFactor value gradually from 0.0 to 1.0 during or after animations.
+       * This allows the snapping to engage seamlessly without visible jitter or popping, maintaining both visual quality and motion fluidity.
+       * Use 0.0 during animation to avoid snapping artifacts.
+       * Gradually increase to 1.0 as the animation settles, for crisp pixel alignment.
+       */
+      PIXEL_SNAP_FACTOR,
     };
   };