This commit is contained in:
Mira Grudzinska 2025-06-03 21:18:36 +02:00 committed by GitHub
commit f42688e751
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 176 additions and 38 deletions

View file

@ -284,6 +284,25 @@ struct Matrix
};
/**
* @brief A data structure representing metrics for a single glyph.
*
* @since Experimental API
*/
struct GlyphMetrics
{
Point kerning = {0.0f, 0.0f};
float advanceWidth;
float leftSideBearing;
float yOffset;
float minw;
float minh;
//for glyph<->text mapping:
uint32_t length;
};
/**
* @class Paint
*
@ -1682,6 +1701,23 @@ public:
*/
static Result unload(const char* filename) noexcept;
/**
* @brief Gets the detailed metrics for each glyph in the current text object.
*
* This function allocates memory internally to hold an array of glyph metrics corresponding to the currently set text and font.
*
* @param[out] metrics Pointer to an array of glyph metrics. Valid until the text is modified.
* @param[out] size The number of glyphs returned in the metrics array.
*
* @retval Result::InsufficientCondition If font or text was not set, a @c nullptr is passed as @p metrics, or font data loading/reading failed.
*
* @note The caller is responsible for freeing the allocated memory by calling this function again with the same @p metrics pointer and a @c nullptr @p size pointer.
* @note Must be called after setting both font() and text().
*
* @since Experimental API
*/
Result metrics(GlyphMetrics** metrics, uint32_t* size) noexcept;
/**
* @brief Creates a new Text object.
*

View file

@ -24,6 +24,8 @@
#include "tvgStr.h"
#include "tvgTtfLoader.h"
#define PX_PER_PT 1.333333f //1 pt = 1/72 in; 1 in = 96 px; -> 96/72
#if defined(_WIN32) && (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
#include <windows.h>
#elif defined(__linux__)
@ -196,6 +198,47 @@ static uint32_t* _codepoints(const char* text, size_t n)
}
static uint32_t* _codepointsLength(const char* text, size_t n) {
auto length = tvg::malloc<uint32_t*>(sizeof(uint32_t) * (n + 1));
size_t i = 0;
auto p = text;
while(*p) {
if (!(*p & 0x80U)) {
length[i++] = 1;
++p;
} else if ((*p & 0xe0U) == 0xc0U) {
length[i++] = 2;
p += 2;
} else if ((*p & 0xf0U) == 0xe0U) {
length[i++] = 3;
p += 3;
} else if ((*p & 0xf8U) == 0xf0U) {
length[i++] = 4;
p += 4;
} else {
tvg::free(length);
return nullptr;
}
}
length[i] = 0;
return length;
}
static void _scale(GlyphMetrics& gmetric, float scale)
{
gmetric.kerning.x *= scale;
gmetric.kerning.y *= scale;
gmetric.advanceWidth *= scale;
gmetric.leftSideBearing *= scale;
gmetric.yOffset *= scale;
gmetric.minw *= scale;
gmetric.minh *= scale;
}
void TtfLoader::clear()
{
if (nomap) {
@ -224,8 +267,7 @@ void TtfLoader::clear()
float TtfLoader::transform(Paint* paint, FontMetrics& metrics, float fontSize, bool italic)
{
auto shift = 0.0f;
auto dpi = 96.0f / 72.0f; //dpi base?
auto scale = fontSize * dpi / reader.metrics.unitsPerEm;
auto scale = fontSize * PX_PER_PT / reader.metrics.unitsPerEm;
if (italic) shift = -scale * 0.18f; //experimental decision.
Matrix m = {scale, shift, -(shift * metrics.minw), 0, scale, 0, 0, 0, 1};
paint->transform(m);
@ -289,21 +331,20 @@ bool TtfLoader::read(Shape* shape, char* text, FontMetrics& out)
//TODO: optimize with the texture-atlas?
TtfGlyphMetrics gmetrics;
Point offset = {0.0f, reader.metrics.hhea.ascent};
Point kerning = {0.0f, 0.0f};
auto lglyph = INVALID_GLYPH;
auto loadMinw = true;
size_t idx = 0;
while (code[idx] && idx < n) {
auto rglyph = reader.glyph(code[idx], gmetrics);
auto rglyph = reader.glyph(code[idx], gmetrics.metrics, &gmetrics.outline);
if (rglyph != INVALID_GLYPH) {
if (lglyph != INVALID_GLYPH) reader.kerning(lglyph, rglyph, kerning);
if (!reader.convert(shape, gmetrics, offset, kerning, 1U)) break;
offset.x += (gmetrics.advanceWidth + kerning.x);
if (lglyph != INVALID_GLYPH) reader.kerning(lglyph, rglyph, gmetrics.metrics.kerning);
if (!reader.convert(shape, gmetrics, offset, 1U)) break;
offset.x += (gmetrics.metrics.advanceWidth + gmetrics.metrics.kerning.x);
lglyph = rglyph;
//store the first glyph with outline min size for italic transform.
if (loadMinw && gmetrics.outline) {
out.minw = gmetrics.minw;
out.minw = gmetrics.metrics.minw;
loadMinw = false;
}
}
@ -314,3 +355,40 @@ bool TtfLoader::read(Shape* shape, char* text, FontMetrics& out)
return true;
}
bool TtfLoader::metrics(char* text, float fontSize, GlyphMetrics** metrics, uint32_t* size)
{
auto n = strlen(text);
auto code = _codepoints(text, n);
if (!code) return false;
auto length = _codepointsLength(text, n);
if (!length) return false;
GlyphMetrics gmetrics;
auto lglyph = INVALID_GLYPH, rglyph = INVALID_GLYPH;
Array<GlyphMetrics> out(n);
auto scale = fontSize * PX_PER_PT / reader.metrics.unitsPerEm;
size_t idx = 0;
while (code[idx] && idx < n) {
if ((rglyph = reader.glyph(code[idx], gmetrics, nullptr)) != INVALID_GLYPH) {
if (lglyph != INVALID_GLYPH) reader.kerning(lglyph, rglyph, gmetrics.kerning);
_scale(gmetrics, scale);
gmetrics.length = length[idx];
out.push(gmetrics);
lglyph = rglyph;
}
++idx;
}
tvg::free(code);
tvg::free(length);
*metrics = out.data;
out.data = nullptr;
*size = out.count;
return true;
}

View file

@ -49,6 +49,7 @@ struct TtfLoader : public FontLoader
bool open(const char *data, uint32_t size, const char* rpath, bool copy) override;
float transform(Paint* paint, FontMetrics& metrices, float fontSize, bool italic) override;
bool read(Shape* shape, char* text, FontMetrics& out) override;
bool metrics(char* text, float fontSize, GlyphMetrics** metrics, uint32_t* size) override;
void clear();
};

View file

@ -377,17 +377,17 @@ uint32_t TtfReader::glyph(uint32_t codepoint)
}
uint32_t TtfReader::glyph(uint32_t codepoint, TtfGlyphMetrics& gmetrics)
uint32_t TtfReader::glyph(uint32_t codepoint, GlyphMetrics& gmetrics, uint32_t* goutline)
{
auto glyph = this->glyph(codepoint);
if (glyph == INVALID_GLYPH || !glyphMetrics(glyph, gmetrics)) {
if (glyph == INVALID_GLYPH || !glyphMetrics(glyph, gmetrics, goutline)) {
TVGERR("TTF", "invalid glyph id, codepoint(0x%x)", codepoint);
return INVALID_GLYPH;
}
return glyph;
}
bool TtfReader::glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics)
bool TtfReader::glyphMetrics(uint32_t glyphIndex, GlyphMetrics& gmetrics, uint32_t* goutline)
{
//horizontal metrics
auto hmtx = this->hmtx.load();
@ -412,20 +412,21 @@ bool TtfReader::glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics)
gmetrics.leftSideBearing = _i16(data, offset);
}
gmetrics.outline = outlineOffset(glyphIndex);
auto outline = outlineOffset(glyphIndex);
if (goutline) *goutline = outline;
// glyph without outline
if (gmetrics.outline == 0) {
if (outline == 0) {
gmetrics.minw = gmetrics.minh = gmetrics.yOffset = 0;
return true;
}
if (!validate(gmetrics.outline, 10)) return false;
if (!validate(outline, 10)) return false;
//read the bounding box from the font file verbatim.
float bbox[4];
bbox[0] = static_cast<float>(_i16(data, gmetrics.outline + 2));
bbox[1] = static_cast<float>(_i16(data, gmetrics.outline + 4));
bbox[2] = static_cast<float>(_i16(data, gmetrics.outline + 6));
bbox[3] = static_cast<float>(_i16(data, gmetrics.outline + 8));
bbox[0] = static_cast<float>(_i16(data, outline + 2));
bbox[1] = static_cast<float>(_i16(data, outline + 4));
bbox[2] = static_cast<float>(_i16(data, outline + 6));
bbox[3] = static_cast<float>(_i16(data, outline + 8));
if (bbox[2] <= bbox[0] || bbox[3] <= bbox[1]) return false;
@ -436,7 +437,7 @@ bool TtfReader::glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics)
return true;
}
bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth)
bool TtfReader::convert(Shape* shape, const TtfGlyphMetrics& gmetrics, const Point& offset, uint16_t componentDepth)
{
#define ON_CURVE 0x01
@ -451,7 +452,7 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
maxComponentDepth = _u16(data, maxp + 30);
}
if (componentDepth > maxComponentDepth) return false;
return convertComposite(shape, gmetrics, offset, kerning, componentDepth + 1);
return convertComposite(shape, gmetrics, offset, componentDepth + 1);
}
auto cntrsCnt = (uint32_t) outlineCnt;
@ -471,7 +472,7 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
if (!this->flags(&outline, flags, ptsCnt)) return false;
auto pts = (Point*)alloca(ptsCnt * sizeof(Point));
if (!this->points(outline, flags, pts, ptsCnt, offset + kerning)) return false;
if (!this->points(outline, flags, pts, ptsCnt, offset + gmetrics.metrics.kerning)) return false;
//generate tvg paths.
auto& pathCmds = SHAPE(shape)->rs.path.cmds;
@ -526,7 +527,8 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
return true;
}
bool TtfReader::convertComposite(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth)
bool TtfReader::convertComposite(Shape* shape, const TtfGlyphMetrics& gmetrics, const Point& offset, uint16_t componentDepth)
{
#define ARG_1_AND_2_ARE_WORDS 0x0001
#define ARGS_ARE_XY_VALUES 0x0002
@ -587,8 +589,8 @@ bool TtfReader::convertComposite(Shape* shape, TtfGlyphMetrics& gmetrics, const
// F2DOT14 yscale; /* Format 2.14 */
pointer += 8U;
}
if (!glyphMetrics(glyphIndex, componentGmetrics)) return false;
if (!convert(shape, componentGmetrics, offset + componentOffset, kerning, componentDepth)) return false;
if (!glyphMetrics(glyphIndex, componentGmetrics.metrics, &componentGmetrics.outline)) return false;
if (!convert(shape, componentGmetrics, offset + componentOffset, componentDepth)) return false;
} while (flags & MORE_COMPONENTS);
return true;
}

