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())
return;
TextLayout layout;
layout.SetStyle(text_style_);
layout.SetText(text);
DrawTextLayout(layout, point);
DrawTextLayout(TextLayout::Create(text, style), point);
}
void Canvas::DrawTextLayout(TextLayout const& layout, Point const& point)
void Canvas::DrawTextLayout(TextLayoutPtr layout, Point const& point)
{
KGE_ASSERT(ctx_);
ctx_->DrawTextLayout(layout, point);
if (layout)
{
ctx_->DrawTextLayout(*layout, point);
cache_expired_ = true;
}
}
void Canvas::BeginPath(Point const& begin_pos)

View File

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

View File

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

View File

@ -50,6 +50,7 @@ protected:
private:
std::locale comma_locale_;
BrushPtr background_brush_;
TextStyle debug_text_style_;
TextLayout debug_text_;
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;
if (ptr)
{
ptr->SetText(text);
ptr->SetStyle(style);
ptr->SetText(text);
}
return ptr;
}
TextActor::TextActor()
: show_underline_(false)
, show_strikethrough_(false)
{
layout_ = TextLayout::Create();
}
TextActor::~TextActor() {}
void TextActor::OnRender(RenderContext& ctx)
{
ctx.DrawTextLayout(text_layout_);
if (layout_)
{
ctx.DrawTextLayout(*layout_);
}
}
void TextActor::OnUpdate(Duration dt)
@ -66,48 +68,239 @@ void TextActor::OnUpdate(Duration dt)
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)
{
text_layout_.SetDirtyFlag(TextLayout::DirtyFlag::Clean);
if (show_underline_)
text_layout_.SetUnderline(true, 0, text_layout_.GetText().length());
if (show_strikethrough_)
text_layout_.SetStrikethrough(true, 0, text_layout_.GetText().length());
SetSize(text_layout_.GetLayoutSize());
if (text_.empty())
{
SetSize(Size());
}
else
{
SetSize(layout_->GetLayoutSize());
}
}
}
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)
{
if (!text_layout_.GetFillBrush())
BrushPtr brush = layout_->GetDefaultFillBrush();
if (brush)
{
BrushPtr brush = new Brush;
text_layout_.SetFillBrush(brush);
brush->SetColor(color);
}
else
{
layout_->SetDefaultFillBrush(Brush::Create(color));
}
text_layout_.GetFillBrush()->SetColor(color);
}
void TextActor::SetOutlineColor(Color const& outline_color)
{
if (!text_layout_.GetOutlineBrush())
BrushPtr brush = layout_->GetDefaultOutlineBrush();
if (brush)
{
BrushPtr brush = new Brush;
text_layout_.SetOutlineBrush(brush);
brush->SetColor(outline_color);
}
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

View File

@ -64,11 +64,7 @@ public:
/// \~chinese
/// @brief 获取文本布局
const TextLayout& GetLayout() const;
/// \~chinese
/// @brief 获取文本布局
TextLayout& GetLayout();
TextLayoutPtr GetLayout() const;
/// \~chinese
/// @brief 获取文本布局大小
@ -82,6 +78,10 @@ public:
/// @brief 获取描边画刷
BrushPtr GetOutlineBrush() const;
/// \~chinese
/// @brief 获取描边线条样式
StrokeStylePtr GetOutlineStrokeStyle() const;
/// \~chinese
/// @brief 获取字体
FontPtr GetFont() const;
@ -143,12 +143,12 @@ public:
void SetOutlineColor(Color const& outline_color);
/// \~chinese
/// @brief 设置文字描边线宽
void SetOutlineWidth(float outline_width);
/// @brief 设置描边线条样式
void SetOutlineStrokeStyle(StrokeStylePtr stroke);
/// \~chinese
/// @brief 设置文字描边线相交样式
void SetOutlineStroke(StrokeStylePtr outline_stroke);
/// @brief 设置文字描边线
void SetOutlineWidth(float outline_width);
/// \~chinese
/// @brief 设置是否显示下划线(默认值为 false
@ -158,6 +158,10 @@ public:
/// @brief 设置是否显示删除线(默认值为 false
void SetStrikethrough(bool enable);
/// \~chinese
/// @brief 设置文本布局
void SetTextLayout(TextLayoutPtr layout);
/// \~chinese
/// @brief 更新文字布局
/// @details 文字布局是懒更新的,手动更新文字布局以更新节点状态
@ -171,130 +175,55 @@ protected:
bool CheckVisibility(RenderContext& ctx) const override;
private:
bool show_underline_;
bool show_strikethrough_;
TextLayout text_layout_;
String text_;
TextStyle style_;
TextLayoutPtr layout_;
};
/** @} */
inline const String& TextActor::GetText() const
{
return text_layout_.GetText();
return text_;
}
inline FontPtr TextActor::GetFont() const
{
return text_layout_.GetStyle().font;
return style_.font;
}
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_;
}
inline TextLayout& TextActor::GetLayout()
{
return text_layout_;
return layout_;
}
inline Size TextActor::GetLayoutSize() const
{
return text_layout_.GetLayoutSize();
if (layout_)
{
return layout_->GetLayoutSize();
}
return Size();
}
inline BrushPtr TextActor::GetFillBrush() const
{
return text_layout_.GetFillBrush();
return style_.fill_brush;
}
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

