metaforce/specter/lib/TextView.cpp

343 lines
9.6 KiB
C++
Raw Normal View History

#include "specter/RootView.hpp"
2016-03-04 23:03:47 +00:00
#include "specter/TextView.hpp"
#include "specter/ViewResources.hpp"
2015-11-25 01:46:30 +00:00
#include "utf8proc.h"
2018-10-07 02:58:28 +00:00
#include "hecl/Pipeline.hpp"
2015-11-21 23:45:02 +00:00
2015-11-26 07:35:43 +00:00
#include <freetype/internal/internal.h>
#include <freetype/internal/ftobjs.h>
2016-03-04 23:03:47 +00:00
namespace specter
2015-11-21 23:45:02 +00:00
{
2016-03-04 23:03:47 +00:00
static logvisor::Module Log("specter::TextView");
2015-11-25 01:46:30 +00:00
2018-10-07 02:58:28 +00:00
void TextView::Resources::init(boo::IGraphicsDataFactory::Context& ctx, FontCache* fcache)
2015-11-28 04:05:27 +00:00
{
m_fcache = fcache;
2018-10-07 02:58:28 +00:00
m_regular = hecl::conv->convert(ctx, Shader_SpecterTextViewShader{});
m_subpixel = hecl::conv->convert(ctx, Shader_SpecterTextViewShaderSubpixel{});
2015-11-28 04:05:27 +00:00
}
2016-02-24 20:28:37 +00:00
void TextView::_commitResources(size_t capacity)
{
auto& res = rootView().viewRes();
2018-01-10 06:18:56 +00:00
auto fontTex = m_fontAtlas.texture(res.m_factory);
2018-05-18 04:15:11 +00:00
View::commitResources(res, [&](boo::IGraphicsDataFactory::Context& ctx)
{
buildResources(ctx, res);
if (capacity)
{
2017-01-29 03:57:48 +00:00
m_glyphBuf = res.m_textRes.m_glyphPool.allocateBlock(res.m_factory, capacity);
boo::ObjToken<boo::IShaderPipeline> shader;
if (m_fontAtlas.subpixel())
shader = res.m_textRes.m_subpixel;
else
shader = res.m_textRes.m_regular;
2017-01-29 03:57:48 +00:00
auto vBufInfo = m_glyphBuf.getBufferInfo();
auto uBufInfo = m_viewVertBlockBuf.getBufferInfo();
boo::ObjToken<boo::IGraphicsBuffer> uBufs[] = {uBufInfo.first.get()};
size_t uBufOffs[] = {size_t(uBufInfo.second)};
size_t uBufSizes[] = {sizeof(ViewBlock)};
2018-01-10 06:18:56 +00:00
boo::ObjToken<boo::ITexture> texs[] = {fontTex.get()};
2018-10-07 02:58:28 +00:00
m_shaderBinding = ctx.newShaderDataBinding(shader, {}, vBufInfo.first.get(), nullptr, 1,
uBufs, nullptr, uBufOffs, uBufSizes,
1, texs, nullptr, nullptr, 0, vBufInfo.second);
}
return true;
});
}
2016-03-30 19:15:32 +00:00
TextView::TextView(ViewResources& res,
View& parentView, const FontAtlas& font,
Alignment align, size_t capacity)
2015-12-05 00:42:46 +00:00
: View(res, parentView),
2015-11-26 07:35:43 +00:00
m_capacity(capacity),
2015-12-13 21:00:30 +00:00
m_fontAtlas(font),
m_align(align)
2015-11-25 01:46:30 +00:00
{
if (size_t(hecl::VertexBufferPool<RenderGlyph>::bucketCapacity()) < capacity)
Log.report(logvisor::Fatal, "bucket overflow [%" PRISize "/%" PRISize "]",
capacity, hecl::VertexBufferPool<RenderGlyph>::bucketCapacity());
_commitResources(0);
2015-11-25 01:46:30 +00:00
}
2015-12-13 21:00:30 +00:00
TextView::TextView(ViewResources& res, View& parentView, FontTag font, Alignment align, size_t capacity)
: TextView(res, parentView, res.m_textRes.m_fcache->lookupAtlas(font), align, capacity) {}
2015-11-29 02:55:30 +00:00
2016-03-04 23:03:47 +00:00
TextView::RenderGlyph::RenderGlyph(int& adv, const FontAtlas::Glyph& glyph, const zeus::CColor& defaultColor)
2015-11-26 00:24:01 +00:00
{
m_pos[0].assign(adv + glyph.m_leftPadding, glyph.m_verticalOffset + glyph.m_height, 0.f);
m_pos[1].assign(adv + glyph.m_leftPadding, glyph.m_verticalOffset, 0.f);
m_pos[2].assign(adv + glyph.m_leftPadding + glyph.m_width, glyph.m_verticalOffset + glyph.m_height, 0.f);
m_pos[3].assign(adv + glyph.m_leftPadding + glyph.m_width, glyph.m_verticalOffset, 0.f);
m_uv[0].assign(glyph.m_uv[0], glyph.m_uv[1], glyph.m_layerFloat);
m_uv[1].assign(glyph.m_uv[0], glyph.m_uv[3], glyph.m_layerFloat);
m_uv[2].assign(glyph.m_uv[2], glyph.m_uv[1], glyph.m_layerFloat);
m_uv[3].assign(glyph.m_uv[2], glyph.m_uv[3], glyph.m_layerFloat);
m_color = defaultColor;
adv += glyph.m_advance;
}
2015-12-07 00:52:07 +00:00
int TextView::DoKern(FT_Pos val, const FontAtlas& atlas)
2015-11-26 07:35:43 +00:00
{
2015-12-02 06:13:43 +00:00
if (!val) return 0;
2015-11-26 07:35:43 +00:00
val = FT_MulFix(val, atlas.FT_Xscale());
FT_Pos orig_x = val;
/* we scale down kerning values for small ppem values */
/* to avoid that rounding makes them too big. */
/* `25' has been determined heuristically. */
if (atlas.FT_XPPem() < 25)
val = FT_MulDiv(orig_x, atlas.FT_XPPem(), 25);
2015-11-26 23:03:56 +00:00
return FT_PIX_ROUND(val) >> 6;
2015-11-26 07:35:43 +00:00
}
2017-11-13 06:14:52 +00:00
void TextView::typesetGlyphs(std::string_view str, const zeus::CColor& defaultColor)
2015-11-25 01:46:30 +00:00
{
UTF8Iterator it(str.begin());
size_t charLen = str.size() ? std::min(it.countTo(str.end()), m_capacity) : 0;
2018-05-18 04:15:11 +00:00
if (charLen > m_curSize)
{
m_curSize = charLen;
_commitResources(charLen);
}
2015-12-02 06:13:43 +00:00
uint32_t lCh = -1;
2015-11-26 00:24:01 +00:00
m_glyphs.clear();
m_glyphs.reserve(charLen);
2015-12-20 21:59:23 +00:00
m_glyphInfo.clear();
m_glyphInfo.reserve(charLen);
2015-11-26 00:24:01 +00:00
int adv = 0;
if (charLen)
2015-11-25 01:46:30 +00:00
{
for (; it.iter() < str.end() ; ++it)
2015-11-26 00:24:01 +00:00
{
utf8proc_int32_t ch = *it;
if (ch == -1)
{
Log.report(logvisor::Warning, "invalid UTF-8 char");
break;
}
if (ch == '\n' || ch == '\0')
break;
2015-11-26 00:24:01 +00:00
const FontAtlas::Glyph* glyph = m_fontAtlas.lookupGlyph(ch);
if (!glyph)
continue;
2015-11-25 01:46:30 +00:00
if (lCh != -1)
adv += DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas);
m_glyphs.emplace_back(adv, *glyph, defaultColor);
m_glyphInfo.emplace_back(ch, glyph->m_width, glyph->m_height, adv);
2015-11-26 07:35:43 +00:00
lCh = glyph->m_glyphIdx;
if (m_glyphs.size() == m_capacity)
break;
}
2015-11-25 01:46:30 +00:00
}
2015-11-26 00:24:01 +00:00
2015-12-13 21:00:30 +00:00
if (m_align == Alignment::Right)
{
int adj = -adv;
for (RenderGlyph& g : m_glyphs)
{
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
}
else if (m_align == Alignment::Center)
{
int adj = -adv / 2;
for (RenderGlyph& g : m_glyphs)
{
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
}
2015-12-05 00:42:46 +00:00
m_width = adv;
invalidateGlyphs();
2015-12-05 00:42:46 +00:00
updateSize();
2015-11-25 01:46:30 +00:00
}
2015-12-07 00:52:07 +00:00
2017-11-13 06:14:52 +00:00
void TextView::typesetGlyphs(std::wstring_view str, const zeus::CColor& defaultColor)
2015-11-25 01:46:30 +00:00
{
size_t charLen = std::min(str.size(), m_capacity);
2018-05-18 04:15:11 +00:00
if (charLen > m_curSize)
{
m_curSize = charLen;
_commitResources(charLen);
}
2015-12-02 06:13:43 +00:00
uint32_t lCh = -1;
2015-11-26 00:24:01 +00:00
m_glyphs.clear();
m_glyphs.reserve(charLen);
2015-12-20 21:59:23 +00:00
m_glyphInfo.clear();
m_glyphInfo.reserve(charLen);
2015-11-26 00:24:01 +00:00
int adv = 0;
for (wchar_t ch : str)
{
if (ch == L'\n')
break;
const FontAtlas::Glyph* glyph = m_fontAtlas.lookupGlyph(ch);
if (!glyph)
continue;
if (lCh != -1)
2015-12-02 06:13:43 +00:00
adv += DoKern(m_fontAtlas.lookupKern(lCh, glyph->m_glyphIdx), m_fontAtlas);
2015-11-28 21:45:38 +00:00
m_glyphs.emplace_back(adv, *glyph, defaultColor);
2015-12-20 21:59:23 +00:00
m_glyphInfo.emplace_back(ch, glyph->m_width, glyph->m_height, adv);
2015-11-26 00:24:01 +00:00
2015-12-02 06:13:43 +00:00
lCh = glyph->m_glyphIdx;
2015-11-26 07:35:43 +00:00
if (m_glyphs.size() == m_capacity)
break;
2015-11-26 00:24:01 +00:00
}
2015-12-13 21:00:30 +00:00
if (m_align == Alignment::Right)
{
int adj = -adv;
for (RenderGlyph& g : m_glyphs)
{
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
}
else if (m_align == Alignment::Center)
{
int adj = -adv / 2;
for (RenderGlyph& g : m_glyphs)
{
g.m_pos[0][0] += adj;
g.m_pos[1][0] += adj;
g.m_pos[2][0] += adj;
g.m_pos[3][0] += adj;
}
}
2015-12-05 00:42:46 +00:00
m_width = adv;
invalidateGlyphs();
2015-12-05 00:42:46 +00:00
updateSize();
2015-11-25 01:46:30 +00:00
}
2016-03-04 23:03:47 +00:00
void TextView::colorGlyphs(const zeus::CColor& newColor)
2015-11-25 01:46:30 +00:00
{
2015-11-26 07:35:43 +00:00
for (RenderGlyph& glyph : m_glyphs)
glyph.m_color = newColor;
invalidateGlyphs();
2015-11-25 01:46:30 +00:00
}
2016-03-04 23:03:47 +00:00
void TextView::colorGlyphsTypeOn(const zeus::CColor& newColor, float startInterval, float fadeTime)
2015-11-25 01:46:30 +00:00
{
}
void TextView::invalidateGlyphs()
{
if (m_glyphBuf)
{
2017-01-29 03:57:48 +00:00
RenderGlyph* out = m_glyphBuf.access();
size_t i = 0;
for (RenderGlyph& glyph : m_glyphs)
out[i++] = glyph;
}
}
2015-11-25 01:46:30 +00:00
void TextView::think()
{
}
void TextView::resized(const boo::SWindowRect &root, const boo::SWindowRect& sub)
2015-12-05 00:42:46 +00:00
{
View::resized(root, sub);
2015-12-05 00:42:46 +00:00
}
2015-11-25 01:46:30 +00:00
void TextView::draw(boo::IGraphicsCommandQueue* gfxQ)
{
2015-11-26 07:35:43 +00:00
View::draw(gfxQ);
2015-11-30 03:41:53 +00:00
if (m_glyphs.size())
2015-11-26 00:24:01 +00:00
{
gfxQ->setShaderDataBinding(m_shaderBinding);
2015-11-30 03:41:53 +00:00
gfxQ->drawInstances(0, 4, m_glyphs.size());
2015-11-26 00:24:01 +00:00
}
2015-11-25 01:46:30 +00:00
}
std::pair<int,int> TextView::queryGlyphDimensions(size_t pos) const
{
2015-12-20 21:59:23 +00:00
if (pos >= m_glyphInfo.size())
2016-03-04 23:03:47 +00:00
Log.report(logvisor::Fatal,
"TextView::queryGlyphWidth(%" PRISize ") out of bounds: %" PRISize,
2015-12-20 21:59:23 +00:00
pos, m_glyphInfo.size());
2015-12-20 21:59:23 +00:00
return m_glyphInfo[pos].m_dims;
}
2015-12-20 04:39:09 +00:00
size_t TextView::reverseSelectGlyph(int x) const
{
size_t ret = 0;
size_t idx = 1;
int minDelta = abs(x);
2015-12-20 21:59:23 +00:00
for (const RenderGlyphInfo& info : m_glyphInfo)
2015-12-20 04:39:09 +00:00
{
2015-12-20 21:59:23 +00:00
int thisDelta = abs(info.m_adv-x);
2015-12-20 04:39:09 +00:00
if (thisDelta < minDelta)
{
minDelta = thisDelta;
ret = idx;
}
++idx;
}
return ret;
}
int TextView::queryReverseAdvance(size_t idx) const
{
2015-12-20 21:59:23 +00:00
if (idx > m_glyphInfo.size())
2016-03-04 23:03:47 +00:00
Log.report(logvisor::Fatal,
2015-12-20 04:39:09 +00:00
"TextView::queryReverseGlyph(%" PRISize ") out of inclusive bounds: %" PRISize,
2015-12-20 21:59:23 +00:00
idx, m_glyphInfo.size());
2015-12-20 04:39:09 +00:00
if (!idx) return 0;
2015-12-20 21:59:23 +00:00
return m_glyphInfo[idx-1].m_adv;
}
std::pair<size_t,size_t> TextView::queryWholeWordRange(size_t idx) const
{
if (idx > m_glyphInfo.size())
2016-03-04 23:03:47 +00:00
Log.report(logvisor::Fatal,
2015-12-20 21:59:23 +00:00
"TextView::queryWholeWordRange(%" PRISize ") out of inclusive bounds: %" PRISize,
idx, m_glyphInfo.size());
if (m_glyphInfo.empty())
return {0,0};
if (idx == m_glyphInfo.size())
--idx;
size_t begin = idx;
while (begin > 0 && !m_glyphInfo[begin-1].m_space)
--begin;
size_t end = idx;
while (end < m_glyphInfo.size() && !m_glyphInfo[end].m_space)
++end;
return {begin, end-begin};
2015-12-20 04:39:09 +00:00
}
2015-11-21 23:45:02 +00:00
}