View file

@ -32,12 +32,7 @@
struct TtfGlyphMetrics
{
uint32_t outline; //glyph outline table offset
float advanceWidth;
float leftSideBearing;
float yOffset;
float minw;
float minh;
GlyphMetrics metrics;
};
@ -62,9 +57,9 @@ public:
} metrics;
bool header();
uint32_t glyph(uint32_t codepoint, TtfGlyphMetrics& gmetrics);
uint32_t glyph(uint32_t codepoint, GlyphMetrics& gmetric, uint32_t* goutline);
void kerning(uint32_t lglyph, uint32_t rglyph, Point& out);
bool convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth);
bool convert(Shape* shape, const TtfGlyphMetrics& gmetric, const Point& offset, uint16_t componentDepth);
private:
//table offsets
@ -82,8 +77,8 @@ private:
uint32_t table(const char* tag);
uint32_t outlineOffset(uint32_t glyph);
uint32_t glyph(uint32_t codepoint);
bool glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics);
bool convertComposite(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth);
bool glyphMetrics(uint32_t glyphIndex, GlyphMetrics& gmetric, uint32_t* goutline);
bool convertComposite(Shape* shape, const TtfGlyphMetrics& gmetric, const Point& offset, uint16_t componentDepth);
bool genPath(uint8_t* flags, uint16_t basePoint, uint16_t count);
bool genSimpleOutline(Shape* shape, uint32_t outline, uint32_t cntrsCnt);
bool points(uint32_t outline, uint8_t* flags, Point* pts, uint32_t ptsCnt, const Point& offset);