View File

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

View File

@ -587,7 +587,7 @@ void RendererImpl::CreateFontCollection(Font& font, Resource const& res)
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;
if (!d2d_res_)
@ -595,111 +595,39 @@ void RendererImpl::CreateTextLayout(TextLayout& layout)
hr = E_UNEXPECTED;
}
if (layout.GetText().empty())
if (content.empty())
{
layout.SetTextFormat(nullptr);
layout.SetTextLayout(nullptr);
layout.SetDirtyFlag(TextLayout::DirtyFlag::Updated);
return;
}
const TextStyle& style = layout.GetStyle();
if (!layout.GetTextFormat() || (layout.GetDirtyFlag() & TextLayout::DirtyFlag::DirtyFormat))
if (SUCCEEDED(hr))
{
WideString font_family = style.font_family.empty() ? L"" : string::ToWide(style.font_family);
DWRITE_FONT_WEIGHT font_weight = DWRITE_FONT_WEIGHT(style.font_weight);
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> format;
WideString font_family = style.font_family.empty() ? L"" : string::ToWide(style.font_family);
DWRITE_FONT_WEIGHT font_weight = DWRITE_FONT_WEIGHT(style.font_weight);
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;
if (SUCCEEDED(hr))
{
hr = d2d_res_->CreateTextFormat(output, font_family.c_str(), collection, font_weight, font_style,
DWRITE_FONT_STRETCH_NORMAL, style.font_size);
}
hr = d2d_res_->CreateTextFormat(format, font_family.c_str(), collection, font_weight, font_style,
DWRITE_FONT_STRETCH_NORMAL, style.font_size);
if (SUCCEEDED(hr))
{
layout.SetTextFormat(output);
layout.SetDirtyFlag(layout.GetDirtyFlag() | TextLayout::DirtyFlag::DirtyLayout);
}
}
ComPtr<IDWriteTextLayout> output;
WideString wide = string::ToWide(content);
if (layout.GetDirtyFlag() & TextLayout::DirtyFlag::DirtyLayout)
{
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);
hr = d2d_res_->CreateTextLayout(output, wide.c_str(), wide.length(), format);
if (SUCCEEDED(hr))
{
if (enable_wrapping)
{
hr = output->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
}
else
{
hr = output->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
}
layout.SetTextLayout(output);
layout.SetDirtyFlag(TextLayout::DirtyFlag::Updated);
}
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");
}

View File

@ -58,7 +58,7 @@ public:
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;

View File

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

View File

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

View File

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

View File

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

View File

@ -23,81 +23,52 @@
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()
: dirty_flag_(DirtyFlag::Clean)
, default_outline_width_(0.0f)
{
}
void TextLayout::Update()
void TextLayout::Reset(const String& text, const TextStyle& style)
{
if (!IsDirty())
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)
if (!text.empty())
{
style_.font = font;
dirty_flag_ |= DirtyFlag::DirtyFormat;
Renderer::GetInstance().CreateTextLayout(*this, text, style);
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);
}
}
void TextLayout::SetFontFamily(String const& family)
{
if (style_.font_family != family)
else
{
style_.font_family = family;
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;
Clear();
}
}
uint32_t TextLayout::GetLineCount() const
{
// Force to update layout
const_cast<TextLayout*>(this)->Update();
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
if (text_layout_)
{
@ -115,9 +86,6 @@ uint32_t TextLayout::GetLineCount() const
Size TextLayout::GetLayoutSize() const
{
// Force to update layout
const_cast<TextLayout*>(this)->Update();
#if KGE_RENDER_ENGINE == KGE_RENDER_ENGINE_DIRECTX
if (text_layout_)
{
@ -134,64 +102,265 @@ Size TextLayout::GetLayoutSize() const
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
HRESULT hr = text_layout_ ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
KGE_ASSERT(text_layout_);
if (text_layout_)
{
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
return; // not supported
#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
HRESULT hr = text_layout_ ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
KGE_ASSERT(text_layout_);
if (text_layout_)
{
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
return; // not supported
#endif

View File

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

View File

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