ttf: ++thread safety

Properly allow the ttf loader sharable among threads.

issue: https://github.com/thorvg/thorvg/issues/3165
This commit is contained in:
Hermet Park 2025-03-25 13:10:50 +09:00 committed by Mira Grudzinska
parent ba00675559
commit 7d8bb168d2
7 changed files with 60 additions and 53 deletions

View file

@ -217,17 +217,16 @@ void TtfLoader::clear()
/************************************************************************/ /************************************************************************/
bool TtfLoader::transform(Paint* paint, float fontSize, bool italic) float TtfLoader::transform(Paint* paint, FontMetrics& metrics, float fontSize, bool italic)
{ {
if (!paint) return false;
auto shift = 0.0f; auto shift = 0.0f;
auto dpi = 96.0f / 72.0f; //dpi base? auto dpi = 96.0f / 72.0f; //dpi base?
scale = fontSize * dpi / reader.metrics.unitsPerEm; auto scale = fontSize * dpi / reader.metrics.unitsPerEm;
if (italic) shift = -scale * 0.18f; //experimental decision. if (italic) shift = -scale * 0.18f; //experimental decision.
Matrix m = {scale, shift, -(shift * reader.metrics.minw), 0, scale, 0, 0, 0, 1}; Matrix m = {scale, shift, -(shift * metrics.minw), 0, scale, 0, 0, 0, 1};
paint->transform(m); paint->transform(m);
return true; return scale;
} }
@ -270,21 +269,11 @@ bool TtfLoader::open(const char* data, uint32_t size, bool copy)
} }
bool TtfLoader::request(Shape* shape, char* text) bool TtfLoader::read(Shape* shape, char* text, FontMetrics& out)
{
this->shape = shape;
this->text = text;
return true;
}
bool TtfLoader::read()
{ {
if (!text) return false; if (!text) return false;
shape->reset(); shape->reset();
shape->fill(FillRule::EvenOdd);
auto n = strlen(text); auto n = strlen(text);
auto code = _codepoints(text, n); auto code = _codepoints(text, n);
@ -306,7 +295,7 @@ bool TtfLoader::read()
lglyph = rglyph; lglyph = rglyph;
//store the first glyph with outline min size for italic transform. //store the first glyph with outline min size for italic transform.
if (loadMinw && gmetrics.outline) { if (loadMinw && gmetrics.outline) {
reader.metrics.minw = gmetrics.minw; out.minw = gmetrics.minw;
loadMinw = false; loadMinw = false;
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. * Copyright (c) 2023 - 2025 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -43,11 +43,12 @@ struct TtfLoader : public FontLoader
~TtfLoader(); ~TtfLoader();
using FontLoader::open; using FontLoader::open;
using FontLoader::read;
bool open(const string& path) override; bool open(const string& path) override;
bool open(const char *data, uint32_t size, bool copy) override; bool open(const char *data, uint32_t size, bool copy) override;
bool transform(Paint* paint, float fontSize, bool italic) override; float transform(Paint* paint, FontMetrics& metrices, float fontSize, bool italic) override;
bool request(Shape* shape, char* text) override; bool read(Shape* shape, char* text, FontMetrics& out) override;
bool read() override;
void clear(); void clear();
}; };

View file

@ -72,12 +72,10 @@ static int _cmpu32(const void *a, const void *b)
bool TtfReader::validate(uint32_t offset, uint32_t margin) const bool TtfReader::validate(uint32_t offset, uint32_t margin) const
{ {
#if 1
if ((offset > size) || (size - offset < margin)) { if ((offset > size) || (size - offset < margin)) {
TVGERR("TTF", "Invalidate data"); TVGERR("TTF", "Invalidate data");
return false; return false;
} }
#endif
return true; return true;
} }
@ -204,8 +202,11 @@ uint32_t TtfReader::outlineOffset(uint32_t glyph)
{ {
uint32_t cur, next; uint32_t cur, next;
if (!loca) loca = table("loca"); auto loca = this->loca.load();
if (!glyf) glyf = table("glyf"); if (loca == 0) this->loca = loca = table("loca");
auto glyf = this->glyf.load();
if (glyf == 0) this->glyf = glyf = table("glyf");
if (metrics.locaFormat == 0) { if (metrics.locaFormat == 0) {
auto base = loca + 2 * glyph; auto base = loca + 2 * glyph;
@ -327,8 +328,8 @@ bool TtfReader::header()
metrics.numHmtx = _u16(data, hhea + 34); metrics.numHmtx = _u16(data, hhea + 34);
//kerning //kerning
this->kern = table("kern"); auto kern = this->kern = table("kern");
if (this->kern) { if (kern) {
if (!validate(kern, 4)) return false; if (!validate(kern, 4)) return false;
if (_u16(data, kern) != 0) return false; if (_u16(data, kern) != 0) return false;
} }
@ -339,8 +340,9 @@ bool TtfReader::header()
uint32_t TtfReader::glyph(uint32_t codepoint) uint32_t TtfReader::glyph(uint32_t codepoint)
{ {
auto cmap = this->cmap.load();
if (cmap == 0) { if (cmap == 0) {
cmap = table("cmap"); this->cmap = cmap = table("cmap");
if (!validate(cmap, 4)) return -1; if (!validate(cmap, 4)) return -1;
} }
@ -397,7 +399,8 @@ uint32_t TtfReader::glyph(uint32_t codepoint, TtfGlyphMetrics& gmetrics)
bool TtfReader::glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics) bool TtfReader::glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics)
{ {
//horizontal metrics //horizontal metrics
if (!hmtx) hmtx = table("hmtx"); auto hmtx = this->hmtx.load();
if (hmtx == 0) this->hmtx = hmtx = table("hmtx");
//glyph is inside long metrics segment. //glyph is inside long metrics segment.
if (glyphIndex < metrics.numHmtx) { if (glyphIndex < metrics.numHmtx) {
@ -451,7 +454,8 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
if (outlineCnt == 0) return false; if (outlineCnt == 0) return false;
if (outlineCnt < 0) { if (outlineCnt < 0) {
uint16_t maxComponentDepth = 1U; uint16_t maxComponentDepth = 1U;
if (!maxp) maxp = table("maxp"); auto maxp = this->maxp.load();
if (maxp == 0) this->maxp = maxp = table("maxp");
if (validate(maxp, 32) && _u32(data, maxp) >= 0x00010000U) { // >= version 1.0 if (validate(maxp, 32) && _u32(data, maxp) >= 0x00010000U) { // >= version 1.0
maxComponentDepth = _u16(data, maxp + 30); maxComponentDepth = _u16(data, maxp + 30);
} }
@ -607,7 +611,7 @@ void TtfReader::kerning(uint32_t lglyph, uint32_t rglyph, Point& out)
if (!kern) return; if (!kern) return;
auto kern = this->kern; auto kern = this->kern.load();
out.x = out.y = 0.0f; out.x = out.y = 0.0f;

View file

@ -23,6 +23,7 @@
#ifndef _TVG_TTF_READER_H #ifndef _TVG_TTF_READER_H
#define _TVG_TTF_READER_H #define _TVG_TTF_READER_H
#include <atomic>
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgArray.h" #include "tvgArray.h"
@ -55,7 +56,6 @@ public:
float lineGap; float lineGap;
} hhea; } hhea;
float minw; //first glyph width (used for italic)
uint16_t unitsPerEm; uint16_t unitsPerEm;
uint16_t numHmtx; //the number of Horizontal metrics table uint16_t numHmtx; //the number of Horizontal metrics table
uint8_t locaFormat; //0 for short offsets, 1 for long uint8_t locaFormat; //0 for short offsets, 1 for long
@ -68,12 +68,12 @@ public:
private: private:
//table offsets //table offsets
uint32_t cmap = 0; atomic<uint32_t> cmap{};
uint32_t hmtx = 0; atomic<uint32_t> hmtx{};
uint32_t loca = 0; atomic<uint32_t> loca{};
uint32_t glyf = 0; atomic<uint32_t> glyf{};
uint32_t kern = 0; atomic<uint32_t> kern{};
uint32_t maxp = 0; atomic<uint32_t> maxp{};
uint32_t cmap_12_13(uint32_t table, uint32_t codepoint, int which) const; uint32_t cmap_12_13(uint32_t table, uint32_t codepoint, int which) const;
uint32_t cmap_4(uint32_t table, uint32_t codepoint) const; uint32_t cmap_4(uint32_t table, uint32_t codepoint) const;

View file

@ -23,6 +23,8 @@
#ifndef _TVG_LOAD_MODULE_H_ #ifndef _TVG_LOAD_MODULE_H_
#define _TVG_LOAD_MODULE_H_ #define _TVG_LOAD_MODULE_H_
#include <atomic>
#include "tvgCommon.h"
#include "tvgRender.h" #include "tvgRender.h"
#include "tvgInlist.h" #include "tvgInlist.h"
@ -38,7 +40,7 @@ struct LoadModule
}; };
FileType type; //current loader file type FileType type; //current loader file type
uint16_t sharing = 0; //reference count atomic<uint16_t> sharing{}; //reference count
bool readied = false; //read done already. bool readied = false; //read done already.
bool pathcache = false; //cached by path bool pathcache = false; //cached by path
@ -77,7 +79,7 @@ struct LoadModule
struct ImageLoader : LoadModule struct ImageLoader : LoadModule
{ {
static ColorSpace cs; //desired value static atomic<ColorSpace> cs; //desired value
float w = 0, h = 0; //default image size float w = 0, h = 0; //default image size
RenderSurface surface; RenderSurface surface;
@ -95,14 +97,21 @@ struct ImageLoader : LoadModule
}; };
struct FontMetrics
{
//TODO: add necessary metrics
float minw;
};
struct FontLoader : LoadModule struct FontLoader : LoadModule
{ {
float scale = 1.0f;
FontLoader(FileType type) : LoadModule(type) {} FontLoader(FileType type) : LoadModule(type) {}
virtual bool request(Shape* shape, char* text) = 0; using LoadModule::read;
virtual bool transform(Paint* paint, float fontSize, bool italic) = 0;
virtual bool read(Shape* shape, char* text, FontMetrics& out) = 0;
virtual float transform(Paint* paint, FontMetrics& mertrics, float fontSize, bool italic) = 0;
}; };
#endif //_TVG_LOAD_MODULE_H_ #endif //_TVG_LOAD_MODULE_H_

View file

@ -22,6 +22,7 @@
#include <string.h> #include <string.h>
#include <atomic>
#include "tvgInlist.h" #include "tvgInlist.h"
#include "tvgLoader.h" #include "tvgLoader.h"
#include "tvgLock.h" #include "tvgLock.h"
@ -66,7 +67,8 @@ uintptr_t HASH_KEY(const char* data)
/* Internal Class Implementation */ /* Internal Class Implementation */
/************************************************************************/ /************************************************************************/
ColorSpace ImageLoader::cs = ColorSpace::ARGB8888; //TODO: remove it.
atomic<ColorSpace> ImageLoader::cs{ColorSpace::ARGB8888};
static Key _key; static Key _key;
static Inlist<LoadModule> _activeLoaders; static Inlist<LoadModule> _activeLoaders;

View file

@ -24,6 +24,7 @@
#define _TVG_TEXT_H #define _TVG_TEXT_H
#include <cstring> #include <cstring>
#include "tvgMath.h"
#include "tvgShape.h" #include "tvgShape.h"
#include "tvgFill.h" #include "tvgFill.h"
#include "tvgLoader.h" #include "tvgLoader.h"
@ -33,6 +34,7 @@ struct Text::Impl
FontLoader* loader = nullptr; FontLoader* loader = nullptr;
Text* paint; Text* paint;
Shape* shape; Shape* shape;
FontMetrics metrics;
char* utf8 = nullptr; char* utf8 = nullptr;
float fontSize; float fontSize;
bool italic = false; bool italic = false;
@ -40,6 +42,7 @@ struct Text::Impl
Impl(Text* p) : paint(p), shape(Shape::gen().release()) Impl(Text* p) : paint(p), shape(Shape::gen().release())
{ {
shape->fill(FillRule::EvenOdd);
} }
~Impl() ~Impl()
@ -94,27 +97,26 @@ struct Text::Impl
return PP(shape)->render(renderer); return PP(shape)->render(renderer);
} }
bool load() float load()
{ {
if (!loader) return false; if (!loader) return 0.0f;
loader->request(shape, utf8);
//reload //reload
if (changed) { if (changed) {
loader->read(); loader->read(shape, utf8, metrics);
changed = false; changed = false;
} }
return loader->transform(shape, fontSize, italic); return loader->transform(shape, metrics, fontSize, italic);
} }
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper) RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{ {
if (!load()) return nullptr; auto scale = 1.0f / load();
if (tvg::zero(scale)) return nullptr;
//transform the gradient coordinates based on the final scaled font. //transform the gradient coordinates based on the final scaled font.
auto fill = P(shape)->rs.fill; auto fill = P(shape)->rs.fill;
if (fill && P(shape)->rFlag & RenderUpdateFlag::Gradient) { if (fill && P(shape)->rFlag & RenderUpdateFlag::Gradient) {
auto scale = 1.0f / loader->scale;
if (fill->type() == Type::LinearGradient) { if (fill->type() == Type::LinearGradient) {
P(static_cast<LinearGradient*>(fill))->x1 *= scale; P(static_cast<LinearGradient*>(fill))->x1 *= scale;
P(static_cast<LinearGradient*>(fill))->y1 *= scale; P(static_cast<LinearGradient*>(fill))->y1 *= scale;
@ -134,7 +136,7 @@ struct Text::Impl
bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking) bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking)
{ {
if (!load()) return false; if (load() == 0.0f) return false;
PP(shape)->bounds(x, y, w, h, true, true, false); PP(shape)->bounds(x, y, w, h, true, true, false);
return true; return true;
} }