View file

@ -118,6 +118,7 @@ struct FontLoader : LoadModule
virtual bool read(Shape* shape, char* text, FontMetrics& out) = 0;
virtual float transform(Paint* paint, FontMetrics& mertrics, float fontSize, bool italic) = 0;
virtual bool metrics(char* text, float fontSize, GlyphMetrics** metrics, uint32_t* size) = 0;
};
#endif //_TVG_LOAD_MODULE_H_

View file

@ -107,3 +107,9 @@ Type Text::type() const noexcept
{
return Type::Text;
}
Result Text::metrics(GlyphMetrics** metrics, uint32_t* size) noexcept
{
return TEXT(this)->metrics(metrics, size);
}

View file

@ -37,9 +37,9 @@ struct TextImpl : Text
Paint::Impl impl;
Shape* shape; //text shape
FontLoader* loader = nullptr;
FontMetrics metrics;
FontMetrics fmetrics;
char* utf8 = nullptr;
float fontSize;
float fontSize = 0.0f;
bool italic = false;
bool changed = false;
@ -107,10 +107,10 @@ struct TextImpl : Text
//reload
if (changed) {
loader->read(shape, utf8, metrics);
loader->read(shape, utf8, fmetrics);
changed = false;
}
return loader->transform(shape, metrics, fontSize, italic);
return loader->transform(shape, fmetrics, fontSize, italic);
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
@ -172,6 +172,25 @@ struct TextImpl : Text
{
return nullptr;
}
Result metrics(GlyphMetrics** metrics, uint32_t* size)
{
if (!metrics) return Result::InsufficientCondition;
//free allocated memory
if (*metrics && !size) {
tvg::free(*metrics);
*metrics = nullptr;
return Result::Success;
}
if (!utf8 || tvg::zero(fontSize)) return Result::InsufficientCondition;
if (load() == 0.0f) return Result::InsufficientCondition;
if (!loader->metrics(utf8, fontSize, metrics, size)) return Result::InsufficientCondition;
return Result::Success;
}
};
#endif //_TVG_TEXT_H