Files
2026-02-13 22:24:27 +08:00

162 lines
5.4 KiB
C++

// Copyright (c) 2024 Caleb Hearon <caleb@chearon.net>
#include "FontFace.h"
#include "FontParser.h"
#include "InstanceData.h"
#include <string>
#include <cstring>
static size_t lastId = 0;
FontFace::FontFace(const Napi::CallbackInfo& info) :
Napi::ObjectWrap<FontFace>(info),
id(++lastId),
env(info.Env())
{
if (info.Length() < 2) {
Napi::TypeError::New(env, "Family and source arguments are required").ThrowAsJavaScriptException();
return;
}
if (!setFamilyInternal(info[0])) return;
if (info[1].IsString()) {
Napi::String url = info[1].As<Napi::String>();
size_t len;
napi_get_value_string_utf8(env, url, nullptr, 0, &len);
len++;
descriptor.url = std::make_unique<char[]>(len);
napi_get_value_string_utf8(env, url, descriptor.url.get(), len, nullptr);
} else if (info[1].IsTypedArray()) {
Napi::TypedArray ta = info[0].As<Napi::TypedArray>();
Napi::ArrayBuffer buf = ta.ArrayBuffer();
descriptor.data_len = ta.ByteLength();
descriptor.data = std::make_unique<uint8_t[]>(descriptor.data_len);
std::memcpy(descriptor.data.get(), static_cast<uint8_t*>(buf.Data()) + ta.ByteOffset(), descriptor.data_len);
} else if (info[1].IsArrayBuffer()) {
Napi::ArrayBuffer buf = info[0].As<Napi::ArrayBuffer>();
descriptor.data_len = buf.ByteLength();
descriptor.data = std::make_unique<uint8_t[]>(descriptor.data_len);
std::memcpy(descriptor.data.get(), buf.Data(), descriptor.data_len);
} else {
Napi::TypeError::New(env, "Source must be a string or buffer").ThrowAsJavaScriptException();
return;
}
if (info.Length() >= 3) {
if (!info[2].IsObject()) {
Napi::TypeError::New(env, "Descriptors must be an object").ThrowAsJavaScriptException();
return;
}
Napi::Object descriptors = info[2].As<Napi::Object>();
Napi::Value value;
if (descriptors.Has("weight").UnwrapOr(false) && descriptors.Get("weight").UnwrapTo(&value)) {
if (!setWeightInternal(value)) return;
}
if (descriptors.Has("style").UnwrapOr(false) && descriptors.Get("style").UnwrapTo(&value)) {
if (!setStyleInternal(value)) return;
}
}
}
void
FontFace::Initialize(Napi::Env& env, Napi::Object& exports) {
InstanceData *data = env.GetInstanceData<InstanceData>();
Napi::Function ctor = DefineClass(env, "FontFace", {
InstanceAccessor<&FontFace::GetFamily, &FontFace::SetFamily>("family", napi_default_jsproperty),
InstanceAccessor<&FontFace::GetStyle, &FontFace::SetStyle>("style", napi_default_jsproperty),
InstanceAccessor<&FontFace::GetWeight, &FontFace::SetWeight>("weight", napi_default_jsproperty),
InstanceAccessor<&FontFace::GetStatus>("status", napi_default_jsproperty)
});
data->FontFaceCtor = Napi::Persistent(ctor);
exports.Set("FontFace", ctor);
}
Napi::Value
FontFace::GetFamily(const Napi::CallbackInfo& info) {
return Napi::String::New(env, descriptor.family.get());
}
bool
FontFace::setFamilyInternal(const Napi::Value& value) {
// According to the specs, this is supposed to go through the CSS parser, so
// fonts starting with numbers or having special characters should actually
// throw an error. However, Safari accepts anything, and Firefox puts quotes
// around the family: https://bugzilla.mozilla.org/show_bug.cgi?id=1986533
if (Napi::String family; value.ToString().UnwrapTo(&family)) {
size_t len;
napi_get_value_string_utf8(env, family, nullptr, 0, &len);
len++;
descriptor.family = std::make_unique<char[]>(len);
napi_get_value_string_utf8(env, family, descriptor.family.get(), len, nullptr);
return true;
}
return false;
}
void
FontFace::SetFamily(const Napi::CallbackInfo& info, const Napi::Value& value) {
setFamilyInternal(value);
}
Napi::Value
FontFace::GetStyle(const Napi::CallbackInfo& info) {
switch (descriptor.style) {
case FontStyle::Normal: return Napi::String::New(env, "normal");
case FontStyle::Italic: return Napi::String::New(env, "italic");
case FontStyle::Oblique: return Napi::String::New(env, "oblique");
}
}
bool
FontFace::setStyleInternal(const Napi::Value& value) {
if (Napi::String style; value.ToString().UnwrapTo(&style)) {
if (auto value = FontParser::parseStyle(style.Utf8Value()); value) {
descriptor.style = *value;
return true;
} else {
Napi::TypeError::New(env, "Could not parse style").ThrowAsJavaScriptException();
}
}
return false;
}
void
FontFace::SetStyle(const Napi::CallbackInfo& info, const Napi::Value& value) {
setStyleInternal(value);
}
Napi::Value
FontFace::GetWeight(const Napi::CallbackInfo& info) {
return Napi::String::New(env, std::to_string(descriptor.weight));
}
bool
FontFace::setWeightInternal(const Napi::Value& value) {
if (Napi::String weight; value.ToString().UnwrapTo(&weight)) {
if (auto value = FontParser::parseWeight(weight.Utf8Value()); value) {
descriptor.weight = *value;
return true;
} else {
Napi::TypeError::New(env, "Could not parse weight").ThrowAsJavaScriptException();
}
}
return false;
}
void
FontFace::SetWeight(const Napi::CallbackInfo& info, const Napi::Value& value) {
setWeightInternal(value);
}
Napi::Value
FontFace::GetStatus(const Napi::CallbackInfo& info) {
switch (status) {
case Status::Unloaded: return Napi::String::New(env, "unloaded");
case Status::Loaded: return Napi::String::New(env, "loaded");
case Status::Error: return Napi::String::New(env, "error");
}
}