Update TextLayout

This commit is contained in:
Nomango 2020-02-16 12:53:18 +08:00
parent 2c050cafba
commit 1cb97ba0b2
16 changed files with 857 additions and 538 deletions

View File

@ -223,21 +223,22 @@ void Canvas::DrawTexture(TexturePtr texture, const Rect* src_rect, const Rect* d
} }
} }
void Canvas::DrawTextLayout(String const& text, Point const& point) void Canvas::DrawTextLayout(String const& text, TextStyle const& style, Point const& point)
{ {
if (text.empty()) if (text.empty())
return; return;
TextLayout layout; DrawTextLayout(TextLayout::Create(text, style), point);
layout.SetStyle(text_style_);
layout.SetText(text);
DrawTextLayout(layout, point);
} }
void Canvas::DrawTextLayout(TextLayout const& layout, Point const& point) void Canvas::DrawTextLayout(TextLayoutPtr layout, Point const& point)
{ {
KGE_ASSERT(ctx_); KGE_ASSERT(ctx_);
ctx_->DrawTextLayout(layout, point); if (layout)
{
ctx_->DrawTextLayout(*layout, point);
cache_expired_ = true;
}
} }
void Canvas::BeginPath(Point const& begin_pos) void Canvas::BeginPath(Point const& begin_pos)

View File

@ -126,14 +126,15 @@ public:
/// \~chinese /// \~chinese
/// @brief 绘制文字布局 /// @brief 绘制文字布局
/// @param text 文字 /// @param text 文字
/// @param style 文字样式
/// @param point 绘制文字的位置 /// @param point 绘制文字的位置
void DrawTextLayout(String const& text, Point const& point); void DrawTextLayout(String const& text, TextStyle const& style, Point const& point);
/// \~chinese /// \~chinese
/// @brief 绘制文字布局 /// @brief 绘制文字布局
/// @param layout 文字布局 /// @param layout 文字布局
/// @param point 绘制布局的位置 /// @param point 绘制布局的位置
void DrawTextLayout(TextLayout const& layout, Point const& point); void DrawTextLayout(TextLayoutPtr layout, Point const& point);
/// \~chinese /// \~chinese
/// @brief 开始绘制路径 /// @brief 开始绘制路径
@ -218,11 +219,6 @@ public:
/// @param stroke_style 轮廓样式 /// @param stroke_style 轮廓样式
void SetStrokeStyle(StrokeStylePtr stroke_style); void SetStrokeStyle(StrokeStylePtr stroke_style);
/// \~chinese
/// @brief 设置文字画刷样式
/// @param text_style 文字画刷样式
void SetTextStyle(TextStyle const& text_style);
/// \~chinese /// \~chinese
/// @brief 设置画刷 /// @brief 设置画刷
/// @param[in] brush 画刷 /// @param[in] brush 画刷
@ -285,7 +281,6 @@ private:
private: private:
float stroke_width_; float stroke_width_;
TextStyle text_style_;
StrokeStylePtr stroke_style_; StrokeStylePtr stroke_style_;
ShapeSink shape_sink_; ShapeSink shape_sink_;
BrushPtr fill_brush_; BrushPtr fill_brush_;
@ -313,11 +308,6 @@ inline void Canvas::SetStrokeStyle(StrokeStylePtr stroke_style)
stroke_style_ = stroke_style; stroke_style_ = stroke_style;
} }
inline void Canvas::SetTextStyle(TextStyle const& text_style)
{
text_style_ = text_style;
}
inline void Canvas::SetStrokeColor(Color const& color) inline void Canvas::SetStrokeColor(Color const& color)
{ {
if (!stroke_brush_) if (!stroke_brush_)

View File

@ -59,13 +59,11 @@ DebugActor::DebugActor()
BrushPtr fill_brush = new Brush; BrushPtr fill_brush = new Brush;
fill_brush->SetColor(Color::White); fill_brush->SetColor(Color::White);
TextStyle style; debug_text_style_.font_family = "Arial";
style.font_family = "Arial"; debug_text_style_.font_size = 16.f;
style.font_size = 16.f; debug_text_style_.font_weight = FontWeight::Normal;
style.font_weight = FontWeight::Normal; debug_text_style_.line_spacing = 20.f;
style.line_spacing = 20.f; debug_text_style_.fill_brush = fill_brush;
style.fill_brush = fill_brush;
debug_text_.SetStyle(style);
AddListener<MouseHoverEvent>([=](Event*) { SetOpacity(0.4f); }); AddListener<MouseHoverEvent>([=](Event*) { SetOpacity(0.4f); });
AddListener<MouseOutEvent>([=](Event*) { SetOpacity(1.f); }); AddListener<MouseOutEvent>([=](Event*) { SetOpacity(1.f); });
@ -124,8 +122,7 @@ void DebugActor::OnUpdate(Duration dt)
ss << pmc.PrivateUsage / 1024 << "Kb"; ss << pmc.PrivateUsage / 1024 << "Kb";
} }
debug_text_.SetText(ss.str()); debug_text_.Reset(ss.str(), debug_text_style_);
debug_text_.Update();
Size layout_size = debug_text_.GetLayoutSize(); Size layout_size = debug_text_.GetLayoutSize();
if (layout_size.x > GetWidth() - 20) if (layout_size.x > GetWidth() - 20)

View File

@ -50,6 +50,7 @@ protected:
private: private:
std::locale comma_locale_; std::locale comma_locale_;
BrushPtr background_brush_; BrushPtr background_brush_;
TextStyle debug_text_style_;
TextLayout debug_text_; TextLayout debug_text_;
List<Time> frame_time_; List<Time> frame_time_;
}; };

View File

@ -40,23 +40,25 @@ TextActorPtr TextActor::Create(const String& text, const TextStyle& style)
TextActorPtr ptr = new (std::nothrow) TextActor; TextActorPtr ptr = new (std::nothrow) TextActor;
if (ptr) if (ptr)
{ {
ptr->SetText(text);
ptr->SetStyle(style); ptr->SetStyle(style);
ptr->SetText(text);
} }
return ptr; return ptr;
} }
TextActor::TextActor() TextActor::TextActor()
: show_underline_(false)
, show_strikethrough_(false)
{ {
layout_ = TextLayout::Create();
} }
TextActor::~TextActor() {} TextActor::~TextActor() {}
void TextActor::OnRender(RenderContext& ctx) void TextActor::OnRender(RenderContext& ctx)
{ {
ctx.DrawTextLayout(text_layout_); if (layout_)
{
ctx.DrawTextLayout(*layout_);
}
} }
void TextActor::OnUpdate(Duration dt) void TextActor::OnUpdate(Duration dt)
@ -66,48 +68,239 @@ void TextActor::OnUpdate(Duration dt)
void TextActor::UpdateLayout() void TextActor::UpdateLayout()
{ {
if (text_layout_.IsDirty()) if (!layout_)
return;
if (layout_->GetDirtyFlag() == TextLayout::DirtyFlag::Updated)
{ {
text_layout_.Update(); layout_->SetDirtyFlag(TextLayout::DirtyFlag::Clean);
}
if (text_layout_.GetDirtyFlag() & TextLayout::DirtyFlag::Updated) if (text_.empty())
{ {
text_layout_.SetDirtyFlag(TextLayout::DirtyFlag::Clean); SetSize(Size());
}
if (show_underline_) else
text_layout_.SetUnderline(true, 0, text_layout_.GetText().length()); {
SetSize(layout_->GetLayoutSize());
if (show_strikethrough_) }
text_layout_.SetStrikethrough(true, 0, text_layout_.GetText().length());
SetSize(text_layout_.GetLayoutSize());
} }
} }
bool TextActor::CheckVisibility(RenderContext& ctx) const bool TextActor::CheckVisibility(RenderContext& ctx) const
{ {
return text_layout_.IsValid() && Actor::CheckVisibility(ctx); return layout_ && layout_->IsValid() && Actor::CheckVisibility(ctx);
} }
void TextActor::SetFillColor(Color const& color) void TextActor::SetFillColor(Color const& color)
{ {
if (!text_layout_.GetFillBrush()) BrushPtr brush = layout_->GetDefaultFillBrush();
if (brush)
{ {
BrushPtr brush = new Brush; brush->SetColor(color);
text_layout_.SetFillBrush(brush); }
else
{
layout_->SetDefaultFillBrush(Brush::Create(color));
} }
text_layout_.GetFillBrush()->SetColor(color);
} }
void TextActor::SetOutlineColor(Color const& outline_color) void TextActor::SetOutlineColor(Color const& outline_color)
{ {
if (!text_layout_.GetOutlineBrush()) BrushPtr brush = layout_->GetDefaultOutlineBrush();
if (brush)
{ {
BrushPtr brush = new Brush; brush->SetColor(outline_color);
text_layout_.SetOutlineBrush(brush); }
else
{
layout_->SetDefaultOutlineBrush(Brush::Create(outline_color));
}
}
void TextActor::SetTextLayout(TextLayoutPtr layout)
{
KGE_ASSERT(layout && "TextLayout must not be nullptr");
if (layout_ != layout)
{
layout_ = layout;
UpdateLayout();
}
}
void TextActor::SetText(String const& text)
{
if (text_ != text)
{
text_ = text;
layout_->Reset(text_, style_);
}
}
void TextActor::SetStyle(const TextStyle& style)
{
style_ = style;
if (!text_.empty())
{
layout_->Reset(text_, style);
}
}
void TextActor::SetFont(FontPtr font)
{
if (style_.font != font)
{
style_.font = font;
if (!text_.empty())
{
layout_->SetFont(font, { 0, text_.length() });
}
}
}
void TextActor::SetFontFamily(String const& family)
{
if (style_.font_family != family)
{
style_.font_family = family;
if (!text_.empty())
{
layout_->SetFontFamily(family, { 0, text_.length() });
}
}
}
void TextActor::SetFontSize(float size)
{
if (style_.font_size != size)
{
style_.font_size = size;
if (!text_.empty())
{
layout_->SetFontSize(size, { 0, text_.length() });
}
}
}
void TextActor::SetFontWeight(uint32_t weight)
{
if (style_.font_weight != weight)
{
style_.font_weight = weight;
if (!text_.empty())
{
layout_->SetFontWeight(weight, { 0, text_.length() });
}
}
}
void TextActor::SetItalic(bool italic)
{
if (style_.italic != italic)
{
style_.italic = italic;
if (!text_.empty())
{
layout_->SetItalic(italic, { 0, text_.length() });
}
}
}
void TextActor::SetUnderline(bool enable)
{
if (style_.show_underline != enable)
{
style_.show_underline = enable;
if (!text_.empty())
{
layout_->SetUnderline(enable, { 0, text_.length() });
}
}
}
void TextActor::SetStrikethrough(bool enable)
{
if (style_.show_strikethrough != enable)
{
style_.show_strikethrough = enable;
if (!text_.empty())
{
layout_->SetStrikethrough(enable, { 0, text_.length() });
}
}
}
void TextActor::SetWrapWidth(float wrap_width)
{
if (style_.wrap_width != wrap_width)
{
style_.wrap_width = wrap_width;
if (!text_.empty())
{
layout_->SetWrapWidth(wrap_width);
}
}
}
void TextActor::SetLineSpacing(float line_spacing)
{
if (style_.line_spacing != line_spacing)
{
style_.line_spacing = line_spacing;
if (!text_.empty())
{
layout_->SetLineSpacing(line_spacing);
}
}
}
void TextActor::SetAlignment(TextAlign align)
{
if (style_.alignment != align)
{
style_.alignment = align;
if (!text_.empty())
{
layout_->SetAlignment(align);
}
}
}
void TextActor::SetFillBrush(BrushPtr brush)
{
if (style_.fill_brush != brush)
{
style_.fill_brush = brush;
layout_->SetDefaultFillBrush(brush);
}
}
void TextActor::SetOutlineBrush(BrushPtr brush)
{
if (style_.outline_brush != brush)
{
style_.outline_brush = brush;
layout_->SetDefaultOutlineBrush(brush);
}
}
void TextActor::SetOutlineWidth(float outline_width)
{
if (style_.outline_width != outline_width)
{
style_.outline_width = outline_width;
layout_->SetDefaultOutlineWidth(outline_width);
}
}
void TextActor::SetOutlineStrokeStyle(StrokeStylePtr stroke)
{
if (style_.outline_stroke != stroke)
{
style_.outline_stroke = stroke;
layout_->SetDefaultOutlineStrokeStyle(stroke);
} }
text_layout_.GetOutlineBrush()->SetColor(outline_color);
} }
} // namespace kiwano } // namespace kiwano

View File

@ -64,11 +64,7 @@ public:
/// \~chinese /// \~chinese
/// @brief 获取文本布局 /// @brief 获取文本布局
const TextLayout& GetLayout() const; TextLayoutPtr GetLayout() const;
/// \~chinese
/// @brief 获取文本布局
TextLayout& GetLayout();
/// \~chinese /// \~chinese
/// @brief 获取文本布局大小 /// @brief 获取文本布局大小
@ -82,6 +78,10 @@ public:
/// @brief 获取描边画刷 /// @brief 获取描边画刷
BrushPtr GetOutlineBrush() const; BrushPtr GetOutlineBrush() const;
/// \~chinese
/// @brief 获取描边线条样式
StrokeStylePtr GetOutlineStrokeStyle() const;
/// \~chinese /// \~chinese
/// @brief 获取字体 /// @brief 获取字体
FontPtr GetFont() const; FontPtr GetFont() const;
@ -143,12 +143,12 @@ public:
void SetOutlineColor(Color const& outline_color); void SetOutlineColor(Color const& outline_color);
/// \~chinese /// \~chinese
/// @brief 设置文字描边线宽 /// @brief 设置描边线条样式
void SetOutlineWidth(float outline_width); void SetOutlineStrokeStyle(StrokeStylePtr stroke);
/// \~chinese /// \~chinese
/// @brief 设置文字描边线相交样式 /// @brief 设置文字描边线
void SetOutlineStroke(StrokeStylePtr outline_stroke); void SetOutlineWidth(float outline_width);
/// \~chinese /// \~chinese
/// @brief 设置是否显示下划线(默认值为 false /// @brief 设置是否显示下划线(默认值为 false
@ -158,6 +158,10 @@ public:
/// @brief 设置是否显示删除线(默认值为 false /// @brief 设置是否显示删除线(默认值为 false
void SetStrikethrough(bool enable); void SetStrikethrough(bool enable);
/// \~chinese
/// @brief 设置文本布局
void SetTextLayout(TextLayoutPtr layout);
/// \~chinese /// \~chinese
/// @brief 更新文字布局 /// @brief 更新文字布局
/// @details 文字布局是懒更新的,手动更新文字布局以更新节点状态 /// @details 文字布局是懒更新的,手动更新文字布局以更新节点状态
@ -171,130 +175,55 @@ protected:
bool CheckVisibility(RenderContext& ctx) const override; bool CheckVisibility(RenderContext& ctx) const override;
private: private:
bool show_underline_; String text_;
bool show_strikethrough_; TextStyle style_;
TextLayout text_layout_; TextLayoutPtr layout_;
}; };
/** @} */ /** @} */
inline const String& TextActor::GetText() const inline const String& TextActor::GetText() const
{ {
return text_layout_.GetText(); return text_;
} }
inline FontPtr TextActor::GetFont() const inline FontPtr TextActor::GetFont() const
{ {
return text_layout_.GetStyle().font; return style_.font;
} }
inline const TextStyle& TextActor::GetStyle() const inline const TextStyle& TextActor::GetStyle() const
{ {
return text_layout_.GetStyle(); return style_;
} }
inline const TextLayout& TextActor::GetLayout() const inline TextLayoutPtr TextActor::GetLayout() const
{ {
return text_layout_; return layout_;
}
inline TextLayout& TextActor::GetLayout()
{
return text_layout_;
} }
inline Size TextActor::GetLayoutSize() const inline Size TextActor::GetLayoutSize() const
{ {
return text_layout_.GetLayoutSize(); if (layout_)
{
return layout_->GetLayoutSize();
}
return Size();
} }
inline BrushPtr TextActor::GetFillBrush() const inline BrushPtr TextActor::GetFillBrush() const
{ {
return text_layout_.GetFillBrush(); return style_.fill_brush;
} }
inline BrushPtr TextActor::GetOutlineBrush() const inline BrushPtr TextActor::GetOutlineBrush() const
{ {
return text_layout_.GetOutlineBrush(); return style_.outline_brush;
} }
inline void TextActor::SetText(String const& text) inline StrokeStylePtr TextActor::GetOutlineStrokeStyle() const
{ {
text_layout_.SetText(text); return style_.outline_stroke;
} }
inline void TextActor::SetStyle(const TextStyle& style)
{
text_layout_.SetStyle(style);
}
inline void TextActor::SetFont(FontPtr font)
{
text_layout_.SetFont(font);
}
inline void TextActor::SetFontFamily(String const& family)
{
text_layout_.SetFontFamily(family);
}
inline void TextActor::SetFontSize(float size)
{
text_layout_.SetFontSize(size);
}
inline void TextActor::SetFontWeight(uint32_t weight)
{
text_layout_.SetFontWeight(weight);
}
inline void TextActor::SetItalic(bool italic)
{
text_layout_.SetItalic(italic);
}
inline void TextActor::SetWrapWidth(float wrap_width)
{
text_layout_.SetWrapWidth(wrap_width);
}
inline void TextActor::SetLineSpacing(float line_spacing)
{
text_layout_.SetLineSpacing(line_spacing);
}
inline void TextActor::SetAlignment(TextAlign align)
{
text_layout_.SetAlignment(align);
}
inline void TextActor::SetUnderline(bool enable)
{
show_underline_ = enable;
}
inline void TextActor::SetStrikethrough(bool enable)
{
show_strikethrough_ = enable;
}
inline void TextActor::SetFillBrush(BrushPtr brush)
{
text_layout_.SetFillBrush(brush);
}
inline void TextActor::SetOutlineBrush(BrushPtr brush)
{
text_layout_.SetOutlineBrush(brush);
}
inline void TextActor::SetOutlineWidth(float outline_width)
{
text_layout_.SetOutlineWidth(outline_width);
}
inline void TextActor::SetOutlineStroke(StrokeStylePtr outline_stroke)
{
text_layout_.SetOutlineStroke(outline_stroke);
}
} // namespace kiwano } // namespace kiwano

