mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-07 21:23:32 +00:00
Merge c92ddd6cc2
into 0fa5d41c8d
This commit is contained in:
commit
f42688e751
8 changed files with 176 additions and 38 deletions
36
inc/thorvg.h
36
inc/thorvg.h
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -312,5 +353,42 @@ bool TtfLoader::read(Shape* shape, char* text, FontMetrics& out)
|
|||
|
||||
tvg::free(code);
|
||||
|
||||
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;
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue