Skip to content

Commit

Permalink
Fix the rendering issue with ZWJ emojis on apple platforms.
Browse files Browse the repository at this point in the history
  • Loading branch information
domchen committed Dec 23, 2024
1 parent de9f704 commit 6f92f40
Show file tree
Hide file tree
Showing 23 changed files with 136 additions and 137 deletions.
2 changes: 1 addition & 1 deletion ios/PAGViewer/Classes/ViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ - (void)addPAGViewAndPlay {
PAGFile* pagFile = [PAGFile Load:path];
if ([pagFile numTexts] > 0) {
PAGText* textData = [pagFile getTextData:0];
textData.text = @"hah哈 哈哈哈哈👌하";
textData.text = @"hah哈 哈哈👩🏼‍❤️‍👨🏽哈哈👌하";
[pagFile replaceText:0 data:textData];
}

Expand Down
6 changes: 3 additions & 3 deletions src/platform/Platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ NALUType Platform::naluType() const {
void Platform::traceImage(const tgfx::ImageInfo&, const void*, const std::string&) const {
}

std::optional<PositionedGlyphs> Platform::shapeText(const std::string&,
const std::shared_ptr<tgfx::Typeface>&) const {
return std::nullopt;
std::vector<ShapedGlyph> Platform::shapeText(const std::string&,
std::shared_ptr<tgfx::Typeface>) const {
return {};
}

std::string Platform::getCacheDir() const {
Expand Down
6 changes: 3 additions & 3 deletions src/platform/Platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#include <unordered_map>
#include "codec/NALUType.h"
#include "rendering/utils/DisplayLink.h"
#include "rendering/utils/shaper/PositionedGlyphs.h"
#include "rendering/utils/shaper/ShapedGlyph.h"
#include "rendering/video/VideoDecoderFactory.h"
#include "tgfx/core/Data.h"
#include "tgfx/core/ImageInfo.h"
Expand Down Expand Up @@ -76,8 +76,8 @@ class Platform {
/**
* Returns the shaped glyphs of the given text and typeface.
*/
virtual std::optional<PositionedGlyphs> shapeText(
const std::string& text, const std::shared_ptr<tgfx::Typeface>& typeface) const;
virtual std::vector<ShapedGlyph> shapeText(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) const;

/**
* Returns the corresponding sandbox path from the absolute file path, which usually starts with
Expand Down
4 changes: 2 additions & 2 deletions src/platform/cocoa/private/CocoaPlatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class CocoaPlatform : public Platform {
void traceImage(const tgfx::ImageInfo& info, const void* pixels,
const std::string& tag) const override;

std::optional<PositionedGlyphs> shapeText(
const std::string& text, const std::shared_ptr<tgfx::Typeface>& typeface) const override;
std::vector<ShapedGlyph> shapeText(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) const override;

std::string getCacheDir() const override;
};
Expand Down
18 changes: 4 additions & 14 deletions src/platform/cocoa/private/CocoaPlatform.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@

#include "CocoaPlatform.h"
#import <Foundation/Foundation.h>
#include "NativeTextShaper.h"
#include "TraceImage.h"
#include "pag/pag.h"
#ifdef PAG_USE_HARFBUZZ
#include "base/utils/USE.h"
#else
#include "NativeTextShaper.h"
#endif

namespace pag {
bool CocoaPlatform::registerFallbackFonts() const {
Expand Down Expand Up @@ -69,15 +65,9 @@
return [cachePath UTF8String];
}

std::optional<PositionedGlyphs> CocoaPlatform::shapeText(
const std::string& text, const std::shared_ptr<tgfx::Typeface>& typeface) const {
#ifdef PAG_USE_HARFBUZZ
USE(text);
USE(typeface);
return std::nullopt;
#else
return NativeTextShaper::Shape(text, typeface);
#endif
std::vector<ShapedGlyph> CocoaPlatform::shapeText(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) const {
return NativeTextShaper::Shape(text, std::move(typeface));
}

} // namespace pag
6 changes: 3 additions & 3 deletions src/platform/cocoa/private/NativeTextShaper.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
#pragma once

#include <optional>
#include "rendering/utils/shaper/PositionedGlyphs.h"
#include "rendering/utils/shaper/ShapedGlyph.h"
#include "tgfx/core/Typeface.h"

namespace pag {
class NativeTextShaper {
public:
static std::optional<PositionedGlyphs> Shape(const std::string& text,
const std::shared_ptr<tgfx::Typeface>& typeface);
static std::vector<ShapedGlyph> Shape(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface);
};
} // namespace pag
35 changes: 20 additions & 15 deletions src/platform/cocoa/private/NativeTextShaper.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@
#include "tgfx/utils/UTF.h"

namespace pag {
std::optional<PositionedGlyphs> NativeTextShaper::Shape(
const std::string& text, const std::shared_ptr<tgfx::Typeface>& typeface) {
auto ctFont = tgfx::CTTypeface::GetCTFont(typeface.get());
if (ctFont == nullptr) {
return std::nullopt;
}
std::vector<uint32_t> clusters;
std::vector<ShapedGlyph> NativeTextShaper::Shape(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) {
auto mainFont = tgfx::CTTypeface::GetCTFont(typeface.get());
std::vector<uint32_t> clusters = {};
const char* textStart = text.data();
const char* textStop = textStart + text.size();
while (textStart < textStop) {
Expand All @@ -43,17 +40,19 @@
auto str = CFStringCreateWithCString(kCFAllocatorDefault, text.c_str(), kCFStringEncodingUTF8);
auto attr = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(attr, kCTFontAttributeName, ctFont);
if (mainFont != nullptr) {
CFDictionaryAddValue(attr, kCTFontAttributeName, mainFont);
}
auto attrString = CFAttributedStringCreate(kCFAllocatorDefault, str, attr);
auto line = CTLineCreateWithAttributedString(attrString);
auto runs = CTLineGetGlyphRuns(line);
std::vector<std::tuple<std::shared_ptr<tgfx::Typeface>, tgfx::GlyphID, uint32_t>> glyphIDs;
std::vector<ShapedGlyph> glyphs = {};
for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
auto run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(runs, i));
auto attrs = CTRunGetAttributes(run);
auto font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName);
std::shared_ptr<tgfx::Typeface> face;
if (font == ctFont) {
if (font == mainFont) {
face = typeface;
} else {
face = tgfx::CTTypeface::MakeFromCTFont(font);
Expand All @@ -62,18 +61,24 @@
}
}
auto count = CTRunGetGlyphCount(run);
std::vector<CGGlyph> glyphs(count);
CTRunGetGlyphs(run, CFRangeMake(0, count), glyphs.data());
std::vector<CGGlyph> glyphIDs(count);
CTRunGetGlyphs(run, CFRangeMake(0, count), glyphIDs.data());
std::vector<CFIndex> indices(count);
CTRunGetStringIndices(run, CFRangeMake(0, count), indices.data());
for (size_t j = 0; j < glyphs.size(); j++) {
glyphIDs.emplace_back(face, static_cast<tgfx::GlyphID>(glyphs[j]), clusters[indices[j]]);
std::vector<CGPoint> positions(count);
CTRunGetPositions(run, CFRangeMake(0, count), positions.data());
for (size_t j = 0; j < glyphIDs.size(); j++) {
if (j > 0 && positions[j].x == positions[j - 1].x) {
glyphs.back().glyphIDs.emplace_back(static_cast<tgfx::GlyphID>(glyphIDs[j]));
continue;
}
glyphs.emplace_back(face, static_cast<tgfx::GlyphID>(glyphIDs[j]), clusters[indices[j]]);
}
}
CFRelease(line);
CFRelease(attrString);
CFRelease(attr);
CFRelease(str);
return PositionedGlyphs(glyphIDs);
return glyphs;
}
} // namespace pag
10 changes: 2 additions & 8 deletions src/platform/web/NativePlatform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,9 @@ void NativePlatform::traceImage(const tgfx::ImageInfo& info, const void* pixels,
traceImage(info, bytes, tag);
}

std::optional<PositionedGlyphs> NativePlatform::shapeText(
const std::string& text, const std::shared_ptr<tgfx::Typeface>& typeface) const {
#ifdef PAG_USE_HARBUZZ
USE(text);
USE(typeface);
return std::nullopt;
#else
std::vector<ShapedGlyph> NativePlatform::shapeText(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) const {
return NativeTextShaper::Shape(text, typeface);
#endif
}

std::vector<const VideoDecoderFactory*> NativePlatform::getVideoDecoderFactories() const {
Expand Down
4 changes: 2 additions & 2 deletions src/platform/web/NativePlatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class NativePlatform : public Platform {
void traceImage(const tgfx::ImageInfo& info, const void* pixels,
const std::string& tag) const override;

std::optional<PositionedGlyphs> shapeText(
const std::string& text, const std::shared_ptr<tgfx::Typeface>& typeface) const override;
std::vector<ShapedGlyph> shapeText(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) const override;

std::vector<const VideoDecoderFactory*> getVideoDecoderFactories() const override;
};
Expand Down
15 changes: 7 additions & 8 deletions src/platform/web/NativeTextShaper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,20 @@ static void MergeClusters(std::vector<Info>& infos) {
}
}

PositionedGlyphs NativeTextShaper::Shape(const std::string& text,
const std::shared_ptr<tgfx::Typeface>& typeface) {
std::vector<Info> infos;
const char* textStart = text.data();
const char* textStop = textStart + text.size();
std::vector<ShapedGlyph> NativeTextShaper::Shape(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface) {
std::vector<Info> infos = {};
auto textStart = text.data();
auto textStop = textStart + text.size();
while (textStart < textStop) {
auto cluster = static_cast<uint32_t>(textStart - text.data());
infos.emplace_back(Info{tgfx::UTF::NextUTF8(&textStart, textStop), cluster});
}

CheckContinuations(infos);

MergeClusters(infos);

std::vector<std::tuple<std::shared_ptr<tgfx::Typeface>, tgfx::GlyphID, uint32_t>> glyphs;
std::vector<ShapedGlyph> glyphs = {};
auto fallbackTypefaces = FontManager::GetFallbackTypefaces();
for (size_t i = 0; i < infos.size(); ++i) {
auto length = (i + 1 == infos.size() ? text.length() : infos[i + 1].cluster) - infos[i].cluster;
Expand All @@ -130,6 +129,6 @@ PositionedGlyphs NativeTextShaper::Shape(const std::string& text,
glyphs.emplace_back(typeface, typeface->getGlyphID(str), infos[i].cluster);
}
}
return PositionedGlyphs(glyphs);
return glyphs;
}
} // namespace pag
6 changes: 3 additions & 3 deletions src/platform/web/NativeTextShaper.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

#pragma once

#include "rendering/utils/shaper/PositionedGlyphs.h"
#include "rendering/utils/shaper/ShapedGlyph.h"
#include "tgfx/core/Typeface.h"

namespace pag {
class NativeTextShaper {
public:
static PositionedGlyphs Shape(const std::string& text,
const std::shared_ptr<tgfx::Typeface>& typeface);
static std::vector<ShapedGlyph> Shape(const std::string& text,
std::shared_ptr<tgfx::Typeface> typeface);
};
} // namespace pag
6 changes: 4 additions & 2 deletions src/rendering/caches/TextAtlas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,10 @@ static std::vector<Page> CreatePages(const std::vector<GlyphHandle>& glyphs, int
pack.reset();
point = pack.addRect(width, height);
}
textRun->glyphIDs.push_back(glyph->getGlyphID());
textRun->positions.push_back({-bounds.x() + point.x, -bounds.y() + point.y});
for (auto& glyphID : glyph->getGlyphIDs()) {
textRun->glyphIDs.push_back(glyphID);
textRun->positions.push_back({-bounds.x() + point.x, -bounds.y() + point.y});
}
AtlasLocator locator;
locator.imageIndex = pages.size();
locator.location = tgfx::Rect::MakeXYWH(point.x, point.y, static_cast<float>(width),
Expand Down
39 changes: 22 additions & 17 deletions src/rendering/graphics/Glyph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,34 @@ std::vector<GlyphHandle> Glyph::BuildFromText(const std::string& text, const tgf
auto textFont = font;
std::unordered_map<std::string, GlyphHandle> glyphMap;
std::vector<GlyphHandle> glyphList;
auto positionedGlyphs = TextShaper::Shape(text, font.getTypeface());
auto count = positionedGlyphs.glyphCount();
auto shapedGlyphs = TextShaper::Shape(text, font.getTypeface());
auto count = shapedGlyphs.size();
for (size_t i = 0; i < count; ++i) {
auto index = positionedGlyphs.getStringIndex(i);
auto length = (i + 1 == count ? text.length() : positionedGlyphs.getStringIndex(i + 1)) - index;
auto name = text.substr(index, length);
auto& shapedGlyph = shapedGlyphs[i];
auto length = (i + 1 == count ? text.length() : shapedGlyphs[i + 1].stringIndex) -
shapedGlyph.stringIndex;
auto name = text.substr(shapedGlyph.stringIndex, length);
if (glyphMap.find(name) != glyphMap.end()) {
glyphList.emplace_back(std::make_shared<Glyph>(*glyphMap[name]));
continue;
}
textFont.setTypeface(positionedGlyphs.getTypeface(i));
auto glyph = std::shared_ptr<Glyph>(
new Glyph(positionedGlyphs.getGlyphID(i), name, textFont, isVertical, paint));
textFont.setTypeface(shapedGlyph.typeface);
auto glyph =
std::shared_ptr<Glyph>(new Glyph(shapedGlyph.glyphIDs, name, textFont, isVertical, paint));
glyphMap[name] = glyph;
glyphList.emplace_back(glyph);
}
return glyphList;
}

Glyph::Glyph(tgfx::GlyphID glyphId, std::string name, tgfx::Font font, bool isVertical,
const TextPaint& textPaint)
: _glyphId(glyphId), _name(std::move(name)), _font(std::move(font)), _isVertical(isVertical) {
horizontalInfo->advance = _font.getAdvance(_glyphId);
Glyph::Glyph(std::vector<tgfx::GlyphID> glyphIDs, std::string name, tgfx::Font font,
bool isVertical, const TextPaint& textPaint)
: _glyphIDs(std::move(glyphIDs)), _name(std::move(name)), _font(std::move(font)),
_isVertical(isVertical) {
auto glyphID = _glyphIDs.front();
horizontalInfo->advance = _font.getAdvance(glyphID);
horizontalInfo->originPosition.set(horizontalInfo->advance / 2, 0);
horizontalInfo->bounds = _font.getBounds(_glyphId);
horizontalInfo->bounds = _font.getBounds(glyphID);
auto metrics = _font.getMetrics();
if (horizontalInfo->bounds.isEmpty() && horizontalInfo->advance > 0) {
horizontalInfo->bounds.setLTRB(0, metrics.ascent, horizontalInfo->advance, metrics.descent);
Expand All @@ -78,10 +81,10 @@ Glyph::Glyph(tgfx::GlyphID glyphId, std::string name, tgfx::Font font, bool isVe
verticalInfo->ascent += offsetX;
verticalInfo->descent += offsetX;
} else {
auto offset = _font.getVerticalOffset(_glyphId);
auto offset = _font.getVerticalOffset(glyphID);
verticalInfo->extraMatrix.postTranslate(offset.x, offset.y);
auto width = verticalInfo->advance;
verticalInfo->advance = _font.getAdvance(_glyphId, true);
verticalInfo->advance = _font.getAdvance(glyphID, true);
if (verticalInfo->advance == 0) {
verticalInfo->advance = width;
}
Expand Down Expand Up @@ -131,7 +134,9 @@ tgfx::Matrix Glyph::getTotalMatrix() const {

void Glyph::computeAtlasKey(tgfx::BytesKey* bytesKey, TextStyle style) const {
bytesKey->write(static_cast<uint32_t>(getFont().hasColor()));
bytesKey->write(static_cast<uint32_t>(getGlyphID()));
for (auto& glyphID : getGlyphIDs()) {
bytesKey->write(glyphID);
}
bytesKey->write(static_cast<uint32_t>(style));
}

Expand All @@ -154,6 +159,6 @@ std::shared_ptr<Glyph> Glyph::makeScaledGlyph(float s) const {
textPaint.fillColor = fillColor;
textPaint.strokeColor = strokeColor;
textPaint.strokeWidth = strokeWidth * s;
return std::shared_ptr<Glyph>(new Glyph(_glyphId, _name, scaledFont, _isVertical, textPaint));
return std::shared_ptr<Glyph>(new Glyph(_glyphIDs, _name, scaledFont, _isVertical, textPaint));
}
} // namespace pag
8 changes: 4 additions & 4 deletions src/rendering/graphics/Glyph.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ class Glyph {
/**
* Returns the id of this glyph in associated typeface.
*/
tgfx::GlyphID getGlyphID() const {
return _glyphId;
const std::vector<tgfx::GlyphID>& getGlyphIDs() const {
return _glyphIDs;
}

/**
Expand Down Expand Up @@ -250,7 +250,7 @@ class Glyph {
};

// read only attributes:
tgfx::GlyphID _glyphId = 0;
std::vector<tgfx::GlyphID> _glyphIDs = {};
std::string _name;
tgfx::Font _font;
bool _isVertical = false;
Expand All @@ -268,7 +268,7 @@ class Glyph {
std::shared_ptr<Info> verticalInfo;
const Info* info = horizontalInfo.get();

Glyph(tgfx::GlyphID glyphId, std::string name, tgfx::Font font, bool isVertical,
Glyph(std::vector<tgfx::GlyphID> glyphIDs, std::string name, tgfx::Font font, bool isVertical,
const TextPaint& textPaint);
};
} // namespace pag
6 changes: 4 additions & 2 deletions src/rendering/graphics/Text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ static std::unique_ptr<TextRun> MakeTextRun(const std::vector<Glyph*>& glyphs) {
std::vector<tgfx::GlyphID> glyphIDs = {};
std::vector<tgfx::Point> positions = {};
for (auto& glyph : glyphs) {
glyphIDs.push_back(glyph->getGlyphID());
auto m = glyph->getTotalMatrix();
m.postConcat(noTranslateMatrix);
positions.push_back({m.getTranslateX(), m.getTranslateY()});
for (auto& glyphID : glyph->getGlyphIDs()) {
glyphIDs.push_back(glyphID);
positions.push_back({m.getTranslateX(), m.getTranslateY()});
}
}
textRun->textFont = firstGlyph->getFont();
textRun->glyphIDs = glyphIDs;
Expand Down
Loading

0 comments on commit 6f92f40

Please sign in to comment.