View File

@ -116,27 +116,31 @@ void RenderContextImpl::DrawTextLayout(TextLayout const& layout, Point const& of
if (layout.IsValid()) if (layout.IsValid())
{ {
ComPtr<ID2D1Brush> fill_brush; ComPtr<ID2D1Brush> fill_brush;
ComPtr<ID2D1Brush> outline_brush; ComPtr<ID2D1Brush> outline_brush;
const TextStyle& style = layout.GetStyle(); ComPtr<ID2D1StrokeStyle> outline_stroke;
if (style.fill_brush) if (layout.GetDefaultFillBrush())
{ {
fill_brush = style.fill_brush->GetBrush(); fill_brush = layout.GetDefaultFillBrush()->GetBrush();
fill_brush->SetOpacity(brush_opacity_); fill_brush->SetOpacity(brush_opacity_);
} }
if (style.outline_brush) if (layout.GetDefaultOutlineBrush())
{ {
outline_brush = style.outline_brush->GetBrush(); outline_brush = layout.GetDefaultOutlineBrush()->GetBrush();
outline_brush->SetOpacity(brush_opacity_); outline_brush->SetOpacity(brush_opacity_);
} }
HRESULT hr = S_OK; if (layout.GetDefaultOutlineStrokeStyle())
ID2D1StrokeStyle* stroke_style = style.outline_stroke ? style.outline_stroke->GetStrokeStyle().Get() : nullptr; {
outline_stroke = layout.GetDefaultOutlineStrokeStyle()->GetStrokeStyle();
}
hr = text_renderer_->DrawTextLayout(layout.GetTextLayout().Get(), offset.x, offset.y, fill_brush.Get(), float outline_width = layout.GetDefaultOutlineWidth();
outline_brush.Get(), style.outline_width, stroke_style);
HRESULT hr = text_renderer_->DrawTextLayout(layout.GetTextLayout().Get(), offset.x, offset.y, fill_brush.Get(),
outline_brush.Get(), outline_width, outline_stroke.Get());
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {

View File

@ -587,7 +587,7 @@ void RendererImpl::CreateFontCollection(Font& font, Resource const& res)
KGE_THROW_IF_FAILED(hr, "Create font collection failed"); KGE_THROW_IF_FAILED(hr, "Create font collection failed");
} }
void RendererImpl::CreateTextLayout(TextLayout& layout) void RendererImpl::CreateTextLayout(TextLayout& layout, const String& content, const TextStyle& style)
{ {
HRESULT hr = S_OK; HRESULT hr = S_OK;
if (!d2d_res_) if (!d2d_res_)
@ -595,111 +595,39 @@ void RendererImpl::CreateTextLayout(TextLayout& layout)
hr = E_UNEXPECTED; hr = E_UNEXPECTED;
} }
if (layout.GetText().empty()) if (content.empty())
{ {
layout.SetTextFormat(nullptr);
layout.SetTextLayout(nullptr); layout.SetTextLayout(nullptr);
layout.SetDirtyFlag(TextLayout::DirtyFlag::Updated); layout.SetDirtyFlag(TextLayout::DirtyFlag::Updated);
return; return;
} }
const TextStyle& style = layout.GetStyle(); if (SUCCEEDED(hr))
if (!layout.GetTextFormat() || (layout.GetDirtyFlag() & TextLayout::DirtyFlag::DirtyFormat))
{ {
WideString font_family = style.font_family.empty() ? L"" : string::ToWide(style.font_family); ComPtr<IDWriteTextFormat> format;
DWRITE_FONT_WEIGHT font_weight = DWRITE_FONT_WEIGHT(style.font_weight); WideString font_family = style.font_family.empty() ? L"" : string::ToWide(style.font_family);
DWRITE_FONT_STYLE font_style = style.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; DWRITE_FONT_WEIGHT font_weight = DWRITE_FONT_WEIGHT(style.font_weight);
IDWriteFontCollection* collection = style.font ? style.font->GetCollection().Get() : nullptr; DWRITE_FONT_STYLE font_style = style.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
IDWriteFontCollection* collection = style.font ? style.font->GetCollection().Get() : nullptr;
ComPtr<IDWriteTextFormat> output; hr = d2d_res_->CreateTextFormat(format, font_family.c_str(), collection, font_weight, font_style,
if (SUCCEEDED(hr)) DWRITE_FONT_STRETCH_NORMAL, style.font_size);
{
hr = d2d_res_->CreateTextFormat(output, font_family.c_str(), collection, font_weight, font_style,
DWRITE_FONT_STRETCH_NORMAL, style.font_size);
}
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
layout.SetTextFormat(output); ComPtr<IDWriteTextLayout> output;
layout.SetDirtyFlag(layout.GetDirtyFlag() | TextLayout::DirtyFlag::DirtyLayout); WideString wide = string::ToWide(content);
}
}
if (layout.GetDirtyFlag() & TextLayout::DirtyFlag::DirtyLayout) hr = d2d_res_->CreateTextLayout(output, wide.c_str(), wide.length(), format);
{
ComPtr<IDWriteTextLayout> output;
if (SUCCEEDED(hr))
{
WideString text = string::ToWide(layout.GetText());
hr = d2d_res_->CreateTextLayout(output, text.c_str(), text.length(), layout.GetTextFormat());
}
if (SUCCEEDED(hr))
{
hr = output->SetTextAlignment(DWRITE_TEXT_ALIGNMENT(style.alignment));
}
if (SUCCEEDED(hr))
{
float wrap_width = style.wrap_width;
bool enable_wrapping = (wrap_width > 0);
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
if (enable_wrapping) layout.SetTextLayout(output);
{ layout.SetDirtyFlag(TextLayout::DirtyFlag::Updated);
hr = output->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
}
else
{
hr = output->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
}
} }
if (SUCCEEDED(hr))
{
if (enable_wrapping)
{
hr = output->SetMaxWidth(wrap_width);
}
else
{
// Fix the layout width when the text does not wrap
DWRITE_TEXT_METRICS metrics;
hr = output->GetMetrics(&metrics);
if (SUCCEEDED(hr))
{
hr = output->SetMaxWidth(metrics.width);
}
}
}
}
if (SUCCEEDED(hr))
{
float spacing = style.line_spacing;
if (spacing == 0.f)
{
hr = output->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_DEFAULT, 0, 0);
}
else
{
hr = output->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, spacing, spacing * 0.8f);
}
}
if (SUCCEEDED(hr))
{
layout.SetTextLayout(output);
} }
} }
layout.SetDirtyFlag(TextLayout::DirtyFlag::Updated);
KGE_THROW_IF_FAILED(hr, "Create text layout failed"); KGE_THROW_IF_FAILED(hr, "Create text layout failed");
} }

