Skip to content

Native text drawing on macOS/iOS #19184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,12 @@ if(OPENXR AND NOT ARMV7_DEVICE)
set(nativeExtraLibs ${nativeExtraLibs} openxr_loader)
endif()

if(IOS OR MACOSX)
set(nativeExtra ${nativeExtra}
Common/Render/Text/draw_text_cocoa.mm
Common/Render/Text/draw_text_cocoa.h)
endif()

if(ANDROID)
set(NativeAppSource ${NativeAppSource}
android/jni/app-android.cpp
Expand Down Expand Up @@ -1317,7 +1323,7 @@ elseif(IOS AND NOT LIBRETRO)
Common/Battery/AppleBatteryClient.m
)

set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreVideo -framework CoreMedia -framework CoreServices -framework Metal -framework IOSurface" )
set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreText -framework CoreVideo -framework CoreMedia -framework CoreServices -framework Metal -framework IOSurface" )
if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework")
set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController")
endif()
Expand Down
14 changes: 7 additions & 7 deletions Common/Math/geom2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

#include <cmath>

struct Point {
Point() : x(0.0f), y(0.0f) {}
Point(float x_, float y_) : x(x_), y(y_) {}
struct Point2D {
Point2D() : x(0.0f), y(0.0f) {}
Point2D(float x_, float y_) : x(x_), y(y_) {}

float x;
float y;

float distanceTo(const Point &other) const {
float distanceTo(const Point2D &other) const {
float dx = other.x - x, dy = other.y - y;
return sqrtf(dx*dx + dy*dy);
}

bool operator ==(const Point &other) const {
bool operator ==(const Point2D &other) const {
return x == other.x && y == other.y;
}

Expand Down Expand Up @@ -60,8 +60,8 @@ struct Bounds {
float y2() const { return y + h; }
float centerX() const { return x + w * 0.5f; }
float centerY() const { return y + h * 0.5f; }
Point Center() const {
return Point(centerX(), centerY());
Point2D Center() const {
return Point2D(centerX(), centerY());
}
Bounds Expand(float amount) const {
return Bounds(x - amount, y - amount, w + amount * 2, h + amount * 2);
Expand Down
7 changes: 5 additions & 2 deletions Common/Render/Text/draw_text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "Common/Render/Text/draw_text.h"
#include "Common/Render/Text/draw_text_win.h"
#include "Common/Render/Text/draw_text_cocoa.h"
#include "Common/Render/Text/draw_text_uwp.h"
#include "Common/Render/Text/draw_text_qt.h"
#include "Common/Render/Text/draw_text_android.h"
Expand Down Expand Up @@ -70,15 +71,15 @@ void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const
DrawString(target, toDraw.c_str(), x, y, color, align);
}

void TextDrawer::DrawStringBitmapRect(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align) {
bool TextDrawer::DrawStringBitmapRect(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align) {
std::string toDraw(str);
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
if (wrap) {
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap);
}

DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align);
return DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align);
}

TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) {
Expand All @@ -89,6 +90,8 @@ TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) {
drawer = new TextDrawerWin32(draw);
#elif PPSSPP_PLATFORM(UWP)
drawer = new TextDrawerUWP(draw);
#elif PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
drawer = new TextDrawerCocoa(draw);
#elif defined(USING_QT_UI)
drawer = new TextDrawerQt(draw);
#elif PPSSPP_PLATFORM(ANDROID)
Expand Down
13 changes: 11 additions & 2 deletions Common/Render/Text/draw_text.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <memory>
#include <cstdint>
#include <map>

#include "Common/Data/Text/WrapText.h"
#include "Common/Render/DrawBuffer.h"
Expand All @@ -33,6 +34,7 @@ struct TextStringEntry {
struct TextMeasureEntry {
int width;
int height;
int leading; // only used with Cocoa
int lastUsedFrame;
};

Expand All @@ -46,10 +48,14 @@ class TextDrawer {
void SetFontScale(float xscale, float yscale);
virtual void MeasureString(std::string_view str, float *w, float *h) = 0;
virtual void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) = 0;

// TODO: This one we should be able to make a default implementation for, calling the specialized DrawBitmap.
// Only problem is that we need to make sure that the texFormats are all supported by all the backends, or we explicitly limit.
virtual void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) = 0;

void DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align);
virtual void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) = 0;
void DrawStringBitmapRect(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align);
virtual bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) = 0;
bool DrawStringBitmapRect(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, const Bounds &bounds, int align);
// Use for housekeeping like throwing out old strings.
virtual void OncePerFrame() = 0;

Expand Down Expand Up @@ -86,6 +92,9 @@ class TextDrawer {
float fontScaleY_ = 1.0f;
float dpiScale_ = 1.0f;
bool ignoreGlobalDpi_ = false;

std::map<CacheKey, std::unique_ptr<TextStringEntry>> cache_;
std::map<CacheKey, std::unique_ptr<TextMeasureEntry>> sizeCache_;
};

class TextDrawerWordWrapper : public WordWrapper {
Expand Down
5 changes: 3 additions & 2 deletions Common/Render/Text/draw_text_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ void TextDrawerAndroid::MeasureStringRect(std::string_view str, const Bounds &bo
*h = total_h * fontScaleY_ * dpiScale_;
}

void TextDrawerAndroid::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) {
bool TextDrawerAndroid::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align) {
if (str.empty()) {
bitmapData.clear();
return;
return false;
}

double size = 0.0;
Expand Down Expand Up @@ -244,6 +244,7 @@ void TextDrawerAndroid::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextS
}
env->ReleaseIntArrayElements(imageData, jimage, 0);
env->DeleteLocalRef(imageData);
return true;
}

void TextDrawerAndroid::DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align) {
Expand Down
5 changes: 1 addition & 4 deletions Common/Render/Text/draw_text_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class TextDrawerAndroid : public TextDrawer {
void MeasureString(std::string_view str, float *w, float *h) override;
void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override;
void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override;
void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override;
// Use for housekeeping like throwing out old strings.
void OncePerFrame() override;

Expand All @@ -43,9 +43,6 @@ class TextDrawerAndroid : public TextDrawer {
bool use4444Format_ = false;

std::map<uint32_t, AndroidFontEntry> fontMap_;

std::map<CacheKey, std::unique_ptr<TextStringEntry>> cache_;
std::map<CacheKey, std::unique_ptr<TextMeasureEntry>> sizeCache_;
};

#endif
40 changes: 40 additions & 0 deletions Common/Render/Text/draw_text_cocoa.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include "ppsspp_config.h"

#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)

#include <map>
#include "Common/Render/Text/draw_text.h"

struct TextDrawerContext;
// Internal struct but all details in .cpp file (pimpl to avoid pulling in excessive headers here)
class TextDrawerFontContext;

class TextDrawerCocoa : public TextDrawer {
public:
TextDrawerCocoa(Draw::DrawContext *draw);
~TextDrawerCocoa();

uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override;
void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align = ALIGN_TOPLEFT) override;
// Use for housekeeping like throwing out old strings.
void OncePerFrame() override;

protected:
void ClearCache() override;
void RecreateFonts(); // On DPI change

TextDrawerContext *ctx_;
std::map<uint32_t, std::unique_ptr<TextDrawerFontContext>> fontMap_;

uint32_t fontHash_;
std::map<CacheKey, std::unique_ptr<TextStringEntry>> cache_;
std::map<CacheKey, std::unique_ptr<TextMeasureEntry>> sizeCache_;
};

#endif
Loading
Loading