View File

@ -58,7 +58,7 @@ public:
void CreateFontCollection(Font& font, Resource const& res) override; void CreateFontCollection(Font& font, Resource const& res) override;
void CreateTextLayout(TextLayout& layout) override; void CreateTextLayout(TextLayout& layout, const String& content, const TextStyle& style) override;
void CreateLineShape(Shape& shape, Point const& begin_pos, Point const& end_pos) override; void CreateLineShape(Shape& shape, Point const& begin_pos, Point const& end_pos) override;

View File

@ -32,8 +32,8 @@ public:
STDMETHOD(CreateDeviceResources)(_In_ ID2D1RenderTarget* pRT); STDMETHOD(CreateDeviceResources)(_In_ ID2D1RenderTarget* pRT);
STDMETHOD(DrawTextLayout) STDMETHOD(DrawTextLayout)
(_In_ IDWriteTextLayout* pTextLayout, float fOriginX, float fOriginY, _In_opt_ ID2D1Brush* pFillBrush, (_In_ IDWriteTextLayout* pTextLayout, float fOriginX, float fOriginY, _In_opt_ ID2D1Brush* pDefaultFillBrush,
_In_opt_ ID2D1Brush* pOutlineBrush, float fOutlineWidth, _In_opt_ ID2D1StrokeStyle* pStrokeStyle); _In_opt_ ID2D1Brush* pDefaultOutlineBrush, float fDefaultOutlineWidth, _In_opt_ ID2D1StrokeStyle* pStrokeStyle);
STDMETHOD(DrawGlyphRun) STDMETHOD(DrawGlyphRun)
(__maybenull void* clientDrawingContext, float baselineOriginX, float baselineOriginY, (__maybenull void* clientDrawingContext, float baselineOriginX, float baselineOriginY,
@ -68,12 +68,12 @@ public:
private: private:
unsigned long cRefCount_; unsigned long cRefCount_;
uint32_t cPrimitivesCount_; uint32_t cPrimitivesCount_;
float fOutlineWidth_; float fDefaultOutlineWidth_;
ComPtr<ID2D1Factory> pFactory_; ComPtr<ID2D1Factory> pFactory_;
ComPtr<ID2D1RenderTarget> pRT_; ComPtr<ID2D1RenderTarget> pRT_;
ComPtr<ID2D1Brush> pFillBrush_; ComPtr<ID2D1Brush> pDefaultFillBrush_;
ComPtr<ID2D1Brush> pOutlineBrush_; ComPtr<ID2D1Brush> pDefaultOutlineBrush_;
ComPtr<ID2D1StrokeStyle> pCurrStrokeStyle_; ComPtr<ID2D1StrokeStyle> pDefaultStrokeStyle_;
}; };
HRESULT ITextRenderer::Create(_Out_ ITextRenderer** ppTextRenderer, _In_ ID2D1RenderTarget* pRT) HRESULT ITextRenderer::Create(_Out_ ITextRenderer** ppTextRenderer, _In_ ID2D1RenderTarget* pRT)
@ -108,7 +108,7 @@ HRESULT ITextRenderer::Create(_Out_ ITextRenderer** ppTextRenderer, _In_ ID2D1Re
TextRenderer::TextRenderer() TextRenderer::TextRenderer()
: cRefCount_(0) : cRefCount_(0)
, cPrimitivesCount_(0) , cPrimitivesCount_(0)
, fOutlineWidth_(1) , fDefaultOutlineWidth_(1)
{ {
if (pRT_) if (pRT_)
{ {
@ -134,19 +134,20 @@ STDMETHODIMP TextRenderer::CreateDeviceResources(_In_ ID2D1RenderTarget* pRT)
} }
STDMETHODIMP TextRenderer::DrawTextLayout(_In_ IDWriteTextLayout* pTextLayout, float fOriginX, float fOriginY, STDMETHODIMP TextRenderer::DrawTextLayout(_In_ IDWriteTextLayout* pTextLayout, float fOriginX, float fOriginY,
_In_opt_ ID2D1Brush* pFillBrush, _In_opt_ ID2D1Brush* pOutlineBrush, _In_opt_ ID2D1Brush* pDefaultFillBrush,
float fOutlineWidth, _In_opt_ ID2D1StrokeStyle* pStrokeStyle) _In_opt_ ID2D1Brush* pDefaultOutlineBrush, float fDefaultOutlineWidth,
_In_opt_ ID2D1StrokeStyle* pDefaultStrokeStyle)
{ {
if (!pTextLayout) if (!pTextLayout)
{ {
return E_INVALIDARG; return E_INVALIDARG;
} }
cPrimitivesCount_ = 0; cPrimitivesCount_ = 0;
pFillBrush_ = pFillBrush; pDefaultFillBrush_ = pDefaultFillBrush;
pOutlineBrush_ = pOutlineBrush; pDefaultOutlineBrush_ = pDefaultOutlineBrush;
fOutlineWidth_ = fOutlineWidth; fDefaultOutlineWidth_ = fDefaultOutlineWidth;
pCurrStrokeStyle_ = pStrokeStyle; pDefaultStrokeStyle_ = pDefaultStrokeStyle;
return pTextLayout->Draw(nullptr, this, fOriginX, fOriginY); return pTextLayout->Draw(nullptr, this, fOriginX, fOriginY);
} }
@ -160,17 +161,19 @@ STDMETHODIMP TextRenderer::DrawGlyphRun(__maybenull void* clientDrawingContext,
KGE_NOT_USED(clientDrawingContext); KGE_NOT_USED(clientDrawingContext);
KGE_NOT_USED(measuringMode); KGE_NOT_USED(measuringMode);
KGE_NOT_USED(glyphRunDescription); KGE_NOT_USED(glyphRunDescription);
KGE_NOT_USED(clientDrawingEffect);
HRESULT hr = S_OK; HRESULT hr = S_OK;
if (pOutlineBrush_) if (pDefaultOutlineBrush_)
{ {
ComPtr<ID2D1GeometrySink> pSink; ComPtr<ID2D1GeometrySink> pSink;
ComPtr<ID2D1PathGeometry> pPathGeometry; ComPtr<ID2D1PathGeometry> pPathGeometry;
ComPtr<ID2D1TransformedGeometry> pTransformedGeometry; ComPtr<ID2D1TransformedGeometry> pTransformedGeometry;
hr = pFactory_->CreatePathGeometry(&pPathGeometry); if (SUCCEEDED(hr))
{
hr = pFactory_->CreatePathGeometry(&pPathGeometry);
}
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
@ -200,9 +203,9 @@ STDMETHODIMP TextRenderer::DrawGlyphRun(__maybenull void* clientDrawingContext,
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
pRT_->DrawGeometry(pTransformedGeometry.Get(), pOutlineBrush_.Get(), pRT_->DrawGeometry(pTransformedGeometry.Get(), pDefaultOutlineBrush_.Get(),
fOutlineWidth_ * 2, // twice width for widening fDefaultOutlineWidth_ * 2, // twice width for widening
pCurrStrokeStyle_.Get()); pDefaultStrokeStyle_.Get());
++cPrimitivesCount_; ++cPrimitivesCount_;
} }
@ -210,11 +213,24 @@ STDMETHODIMP TextRenderer::DrawGlyphRun(__maybenull void* clientDrawingContext,
} }
} }
if (SUCCEEDED(hr) && pFillBrush_) if (SUCCEEDED(hr))
{ {
pRT_->DrawGlyphRun(D2D1::Point2F(baselineOriginX, baselineOriginY), glyphRun, pFillBrush_.Get()); ComPtr<ID2D1Brush> pCurrentFillBrush;
if (clientDrawingEffect)
{
hr = clientDrawingEffect->QueryInterface<ID2D1Brush>(&pCurrentFillBrush);
}
else
{
pCurrentFillBrush = pDefaultFillBrush_;
}
++cPrimitivesCount_; if (SUCCEEDED(hr) && pCurrentFillBrush)
{
pRT_->DrawGlyphRun(D2D1::Point2F(baselineOriginX, baselineOriginY), glyphRun, pCurrentFillBrush.Get());
++cPrimitivesCount_;
}
} }
return hr; return hr;
} }
@ -224,34 +240,51 @@ STDMETHODIMP TextRenderer::DrawUnderline(__maybenull void* clientDrawingContext,
IUnknown* clientDrawingEffect) IUnknown* clientDrawingEffect)
{ {
KGE_NOT_USED(clientDrawingContext); KGE_NOT_USED(clientDrawingContext);
KGE_NOT_USED(clientDrawingEffect);
HRESULT hr; HRESULT hr = S_OK;
D2D1_RECT_F rect = D2D1::RectF(0, underline->offset, underline->width, underline->offset + underline->thickness);
ComPtr<ID2D1RectangleGeometry> pRectangleGeometry;
hr = pFactory_->CreateRectangleGeometry(&rect, &pRectangleGeometry);
D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(1.0f, 0.0f, 0.0f, 1.0f, baselineOriginX, baselineOriginY);
ComPtr<ID2D1RectangleGeometry> pRectangleGeometry;
ComPtr<ID2D1TransformedGeometry> pTransformedGeometry; ComPtr<ID2D1TransformedGeometry> pTransformedGeometry;
if (SUCCEEDED(hr)) ComPtr<ID2D1Brush> pCurrentFillBrush;
if (clientDrawingEffect)
{ {
hr = pFactory_->CreateTransformedGeometry(pRectangleGeometry.Get(), &matrix, &pTransformedGeometry); hr = clientDrawingEffect->QueryInterface<ID2D1Brush>(&pCurrentFillBrush);
}
else
{
pCurrentFillBrush = pDefaultFillBrush_;
} }
if (SUCCEEDED(hr) && pOutlineBrush_) if (pCurrentFillBrush || pDefaultOutlineBrush_)
{ {
pRT_->DrawGeometry(pTransformedGeometry.Get(), pOutlineBrush_.Get(), fOutlineWidth_ * 2, if (SUCCEEDED(hr))
pCurrStrokeStyle_.Get()); {
D2D1_RECT_F rect =
D2D1::RectF(0, underline->offset, underline->width, underline->offset + underline->thickness);
hr = pFactory_->CreateRectangleGeometry(&rect, &pRectangleGeometry);
}
if (SUCCEEDED(hr))
{
D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(1.0f, 0.0f, 0.0f, 1.0f, baselineOriginX, baselineOriginY);
hr = pFactory_->CreateTransformedGeometry(pRectangleGeometry.Get(), &matrix, &pTransformedGeometry);
}
}
if (SUCCEEDED(hr) && pDefaultOutlineBrush_)
{
pRT_->DrawGeometry(pTransformedGeometry.Get(), pDefaultOutlineBrush_.Get(), fDefaultOutlineWidth_ * 2,
pDefaultStrokeStyle_.Get());
++cPrimitivesCount_; ++cPrimitivesCount_;
} }
if (SUCCEEDED(hr) && pFillBrush_) if (SUCCEEDED(hr) && pCurrentFillBrush)
{ {
pRT_->FillGeometry(pTransformedGeometry.Get(), pFillBrush_.Get()); pRT_->FillGeometry(pTransformedGeometry.Get(), pCurrentFillBrush.Get());
++cPrimitivesCount_; ++cPrimitivesCount_;
} }
@ -263,35 +296,51 @@ STDMETHODIMP TextRenderer::DrawStrikethrough(__maybenull void* clientDrawingCont
IUnknown* clientDrawingEffect) IUnknown* clientDrawingEffect)
{ {
KGE_NOT_USED(clientDrawingContext); KGE_NOT_USED(clientDrawingContext);
KGE_NOT_USED(clientDrawingEffect);
HRESULT hr; HRESULT hr = S_OK;
D2D1_RECT_F rect =
D2D1::RectF(0, strikethrough->offset, strikethrough->width, strikethrough->offset + strikethrough->thickness);
ComPtr<ID2D1RectangleGeometry> pRectangleGeometry;
hr = pFactory_->CreateRectangleGeometry(&rect, &pRectangleGeometry);
D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(1.0f, 0.0f, 0.0f, 1.0f, baselineOriginX, baselineOriginY);
ComPtr<ID2D1RectangleGeometry> pRectangleGeometry;
ComPtr<ID2D1TransformedGeometry> pTransformedGeometry; ComPtr<ID2D1TransformedGeometry> pTransformedGeometry;
if (SUCCEEDED(hr)) ComPtr<ID2D1Brush> pCurrentFillBrush;
if (clientDrawingEffect)
{ {
hr = pFactory_->CreateTransformedGeometry(pRectangleGeometry.Get(), &matrix, &pTransformedGeometry); hr = clientDrawingEffect->QueryInterface<ID2D1Brush>(&pCurrentFillBrush);
}
else
{
pCurrentFillBrush = pDefaultFillBrush_;
} }
if (SUCCEEDED(hr) && pOutlineBrush_) if (pCurrentFillBrush || pDefaultOutlineBrush_)
{ {
pRT_->DrawGeometry(pTransformedGeometry.Get(), pOutlineBrush_.Get(), fOutlineWidth_ * 2, if (SUCCEEDED(hr))
pCurrStrokeStyle_.Get()); {
D2D1_RECT_F rect = D2D1::RectF(0, strikethrough->offset, strikethrough->width,
strikethrough->offset + strikethrough->thickness);
hr = pFactory_->CreateRectangleGeometry(&rect, &pRectangleGeometry);
}
if (SUCCEEDED(hr))
{
D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(1.0f, 0.0f, 0.0f, 1.0f, baselineOriginX, baselineOriginY);
hr = pFactory_->CreateTransformedGeometry(pRectangleGeometry.Get(), &matrix, &pTransformedGeometry);
}
}
if (SUCCEEDED(hr) && pDefaultOutlineBrush_)
{
pRT_->DrawGeometry(pTransformedGeometry.Get(), pDefaultOutlineBrush_.Get(), fDefaultOutlineWidth_ * 2,
pDefaultStrokeStyle_.Get());
++cPrimitivesCount_; ++cPrimitivesCount_;
} }
if (SUCCEEDED(hr) && pFillBrush_) if (SUCCEEDED(hr) && pCurrentFillBrush)
{ {
pRT_->FillGeometry(pTransformedGeometry.Get(), pFillBrush_.Get()); pRT_->FillGeometry(pTransformedGeometry.Get(), pCurrentFillBrush.Get());
++cPrimitivesCount_; ++cPrimitivesCount_;
} }

View File

@ -29,8 +29,9 @@ public:
static KGE_API HRESULT Create(_Out_ ITextRenderer * *ppTextRenderer, _In_ ID2D1RenderTarget * pRT); static KGE_API HRESULT Create(_Out_ ITextRenderer * *ppTextRenderer, _In_ ID2D1RenderTarget * pRT);
STDMETHOD(DrawTextLayout) STDMETHOD(DrawTextLayout)
(_In_ IDWriteTextLayout * pTextLayout, float fOriginX, float fOriginY, _In_opt_ ID2D1Brush* pFillBrush, (_In_ IDWriteTextLayout * pTextLayout, float fOriginX, float fOriginY, _In_opt_ ID2D1Brush* pDefaultFillBrush,
_In_opt_ ID2D1Brush* pOutlineBrush, float fOutlineWidth, _In_opt_ ID2D1StrokeStyle* pStrokeStyle) PURE; _In_opt_ ID2D1Brush* pDefaultOutlineBrush, float fDefaultOutlineWidth,
_In_opt_ ID2D1StrokeStyle* pDefaultStrokeStyle) PURE;
STDMETHOD_(uint32_t, GetLastPrimitivesCount)() PURE; STDMETHOD_(uint32_t, GetLastPrimitivesCount)() PURE;
}; };

View File

@ -54,7 +54,7 @@ bool Font::Load(String const& file)
{ {
Renderer::GetInstance().CreateFontCollection(*this, file); Renderer::GetInstance().CreateFontCollection(*this, file);
} }
catch (std::runtime_error&) catch (RuntimeError&)
{ {
return false; return false;
} }
@ -67,7 +67,7 @@ bool Font::Load(Resource const& resource)
{ {
Renderer::GetInstance().CreateFontCollection(*this, resource); Renderer::GetInstance().CreateFontCollection(*this, resource);
} }
catch (std::runtime_error&) catch (RuntimeError&)
{ {
return false; return false;
} }

View File

@ -116,8 +116,10 @@ public:
/// \~chinese /// \~chinese
/// @brief 创建文字布局内部资源 /// @brief 创建文字布局内部资源
/// @param[out] layout 字体布局 /// @param[out] layout 字体布局
/// @param text 文字内容
/// @param style 文本样式
/// @throw kiwano::SystemError 创建失败时抛出 /// @throw kiwano::SystemError 创建失败时抛出
virtual void CreateTextLayout(TextLayout& layout) = 0; virtual void CreateTextLayout(TextLayout& layout, const String& content, const TextStyle& style) = 0;
/// \~chinese /// \~chinese
/// @brief 创建线段形状内部资源 /// @brief 创建线段形状内部资源

View File

@ -23,81 +23,52 @@
namespace kiwano namespace kiwano
{ {
TextLayoutPtr TextLayout::Create()
{
TextLayoutPtr ptr = new (std::nothrow) TextLayout;
return ptr;
}
TextLayoutPtr TextLayout::Create(const String& content, const TextStyle& style)
{
TextLayoutPtr ptr = new (std::nothrow) TextLayout;
if (ptr)
{
ptr->Reset(content, style);
}
return ptr;
}
TextLayout::TextLayout() TextLayout::TextLayout()
: dirty_flag_(DirtyFlag::Clean) : dirty_flag_(DirtyFlag::Clean)
, default_outline_width_(0.0f)
{ {
} }
void TextLayout::Update() void TextLayout::Reset(const String& text, const TextStyle& style)
{ {
if (!IsDirty()) if (!text.empty())
return;
Renderer::GetInstance().CreateTextLayout(*this);
}
void TextLayout::SetText(const String& text)
{
text_ = text;
dirty_flag_ |= DirtyFlag::DirtyLayout;
}
void TextLayout::SetStyle(const TextStyle& style)
{
style_ = style;
dirty_flag_ |= DirtyFlag::DirtyLayout;
}
void TextLayout::SetFont(FontPtr font)
{
if (style_.font != font)
{ {
style_.font = font; Renderer::GetInstance().CreateTextLayout(*this, text, style);
dirty_flag_ |= DirtyFlag::DirtyFormat;
SetAlignment(style.alignment);
SetWrapWidth(style.wrap_width);
SetLineSpacing(style.line_spacing);
SetUnderline(style.show_underline, { 0, text.length() });
SetStrikethrough(style.show_strikethrough, { 0, text.length() });
SetDefaultFillBrush(style.fill_brush);
SetDefaultOutlineBrush(style.outline_brush);
SetDefaultOutlineWidth(style.outline_width);
SetDefaultOutlineStrokeStyle(style.outline_stroke);
} }
} else
void TextLayout::SetFontFamily(String const& family)
{
if (style_.font_family != family)
{ {
style_.font_family = family; Clear();
dirty_flag_ |= DirtyFlag::DirtyFormat;
}
}
void TextLayout::SetFontSize(float size)
{
if (style_.font_size != size)
{
style_.font_size = size;
dirty_flag_ |= DirtyFlag::DirtyFormat;
}
}
void TextLayout::SetFontWeight(uint32_t weight)
{
if (style_.font_weight != weight)
{
style_.font_weight = weight;
dirty_flag_ |= DirtyFlag::DirtyFormat;
}
}
void TextLayout::SetItalic(bool italic)
{
if (style_.italic != italic)
{
style_.italic = italic;
dirty_flag_ |= DirtyFlag::DirtyFormat;
} }
} }
uint32_t TextLayout::GetLineCount() const uint32_t TextLayout::GetLineCount() const
{ {
// Force to update layout
const_cast<TextLayout*>(this)->Update();
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
if (text_layout_) if (text_layout_)
{ {
@ -115,9 +86,6 @@ uint32_t TextLayout::GetLineCount() const
Size TextLayout::GetLayoutSize() const Size TextLayout::GetLayoutSize() const
{ {
// Force to update layout
const_cast<TextLayout*>(this)->Update();
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
if (text_layout_) if (text_layout_)
{ {
@ -134,64 +102,265 @@ Size TextLayout::GetLayoutSize() const
return Size(); return Size();
} }
void TextLayout::SetWrapWidth(float wrap_width) void TextLayout::SetFont(FontPtr font, TextRange range)
{ {
if (style_.wrap_width != wrap_width)
{
style_.wrap_width = wrap_width;
dirty_flag_ |= DirtyFlag::DirtyLayout;
}
}
void TextLayout::SetLineSpacing(float line_spacing)
{
if (style_.line_spacing != line_spacing)
{
style_.line_spacing = line_spacing;
dirty_flag_ |= DirtyFlag::DirtyLayout;
}
}
void TextLayout::SetAlignment(TextAlign align)
{
if (style_.alignment != align)
{
style_.alignment = align;
dirty_flag_ |= DirtyFlag::DirtyLayout;
}
}
void TextLayout::SetUnderline(bool enable, uint32_t start, uint32_t length)
{
// Force to update layout
Update();
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
HRESULT hr = text_layout_ ? S_OK : E_FAIL; KGE_ASSERT(text_layout_);
if (text_layout_)
if (SUCCEEDED(hr))
{ {
hr = text_layout_->SetUnderline(enable, { start, length }); IDWriteFontCollection* collection = font ? font->GetCollection().Get() : nullptr;
HRESULT hr = text_layout_->SetFontCollection(collection, { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetFontCollection failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
// not supported
#endif
}
void TextLayout::SetFontFamily(String const& family, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
WideString font_family = family.empty() ? L"" : string::ToWide(family);
HRESULT hr = text_layout_->SetFontFamilyName(font_family.c_str(), { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetFontFamilyName failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
// not supported
#endif
}
void TextLayout::SetFontSize(float size, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
HRESULT hr = text_layout_->SetFontSize(size, { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetFontSize failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
// not supported
#endif
}
void TextLayout::SetFontWeight(uint32_t weight, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
DWRITE_FONT_WEIGHT font_weight = DWRITE_FONT_WEIGHT(weight);
HRESULT hr = text_layout_->SetFontWeight(font_weight, { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetFontWeight failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
// not supported
#endif
}
void TextLayout::SetItalic(bool italic, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
DWRITE_FONT_STYLE font_style = italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
HRESULT hr = text_layout_->SetFontStyle(font_style, { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetFontStyle failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
// not supported
#endif
}
void TextLayout::SetUnderline(bool enable, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
HRESULT hr = text_layout_->SetUnderline(enable, { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetUnderline failed");
dirty_flag_ = DirtyFlag::Updated;
} }
KGE_THROW_IF_FAILED(hr, "Apply underline style to text layout failed");
#else #else
return; // not supported return; // not supported
#endif #endif
} }
void TextLayout::SetStrikethrough(bool enable, uint32_t start, uint32_t length) void TextLayout::SetStrikethrough(bool enable, TextRange range)
{ {
// Force to update layout
Update();
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
HRESULT hr = text_layout_ ? S_OK : E_FAIL; KGE_ASSERT(text_layout_);
if (text_layout_)
if (SUCCEEDED(hr))
{ {
hr = text_layout_->SetStrikethrough(enable, { start, length }); HRESULT hr = text_layout_->SetStrikethrough(enable, { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetStrikethrough failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
return; // not supported
#endif
}
void TextLayout::SetFillBrush(BrushPtr brush, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
HRESULT hr = text_layout_->SetDrawingEffect(brush->GetBrush().Get(), { range.start, range.length });
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetDrawingEffect failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
return; // not supported
#endif
}
void TextLayout::SetOutlineBrush(BrushPtr brush, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
// TODO
KGE_NOT_USED(range);
SetDefaultOutlineBrush(brush);
#else
return; // not supported
#endif
}
void TextLayout::SetOutlineWidth(float width, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
// TODO
KGE_NOT_USED(range);
SetDefaultOutlineWidth(width);
#else
return; // not supported
#endif
}
void TextLayout::SetOutlineStrokeStyle(StrokeStylePtr stroke, TextRange range)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
// TODO
KGE_NOT_USED(range);
SetDefaultOutlineStrokeStyle(stroke);
#else
return; // not supported
#endif
}
void TextLayout::SetAlignment(TextAlign align)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT();
switch (align)
{
case TextAlign::Left:
alignment = DWRITE_TEXT_ALIGNMENT_LEADING;
break;
case TextAlign::Right:
alignment = DWRITE_TEXT_ALIGNMENT_TRAILING;
break;
case TextAlign::Center:
alignment = DWRITE_TEXT_ALIGNMENT_CENTER;
break;
case TextAlign::Justified:
alignment = DWRITE_TEXT_ALIGNMENT_JUSTIFIED;
break;
}
HRESULT hr = text_layout_->SetTextAlignment(alignment);
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetTextAlignment failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
// not supported
#endif
}
void TextLayout::SetWrapWidth(float wrap_width)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
DWRITE_WORD_WRAPPING wrapping = (wrap_width > 0) ? DWRITE_WORD_WRAPPING_WRAP : DWRITE_WORD_WRAPPING_NO_WRAP;
HRESULT hr = text_layout_->SetWordWrapping(wrapping);
if (SUCCEEDED(hr))
{
if (wrap_width > 0)
{
hr = text_layout_->SetMaxWidth(wrap_width);
}
else
{
// Fix the layout width when the text does not wrap
DWRITE_TEXT_METRICS metrics;
hr = text_layout_->GetMetrics(&metrics);
if (SUCCEEDED(hr))
{
hr = text_layout_->SetMaxWidth(metrics.width);
}
}
}
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetWordWrapping failed");
dirty_flag_ = DirtyFlag::Updated;
}
#else
return; // not supported
#endif
}
void TextLayout::SetLineSpacing(float line_spacing)
{
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
KGE_ASSERT(text_layout_);
if (text_layout_)
{
HRESULT hr = S_OK;
float spacing = line_spacing;
if (spacing == 0.f)
{
hr = text_layout_->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_DEFAULT, 0, 0);
}
else
{
hr = text_layout_->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, spacing, spacing * 0.8f);
}
KGE_THROW_IF_FAILED(hr, "IDWriteTextLayout::SetLineSpacing failed");
dirty_flag_ = DirtyFlag::Updated;
} }
KGE_THROW_IF_FAILED(hr, "Apply strikethrough style to text layout failed");
#else #else
return; // not supported return; // not supported
#endif #endif

View File

@ -20,11 +20,14 @@
#pragma once #pragma once
#include <kiwano/math/Math.h> #include <kiwano/math/Math.h>
#include <kiwano/core/ObjectBase.h>
#include <kiwano/render/TextStyle.hpp> #include <kiwano/render/TextStyle.hpp>
namespace kiwano namespace kiwano
{ {
KGE_DECLARE_SMART_PTR(TextLayout);
/** /**
* \addtogroup Render * \addtogroup Render
* @{ * @{
@ -32,9 +35,19 @@ namespace kiwano
/// \~chinese /// \~chinese
/// @brief 文本布局 /// @brief 文本布局
class KGE_API TextLayout : public Noncopyable class KGE_API TextLayout : public virtual ObjectBase
{ {
public: public:
/// \~chinese
/// @brief 创建文本布局
static TextLayoutPtr Create();
/// \~chinese
/// @brief 创建文本布局
/// @param content 文字内容
/// @param style 文本样式
static TextLayoutPtr Create(const String& content, const TextStyle& style);
/// \~chinese /// \~chinese
/// @brief 构造空的文本布局 /// @brief 构造空的文本布局
TextLayout(); TextLayout();
@ -48,17 +61,14 @@ public:
bool IsDirty() const; bool IsDirty() const;
/// \~chinese /// \~chinese
/// @brief 更新文本布局 /// @brief 清空文本布局
/// @note 文本布局是懒更新的,在修改文本布局的属性后需要手动更新 void Clear();
void Update();
/// \~chinese /// \~chinese
/// @brief 获取文本 /// @brief 重设文本布局
const String& GetText() const; /// @param content 文字内容
/// @param style 文本样式
/// \~chinese void Reset(const String& content, const TextStyle& style);
/// @brief 获取文本样式
const TextStyle& GetStyle() const;
/// \~chinese /// \~chinese
/// @brief 获取文本行数 /// @brief 获取文本行数
@ -69,44 +79,99 @@ public:
Size GetLayoutSize() const; Size GetLayoutSize() const;
/// \~chinese /// \~chinese
/// @brief 获取填充画刷 /// @brief 获取默认填充画刷
BrushPtr GetFillBrush() const; BrushPtr GetDefaultFillBrush() const;
/// \~chinese /// \~chinese
/// @brief 获取描边画刷 /// @brief 获取默认描边画刷
BrushPtr GetOutlineBrush() const; BrushPtr GetDefaultOutlineBrush() const;
/// \~chinese /// \~chinese
/// @brief 设置文本 /// @brief 获取默认描边宽度
void SetText(const String& text); float GetDefaultOutlineWidth() const;
/// \~chinese /// \~chinese
/// @brief 设置文本样式 /// @brief 获取默认描边线条样式
void SetStyle(const TextStyle& style); StrokeStylePtr GetDefaultOutlineStrokeStyle() const;
/// \~chinese
/// @brief 文字范围
struct TextRange
{
uint32_t start; ///< 起始位置
uint32_t length; ///< 长度
};
/// \~chinese /// \~chinese
/// @brief 设置字体 /// @brief 设置字体
void SetFont(FontPtr font); /// @param font 字体
/// @param range 文字范围
void SetFont(FontPtr font, TextRange range);
/// \~chinese /// \~chinese
/// @brief 设置字体族 /// @brief 设置字体族
void SetFontFamily(String const& family); /// @param family 字体族
/// @param range 文字范围
void SetFontFamily(String const& family, TextRange range);
/// \~chinese /// \~chinese
/// @brief 设置字号(默认值为 18 /// @brief 设置字号(默认值为 18
void SetFontSize(float size); /// @param size 字号
/// @param range 文字范围
void SetFontSize(float size, TextRange range);
/// \~chinese /// \~chinese
/// @brief 设置字体粗细值(默认值为 FontWeight::Normal /// @brief 设置字体粗细值(默认值为 FontWeight::Normal
void SetFontWeight(uint32_t weight); /// @param weight 粗细值
/// @param range 文字范围
/// \~chinese void SetFontWeight(uint32_t weight, TextRange range);
/// @brief 设置文字填充画刷
void SetFillBrush(BrushPtr brush);
/// \~chinese /// \~chinese
/// @brief 设置文字斜体(默认值为 false /// @brief 设置文字斜体(默认值为 false
void SetItalic(bool italic); /// @param italic 是否是斜体
/// @param range 文字范围
void SetItalic(bool italic, TextRange range);
/// \~chinese
/// @brief 设置下划线
/// @param enable 是否显示下划线
/// @param range 文字范围
void SetUnderline(bool enable, TextRange range);
/// \~chinese
/// @brief 设置删除线
/// @param enable 是否显示删除线
/// @param range 文字范围
void SetStrikethrough(bool enable, TextRange range);
/// \~chinese
/// @brief 设置文字填充画刷,描边画刷和描边线宽
/// @param brush 画刷
/// @param range 文字范围
void SetFillBrush(BrushPtr brush, TextRange range);
/// \~chinese
/// @brief 设置文字描边画刷
/// @param brush 画刷
/// @param range 文字范围
void SetOutlineBrush(BrushPtr brush, TextRange range);
/// \~chinese
/// @brief 设置文字描边线宽
/// @param width 描边线宽
/// @param range 文字范围
void SetOutlineWidth(float width, TextRange range);
/// \~chinese
/// @brief 设置描边线条样式
/// @param stroke 线条样式
/// @param range 文字范围
void SetOutlineStrokeStyle(StrokeStylePtr stroke, TextRange range);
/// \~chinese
/// @brief 设置对齐方式
/// @param align 对齐方式
void SetAlignment(TextAlign align);
/// \~chinese /// \~chinese
/// @brief 设置文本自动换行的宽度 /// @brief 设置文本自动换行的宽度
@ -117,66 +182,52 @@ public:
void SetLineSpacing(float line_spacing); void SetLineSpacing(float line_spacing);
/// \~chinese /// \~chinese
/// @brief 设置对齐方式 /// @brief 设置默认文字填充画刷
void SetAlignment(TextAlign align); /// @param brush 画刷
void SetDefaultFillBrush(BrushPtr brush);
/// \~chinese /// \~chinese
/// @brief 设置文字描边画刷 /// @brief 设置默认文字描边画刷
void SetOutlineBrush(BrushPtr brush); /// @param brush 画刷
void SetDefaultOutlineBrush(BrushPtr brush);
/// \~chinese /// \~chinese
/// @brief 设置文字描边线宽 /// @brief 设置默认文字描边线宽
void SetOutlineWidth(float outline_width); /// @param width 描边线宽
void SetDefaultOutlineWidth(float width);
/// \~chinese /// \~chinese
/// @brief 设置文字描边线相交样式 /// @brief 设置默认描边线条样式
void SetOutlineStroke(StrokeStylePtr outline_stroke); /// @param stroke 线条样式
void SetDefaultOutlineStrokeStyle(StrokeStylePtr stroke);
/// \~chinese
/// @brief 设置下划线
/// @param enable 是否显示下划线
/// @param start 起始位置
/// @param length 长度
void SetUnderline(bool enable, uint32_t start, uint32_t length);
/// \~chinese
/// @brief 设置删除线
/// @param enable 是否显示删除线
/// @param start 起始位置
/// @param length 长度
void SetStrikethrough(bool enable, uint32_t start, uint32_t length);
/// \~chinese /// \~chinese
/// @brief 脏数据标志 /// @brief 脏数据标志
enum DirtyFlag : uint8_t enum class DirtyFlag : uint8_t
{ {
Clean = 0, ///< 干净数据 Clean = 0, ///< 干净布局
DirtyFormat = 1, ///< 文字格式待更新 Dirty = 1 << 0, ///< 脏布局
DirtyLayout = 1 << 1, ///< 文字布局待更新 Updated = 1 << 1, ///< 已更新
Updated = 1 << 2, ///< 数据已更新
}; };
uint8_t GetDirtyFlag() const; DirtyFlag GetDirtyFlag() const;
void SetDirtyFlag(uint8_t flag); void SetDirtyFlag(DirtyFlag flag);
private: private:
uint8_t dirty_flag_; DirtyFlag dirty_flag_;
String text_; BrushPtr default_fill_brush_;
TextStyle style_; BrushPtr default_outline_brush_;
float default_outline_width_;
StrokeStylePtr default_outline_stroke_;
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
public: public:
ComPtr<IDWriteTextFormat> GetTextFormat() const;
void SetTextFormat(ComPtr<IDWriteTextFormat> format);
ComPtr<IDWriteTextLayout> GetTextLayout() const; ComPtr<IDWriteTextLayout> GetTextLayout() const;
void SetTextLayout(ComPtr<IDWriteTextLayout> layout); void SetTextLayout(ComPtr<IDWriteTextLayout> layout);
private: private:
ComPtr<IDWriteTextFormat> text_format_;
ComPtr<IDWriteTextLayout> text_layout_; ComPtr<IDWriteTextLayout> text_layout_;
#endif #endif
}; };
@ -197,72 +248,72 @@ inline bool TextLayout::IsDirty() const
return dirty_flag_ != DirtyFlag::Clean; return dirty_flag_ != DirtyFlag::Clean;
} }
inline const String& TextLayout::GetText() const inline void TextLayout::Clear()
{ {
return text_; #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
text_layout_ = nullptr;
dirty_flag_ = DirtyFlag::Updated;
#else
return; // not supported
#endif
} }
inline const TextStyle& TextLayout::GetStyle() const inline TextLayout::DirtyFlag TextLayout::GetDirtyFlag() const
{
return style_;
}
inline uint8_t TextLayout::GetDirtyFlag() const
{ {
return dirty_flag_; return dirty_flag_;
} }
inline void TextLayout::SetDirtyFlag(uint8_t flag) inline void TextLayout::SetDirtyFlag(TextLayout::DirtyFlag flag)
{ {
dirty_flag_ = flag; dirty_flag_ = flag;
} }
inline BrushPtr TextLayout::GetFillBrush() const inline BrushPtr TextLayout::GetDefaultFillBrush() const
{ {
return style_.fill_brush; return default_fill_brush_;
} }
inline BrushPtr TextLayout::GetOutlineBrush() const inline BrushPtr TextLayout::GetDefaultOutlineBrush() const
{ {
return style_.outline_brush; return default_outline_brush_;
} }
inline void TextLayout::SetFillBrush(BrushPtr brush) inline float TextLayout::GetDefaultOutlineWidth() const
{ {
style_.fill_brush = brush; return default_outline_width_;
} }
inline void TextLayout::SetOutlineBrush(BrushPtr brush) inline StrokeStylePtr TextLayout::GetDefaultOutlineStrokeStyle() const
{ {
style_.outline_brush = brush; return default_outline_stroke_;
} }
inline void TextLayout::SetOutlineWidth(float outline_width) inline void TextLayout::SetDefaultFillBrush(BrushPtr brush)
{ {
style_.outline_width = outline_width; default_fill_brush_ = brush;
} }
inline void TextLayout::SetOutlineStroke(StrokeStylePtr outline_stroke) inline void TextLayout::SetDefaultOutlineBrush(BrushPtr brush)
{ {
style_.outline_stroke = outline_stroke; default_outline_brush_ = brush;
}
inline void TextLayout::SetDefaultOutlineWidth(float width)
{
default_outline_width_ = width;
}
inline void TextLayout::SetDefaultOutlineStrokeStyle(StrokeStylePtr stroke)
{
default_outline_stroke_ = stroke;
} }
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX #if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
inline ComPtr<IDWriteTextFormat> TextLayout::GetTextFormat() const
{
return text_format_;
}
inline ComPtr<IDWriteTextLayout> TextLayout::GetTextLayout() const inline ComPtr<IDWriteTextLayout> TextLayout::GetTextLayout() const
{ {
return text_layout_; return text_layout_;
} }
inline void TextLayout::SetTextFormat(ComPtr<IDWriteTextFormat> format)
{
text_format_ = format;
}
inline void TextLayout::SetTextLayout(ComPtr<IDWriteTextLayout> layout) inline void TextLayout::SetTextLayout(ComPtr<IDWriteTextLayout> layout)
{ {
text_layout_ = layout; text_layout_ = layout;

View File

@ -37,9 +37,10 @@ namespace kiwano
*/ */
enum class TextAlign enum class TextAlign
{ {
Left, ///< 左对齐 Left, ///< 左对齐
Right, ///< 右对齐 Right, ///< 右对齐
Center ///< 居中对齐 Center, ///< 居中对齐
Justified ///< 两端对齐
}; };
/** /**
@ -69,18 +70,20 @@ struct FontWeight
class KGE_API TextStyle class KGE_API TextStyle
{ {
public: public:
FontPtr font; ///< 字体 FontPtr font; ///< 字体
String font_family; ///< 字体族 String font_family; ///< 字体族
float font_size; ///< 字号 float font_size; ///< 字号
uint32_t font_weight; ///< 粗细值 uint32_t font_weight; ///< 粗细值
bool italic; ///< 是否斜体 bool italic; ///< 是否斜体
TextAlign alignment; ///< 对齐方式 TextAlign alignment; ///< 对齐方式
float wrap_width; ///< 自动换行宽度 BrushPtr fill_brush; ///< 填充画刷
float line_spacing; ///< 行间距 BrushPtr outline_brush; ///< 描边画刷
BrushPtr fill_brush; ///< 填充画刷 float outline_width; ///< 描边线宽
BrushPtr outline_brush; ///< 描边画刷 StrokeStylePtr outline_stroke; ///< 描边线宽
float outline_width; ///< 描边线宽 float wrap_width; ///< 自动换行宽度
StrokeStylePtr outline_stroke; ///< 描边线样式 float line_spacing; ///< 行间距
bool show_underline; ///< 显示下划线
bool show_strikethrough; ///< 显示删除线
public: public:
/** /**
@ -113,10 +116,11 @@ inline TextStyle::TextStyle(const String& font_family, float font_size, uint32_t
, font_weight(font_weight) , font_weight(font_weight)
, italic(false) , italic(false)
, alignment(TextAlign::Left) , alignment(TextAlign::Left)
, outline_width(1.0f)
, wrap_width(0) , wrap_width(0)
, line_spacing(0) , line_spacing(0)
, outline_width(1.0f) , show_underline(false)
, outline_stroke() , show_strikethrough(false)
{ {
} }