mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 15:26:07 +00:00
Backed out changeset 8284007baae4 (bug 878674) for Android startup java exceptions
This commit is contained in:
parent
b068dae3e8
commit
3b7e3a0a84
@ -52,7 +52,6 @@
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsISimpleEnumerator.h"
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/scache/StartupCache.h"
|
||||
#include <sys/stat.h>
|
||||
|
||||
@ -78,8 +77,6 @@ GetFontInfoLog()
|
||||
#define LOG(args) PR_LOG(GetFontInfoLog(), PR_LOG_DEBUG, args)
|
||||
#define LOG_ENABLED() PR_LOG_TEST(GetFontInfoLog(), PR_LOG_DEBUG)
|
||||
|
||||
static cairo_user_data_key_t sFTUserFontDataKey;
|
||||
|
||||
static __inline void
|
||||
BuildKeyNameFromFontName(nsAString &aName)
|
||||
{
|
||||
@ -95,89 +92,45 @@ BuildKeyNameFromFontName(nsAString &aName)
|
||||
// This allows us to read font names, tables, etc if necessary
|
||||
// without permanently instantiating a freetype face and consuming
|
||||
// memory long-term.
|
||||
// This may fail (resulting in a null FT_Face), e.g. if it fails to
|
||||
// allocate memory to uncompress a font from omnijar.
|
||||
class AutoFTFace {
|
||||
public:
|
||||
AutoFTFace(FT2FontEntry* aFontEntry)
|
||||
: mFace(nullptr), mFontDataBuf(nullptr), mOwnsFace(false)
|
||||
: mFace(nullptr), mOwnsFace(false)
|
||||
{
|
||||
if (aFontEntry->mFTFace) {
|
||||
mFace = aFontEntry->mFTFace;
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(),
|
||||
"can't use AutoFTFace for fonts without a filename");
|
||||
FT_Library ft = gfxToolkitPlatform::GetPlatform()->GetFTLibrary();
|
||||
|
||||
// A relative path (no initial "/") means this is a resource in
|
||||
// omnijar, not an installed font on the device.
|
||||
// The NS_ASSERTIONs here should never fail, as the resource must have
|
||||
// been read successfully during font-list initialization or we'd never
|
||||
// have created the font entry. The only legitimate runtime failure
|
||||
// here would be memory allocation, in which case mFace remains null.
|
||||
if (aFontEntry->mFilename[0] != '/') {
|
||||
nsRefPtr<nsZipArchive> reader =
|
||||
Omnijar::GetReader(Omnijar::Type::GRE);
|
||||
nsZipItem *item = reader->GetItem(aFontEntry->mFilename.get());
|
||||
NS_ASSERTION(item, "failed to find zip entry");
|
||||
|
||||
uint32_t bufSize = item->RealSize();
|
||||
mFontDataBuf = static_cast<uint8_t*>(moz_malloc(bufSize));
|
||||
if (mFontDataBuf) {
|
||||
nsZipCursor cursor(item, reader, mFontDataBuf, bufSize);
|
||||
cursor.Copy(&bufSize);
|
||||
NS_ASSERTION(bufSize == item->RealSize(),
|
||||
"error reading bundled font");
|
||||
|
||||
if (FT_Err_Ok != FT_New_Memory_Face(ft, mFontDataBuf, bufSize,
|
||||
aFontEntry->mFTFontIndex,
|
||||
&mFace)) {
|
||||
NS_WARNING("failed to create freetype face");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(),
|
||||
"can't use AutoFTFace for fonts without a filename");
|
||||
FT_Library ft = gfxToolkitPlatform::GetPlatform()->GetFTLibrary();
|
||||
if (FT_Err_Ok != FT_New_Face(ft, aFontEntry->mFilename.get(),
|
||||
aFontEntry->mFTFontIndex, &mFace)) {
|
||||
NS_WARNING("failed to create freetype face");
|
||||
}
|
||||
if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE)) {
|
||||
NS_WARNING("failed to select Unicode charmap");
|
||||
}
|
||||
mOwnsFace = true;
|
||||
}
|
||||
if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE)) {
|
||||
NS_WARNING("failed to select Unicode charmap");
|
||||
}
|
||||
mOwnsFace = true;
|
||||
}
|
||||
|
||||
~AutoFTFace() {
|
||||
if (mFace && mOwnsFace) {
|
||||
FT_Done_Face(mFace);
|
||||
if (mFontDataBuf) {
|
||||
moz_free(mFontDataBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator FT_Face() { return mFace; }
|
||||
|
||||
// If we 'forget' the FT_Face (used when ownership is handed over to Cairo),
|
||||
// we do -not- free the mFontDataBuf (if used); that also becomes the
|
||||
// responsibility of the new owner of the face.
|
||||
FT_Face forget() {
|
||||
NS_ASSERTION(mOwnsFace, "can't forget() when we didn't own the face");
|
||||
mOwnsFace = false;
|
||||
return mFace;
|
||||
}
|
||||
|
||||
const uint8_t* FontData() const { return mFontDataBuf; }
|
||||
|
||||
private:
|
||||
FT_Face mFace;
|
||||
uint8_t* mFontDataBuf; // Uncompressed data (for fonts stored in a JAR),
|
||||
// or null for fonts instantiated from a file.
|
||||
// If non-null, this must survive as long as the
|
||||
// FT_Face.
|
||||
bool mOwnsFace;
|
||||
FT_Face mFace;
|
||||
bool mOwnsFace;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -193,12 +146,7 @@ private:
|
||||
cairo_scaled_font_t *
|
||||
FT2FontEntry::CreateScaledFont(const gfxFontStyle *aStyle)
|
||||
{
|
||||
cairo_font_face_t *cairoFace = CairoFontFace();
|
||||
if (!cairoFace) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cairo_scaled_font_t *scaledFont = nullptr;
|
||||
cairo_scaled_font_t *scaledFont = NULL;
|
||||
|
||||
cairo_matrix_t sizeMatrix;
|
||||
cairo_matrix_t identityMatrix;
|
||||
@ -231,7 +179,7 @@ FT2FontEntry::CreateScaledFont(const gfxFontStyle *aStyle)
|
||||
cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF);
|
||||
}
|
||||
|
||||
scaledFont = cairo_scaled_font_create(cairoFace,
|
||||
scaledFont = cairo_scaled_font_create(CairoFontFace(),
|
||||
&sizeMatrix,
|
||||
&identityMatrix, fontOptions);
|
||||
cairo_font_options_destroy(fontOptions);
|
||||
@ -259,9 +207,6 @@ gfxFont*
|
||||
FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
|
||||
{
|
||||
cairo_scaled_font_t *scaledFont = CreateScaledFont(aFontStyle);
|
||||
if (!scaledFont) {
|
||||
return nullptr;
|
||||
}
|
||||
gfxFont *font = new gfxFT2Font(scaledFont, this, aFontStyle, aNeedsBold);
|
||||
cairo_scaled_font_destroy(scaledFont);
|
||||
return font;
|
||||
@ -318,8 +263,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *FontData() const { return mFontData; }
|
||||
|
||||
private:
|
||||
FT_Face mFace;
|
||||
const uint8_t *mFontData;
|
||||
@ -397,6 +340,8 @@ FT2FontEntry::CreateFontEntry(FT_Face aFace,
|
||||
const nsAString& aName,
|
||||
const uint8_t *aFontData)
|
||||
{
|
||||
static cairo_user_data_key_t key;
|
||||
|
||||
FT2FontEntry *fe = new FT2FontEntry(aName);
|
||||
fe->mItalic = FTFaceIsItalic(aFace);
|
||||
fe->mWeight = FTFaceGetWeight(aFace);
|
||||
@ -410,7 +355,7 @@ FT2FontEntry::CreateFontEntry(FT_Face aFace,
|
||||
(FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
|
||||
fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags);
|
||||
FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData);
|
||||
cairo_font_face_set_user_data(fe->mFontFace, &sFTUserFontDataKey,
|
||||
cairo_font_face_set_user_data(fe->mFontFace, &key,
|
||||
userFontData, FTFontDestroyFunc);
|
||||
}
|
||||
|
||||
@ -443,6 +388,8 @@ gfxFT2Font::GetFontEntry()
|
||||
cairo_font_face_t *
|
||||
FT2FontEntry::CairoFontFace()
|
||||
{
|
||||
static cairo_user_data_key_t key;
|
||||
|
||||
if (!mFontFace) {
|
||||
AutoFTFace face(this);
|
||||
if (!face) {
|
||||
@ -453,8 +400,8 @@ FT2FontEntry::CairoFontFace()
|
||||
FT_LOAD_DEFAULT :
|
||||
(FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
|
||||
mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags);
|
||||
FTUserFontData *userFontData = new FTUserFontData(face, face.FontData());
|
||||
cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey,
|
||||
FTUserFontData *userFontData = new FTUserFontData(face, nullptr);
|
||||
cairo_font_face_set_user_data(mFontFace, &key,
|
||||
userFontData, FTFontDestroyFunc);
|
||||
}
|
||||
return mFontFace;
|
||||
@ -517,24 +464,6 @@ FT2FontEntry::CopyFontTable(uint32_t aTableTag,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
hb_blob_t*
|
||||
FT2FontEntry::GetFontTable(uint32_t aTableTag)
|
||||
{
|
||||
if (mFontFace) {
|
||||
// if there's a cairo font face, we may be able to return a blob
|
||||
// that just wraps a range of the attached user font data
|
||||
FTUserFontData *userFontData = static_cast<FTUserFontData*>(
|
||||
cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey));
|
||||
if (userFontData && userFontData->FontData()) {
|
||||
return GetTableFromFontData(userFontData->FontData(), aTableTag);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, use the default method (which in turn will call our
|
||||
// implementation of CopyFontTable)
|
||||
return gfxFontEntry::GetFontTable(aTableTag);
|
||||
}
|
||||
|
||||
void
|
||||
FT2FontEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
||||
FontListSizes* aSizes) const
|
||||
@ -688,7 +617,7 @@ public:
|
||||
}
|
||||
|
||||
virtual void
|
||||
GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList,
|
||||
GetInfoForFile(nsCString& aFileName, nsCString& aFaceList,
|
||||
uint32_t *aTimestamp, uint32_t *aFilesize)
|
||||
{
|
||||
if (!mMap.ops) {
|
||||
@ -700,7 +629,7 @@ public:
|
||||
return;
|
||||
}
|
||||
FNCMapEntry* entry = static_cast<FNCMapEntry*>(hdr);
|
||||
if (entry && entry->mFilesize) {
|
||||
if (entry && entry->mTimestamp && entry->mFilesize) {
|
||||
*aTimestamp = entry->mTimestamp;
|
||||
*aFilesize = entry->mFilesize;
|
||||
aFaceList.Assign(entry->mFaces);
|
||||
@ -712,7 +641,7 @@ public:
|
||||
}
|
||||
|
||||
virtual void
|
||||
CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList,
|
||||
CacheFileInfo(nsCString& aFileName, nsCString& aFaceList,
|
||||
uint32_t aTimestamp, uint32_t aFilesize)
|
||||
{
|
||||
if (!mMap.ops) {
|
||||
@ -809,9 +738,9 @@ gfxFT2FontList::gfxFT2FontList()
|
||||
}
|
||||
|
||||
void
|
||||
gfxFT2FontList::AppendFacesFromCachedFaceList(const nsCString& aFileName,
|
||||
gfxFT2FontList::AppendFacesFromCachedFaceList(nsCString& aFileName,
|
||||
bool aStdFile,
|
||||
const nsCString& aFaceList)
|
||||
nsCString& aFaceList)
|
||||
{
|
||||
const char *beginning = aFaceList.get();
|
||||
const char *end = strchr(beginning, ',');
|
||||
@ -904,7 +833,7 @@ FT2FontEntry::CheckForBrokenFont(gfxFontFamily *aFamily)
|
||||
}
|
||||
|
||||
void
|
||||
gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
|
||||
gfxFT2FontList::AppendFacesFromFontFile(nsCString& aFileName,
|
||||
bool aStdFile,
|
||||
FontNameCache *aCache)
|
||||
{
|
||||
@ -940,7 +869,44 @@ gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
|
||||
if (FT_Err_Ok != FT_New_Face(ftLibrary, aFileName.get(), i, &face)) {
|
||||
continue;
|
||||
}
|
||||
AddFaceToList(aFileName, i, aStdFile, face, faceList);
|
||||
if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
|
||||
FT_Done_Face(face);
|
||||
continue;
|
||||
}
|
||||
|
||||
// build the font entry name and create an FT2FontEntry,
|
||||
// but do -not- keep a reference to the FT_Face
|
||||
FT2FontEntry* fe =
|
||||
CreateNamedFontEntry(face, aFileName.get(), i);
|
||||
|
||||
if (fe) {
|
||||
NS_ConvertUTF8toUTF16 name(face->family_name);
|
||||
BuildKeyNameFromFontName(name);
|
||||
gfxFontFamily *family = mFontFamilies.GetWeak(name);
|
||||
if (!family) {
|
||||
family = new FT2FontFamily(name);
|
||||
mFontFamilies.Put(name, family);
|
||||
if (mBadUnderlineFamilyNames.Contains(name)) {
|
||||
family->SetBadUnderlineFamily();
|
||||
}
|
||||
}
|
||||
fe->mStandardFace = aStdFile;
|
||||
family->AddFontEntry(fe);
|
||||
|
||||
fe->CheckForBrokenFont(family);
|
||||
|
||||
AppendToFaceList(faceList, name, fe);
|
||||
#ifdef PR_LOGGING
|
||||
if (LOG_ENABLED()) {
|
||||
LOG(("(fontinit) added (%s) to family (%s)"
|
||||
" with style: %s weight: %d stretch: %d",
|
||||
NS_ConvertUTF16toUTF8(fe->Name()).get(),
|
||||
NS_ConvertUTF16toUTF8(family->Name()).get(),
|
||||
fe->IsItalic() ? "italic" : "normal",
|
||||
fe->Weight(), fe->Stretch()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
FT_Done_Face(dummy);
|
||||
@ -950,160 +916,6 @@ gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
|
||||
}
|
||||
}
|
||||
|
||||
#define JAR_LAST_MODIFED_TIME "jar-last-modified-time"
|
||||
|
||||
void
|
||||
gfxFT2FontList::FindFontsInOmnijar(FontNameCache *aCache)
|
||||
{
|
||||
bool jarChanged = false;
|
||||
|
||||
mozilla::scache::StartupCache* cache =
|
||||
mozilla::scache::StartupCache::GetSingleton();
|
||||
char *cachedModifiedTimeBuf;
|
||||
uint32_t longSize;
|
||||
int64_t jarModifiedTime;
|
||||
if (cache &&
|
||||
NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME,
|
||||
&cachedModifiedTimeBuf,
|
||||
&longSize)) &&
|
||||
longSize == sizeof(int64_t))
|
||||
{
|
||||
nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE);
|
||||
jarFile->GetLastModifiedTime(&jarModifiedTime);
|
||||
if (jarModifiedTime > *(int64_t*)cachedModifiedTimeBuf) {
|
||||
jarChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* sJarSearchPaths[] = {
|
||||
"res/fonts/*.ttf$",
|
||||
};
|
||||
nsRefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
|
||||
for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) {
|
||||
nsZipFind* find;
|
||||
if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) {
|
||||
const char* path;
|
||||
uint16_t len;
|
||||
while (NS_SUCCEEDED(find->FindNext(&path, &len))) {
|
||||
nsCString entryName(path, len);
|
||||
AppendFacesFromOmnijarEntry(reader, entryName, aCache,
|
||||
jarChanged);
|
||||
}
|
||||
delete find;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
cache->PutBuffer(JAR_LAST_MODIFED_TIME, (char*)&jarModifiedTime,
|
||||
sizeof(jarModifiedTime));
|
||||
}
|
||||
}
|
||||
|
||||
// Given the freetype face corresponding to an entryName and face index,
|
||||
// add the face to the available font list and to the faceList string
|
||||
void
|
||||
gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
|
||||
bool aStdFile, FT_Face aFace,
|
||||
nsCString& aFaceList)
|
||||
{
|
||||
if (FT_Err_Ok != FT_Select_Charmap(aFace, FT_ENCODING_UNICODE)) {
|
||||
// ignore faces that don't support a Unicode charmap
|
||||
return;
|
||||
}
|
||||
|
||||
// build the font entry name and create an FT2FontEntry,
|
||||
// but do -not- keep a reference to the FT_Face
|
||||
FT2FontEntry* fe =
|
||||
CreateNamedFontEntry(aFace, aEntryName.get(), aIndex);
|
||||
|
||||
if (fe) {
|
||||
NS_ConvertUTF8toUTF16 name(aFace->family_name);
|
||||
BuildKeyNameFromFontName(name);
|
||||
gfxFontFamily *family = mFontFamilies.GetWeak(name);
|
||||
if (!family) {
|
||||
family = new FT2FontFamily(name);
|
||||
mFontFamilies.Put(name, family);
|
||||
if (mBadUnderlineFamilyNames.Contains(name)) {
|
||||
family->SetBadUnderlineFamily();
|
||||
}
|
||||
}
|
||||
fe->mStandardFace = aStdFile;
|
||||
family->AddFontEntry(fe);
|
||||
|
||||
fe->CheckForBrokenFont(family);
|
||||
|
||||
AppendToFaceList(aFaceList, name, fe);
|
||||
#ifdef PR_LOGGING
|
||||
if (LOG_ENABLED()) {
|
||||
LOG(("(fontinit) added (%s) to family (%s)"
|
||||
" with style: %s weight: %d stretch: %d",
|
||||
NS_ConvertUTF16toUTF8(fe->Name()).get(),
|
||||
NS_ConvertUTF16toUTF8(family->Name()).get(),
|
||||
fe->IsItalic() ? "italic" : "normal",
|
||||
fe->Weight(), fe->Stretch()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive,
|
||||
const nsCString& aEntryName,
|
||||
FontNameCache *aCache,
|
||||
bool aJarChanged)
|
||||
{
|
||||
nsCString faceList;
|
||||
if (aCache && !aJarChanged) {
|
||||
uint32_t filesize, timestamp;
|
||||
aCache->GetInfoForFile(aEntryName, faceList, ×tamp, &filesize);
|
||||
if (faceList.Length() > 0) {
|
||||
AppendFacesFromCachedFaceList(aEntryName, true, faceList);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsZipItem *item = aArchive->GetItem(aEntryName.get());
|
||||
NS_ASSERTION(item, "failed to find zip entry");
|
||||
|
||||
uint32_t bufSize = item->RealSize();
|
||||
// We use fallible allocation here; if there's not enough RAM, we'll simply
|
||||
// ignore the bundled fonts and fall back to the device's installed fonts.
|
||||
nsAutoPtr<uint8_t> buf(static_cast<uint8_t*>(moz_malloc(bufSize)));
|
||||
if (!buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsZipCursor cursor(item, aArchive, buf, bufSize);
|
||||
uint8_t* data = cursor.Copy(&bufSize);
|
||||
NS_ASSERTION(data && bufSize == item->RealSize(),
|
||||
"error reading bundled font");
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary();
|
||||
|
||||
FT_Face dummy;
|
||||
if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf, bufSize, 0, &dummy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (FT_Long i = 0; i < dummy->num_faces; i++) {
|
||||
FT_Face face;
|
||||
if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf, bufSize, i, &face)) {
|
||||
continue;
|
||||
}
|
||||
AddFaceToList(aEntryName, i, true, face, faceList);
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
|
||||
FT_Done_Face(dummy);
|
||||
|
||||
if (aCache && !faceList.IsEmpty()) {
|
||||
aCache->CacheFileInfo(aEntryName, faceList, 0, bufSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Called on each family after all fonts are added to the list;
|
||||
// this will sort faces to give priority to "standard" font files
|
||||
// if aUserArg is non-null (i.e. we're using it as a boolean flag)
|
||||
@ -1125,6 +937,131 @@ FinalizeFamilyMemberList(nsStringHashKey::KeyType aKey,
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
#define JAR_READ_BUFFER_SIZE 1024
|
||||
|
||||
nsresult
|
||||
CopyFromUriToFile(nsCString aSpec, nsIFile* aLocalFile)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = NS_OpenURI(getter_AddRefs(inputStream), uri);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), aLocalFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
char buf[JAR_READ_BUFFER_SIZE];
|
||||
while (true) {
|
||||
uint32_t read;
|
||||
uint32_t written;
|
||||
|
||||
rv = inputStream->Read(buf, JAR_READ_BUFFER_SIZE, &read);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = outputStream->Write(buf, read, &written);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (written != read) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (read != JAR_READ_BUFFER_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#define JAR_LAST_MODIFED_TIME "jar-last-modified-time"
|
||||
|
||||
void ExtractFontsFromJar(nsIFile* aLocalDir)
|
||||
{
|
||||
bool exists;
|
||||
bool allFontsExtracted = true;
|
||||
nsCString jarPath;
|
||||
int64_t jarModifiedTime;
|
||||
uint32_t longSize;
|
||||
char* cachedModifiedTimeBuf;
|
||||
nsZipFind* find;
|
||||
|
||||
nsRefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
|
||||
nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE);
|
||||
|
||||
Omnijar::GetURIString(Omnijar::Type::GRE, jarPath);
|
||||
jarFile->GetLastModifiedTime(&jarModifiedTime);
|
||||
|
||||
mozilla::scache::StartupCache* cache = mozilla::scache::StartupCache::GetSingleton();
|
||||
if (cache && NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, &cachedModifiedTimeBuf, &longSize))
|
||||
&& longSize == sizeof(int64_t)) {
|
||||
if (jarModifiedTime < *((int64_t*) cachedModifiedTimeBuf)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
aLocalDir->Exists(&exists);
|
||||
if (!exists) {
|
||||
aLocalDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
||||
}
|
||||
|
||||
static const char* sJarSearchPaths[] = {
|
||||
"res/fonts/*.ttf$",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ArrayLength(sJarSearchPaths); i++) {
|
||||
reader->FindInit(sJarSearchPaths[i], &find);
|
||||
while (true) {
|
||||
const char* tmpPath;
|
||||
uint16_t len;
|
||||
find->FindNext(&tmpPath, &len);
|
||||
if (!tmpPath) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsCString path(tmpPath, len);
|
||||
nsCOMPtr<nsIFile> localFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
|
||||
if (NS_FAILED(localFile->InitWithFile(aLocalDir))) {
|
||||
allFontsExtracted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t lastSlash = path.RFindChar('/');
|
||||
nsCString fileName;
|
||||
if (lastSlash == kNotFound) {
|
||||
fileName = path;
|
||||
} else {
|
||||
fileName = Substring(path, lastSlash + 1);
|
||||
}
|
||||
if (NS_FAILED(localFile->AppendNative(fileName))) {
|
||||
allFontsExtracted = false;
|
||||
continue;
|
||||
}
|
||||
int64_t lastModifiedTime;
|
||||
localFile->Exists(&exists);
|
||||
localFile->GetLastModifiedTime(&lastModifiedTime);
|
||||
if (!exists || lastModifiedTime < jarModifiedTime) {
|
||||
nsCString spec;
|
||||
spec.Append(jarPath);
|
||||
spec.Append(path);
|
||||
if (NS_FAILED(CopyFromUriToFile(spec, localFile))) {
|
||||
localFile->Remove(true);
|
||||
allFontsExtracted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allFontsExtracted && cache) {
|
||||
cache->PutBuffer(JAR_LAST_MODIFED_TIME, (char*)&jarModifiedTime, sizeof(int64_t));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
gfxFT2FontList::FindFonts()
|
||||
{
|
||||
@ -1217,25 +1154,25 @@ gfxFT2FontList::FindFonts()
|
||||
// if we can't find/read the font directory, we are doomed!
|
||||
NS_RUNTIMEABORT("Could not read the system fonts directory");
|
||||
}
|
||||
#endif // XP_WIN && ANDROID
|
||||
|
||||
// Look for fonts stored in omnijar, unless we're on a low-memory
|
||||
// device where we don't want to spend the RAM to decompress them.
|
||||
// (Prefs may disable this, or force-enable it even with low memory.)
|
||||
bool lowmem;
|
||||
nsCOMPtr<nsIMemory> mem = nsMemory::GetGlobalMemoryService();
|
||||
if ((NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem &&
|
||||
Preferences::GetBool("gfx.bundled_fonts.enabled")) ||
|
||||
Preferences::GetBool("gfx.bundled_fonts.force-enabled")) {
|
||||
FindFontsInOmnijar(&fnc);
|
||||
// look for fonts shipped with the product
|
||||
NS_NAMED_LITERAL_STRING(kFontsDirName, "fonts");
|
||||
nsCOMPtr<nsIFile> localDir;
|
||||
nsresult rv = NS_GetSpecialDirectory(NS_APP_RES_DIR,
|
||||
getter_AddRefs(localDir));
|
||||
if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(kFontsDirName))) {
|
||||
ExtractFontsFromJar(localDir);
|
||||
nsCString localPath;
|
||||
rv = localDir->GetNativePath(localPath);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
FindFontsInDir(localPath, &fnc);
|
||||
}
|
||||
}
|
||||
|
||||
// look for locally-added fonts in a "fonts" subdir of the profile
|
||||
nsCOMPtr<nsIFile> localDir;
|
||||
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
|
||||
getter_AddRefs(localDir));
|
||||
if (NS_SUCCEEDED(rv) &&
|
||||
NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
|
||||
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
|
||||
getter_AddRefs(localDir));
|
||||
if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(kFontsDirName))) {
|
||||
nsCString localPath;
|
||||
rv = localDir->GetNativePath(localPath);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
@ -1247,6 +1184,7 @@ gfxFT2FontList::FindFonts()
|
||||
// and marking "simple" families.
|
||||
// Passing non-null userData here says that we want faces to be sorted.
|
||||
mFontFamilies.Enumerate(FinalizeFamilyMemberList, this);
|
||||
#endif // XP_WIN && ANDROID
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
|
@ -23,17 +23,16 @@ using mozilla::dom::FontListEntry;
|
||||
|
||||
class FontNameCache;
|
||||
typedef struct FT_FaceRec_* FT_Face;
|
||||
class nsZipArchive;
|
||||
|
||||
class FT2FontEntry : public gfxFontEntry
|
||||
{
|
||||
public:
|
||||
FT2FontEntry(const nsAString& aFaceName) :
|
||||
gfxFontEntry(aFaceName),
|
||||
mFTFace(nullptr),
|
||||
mFontFace(nullptr),
|
||||
mFTFontIndex(0)
|
||||
gfxFontEntry(aFaceName)
|
||||
{
|
||||
mFTFace = nullptr;
|
||||
mFontFace = nullptr;
|
||||
mFTFontIndex = 0;
|
||||
}
|
||||
|
||||
~FT2FontEntry();
|
||||
@ -54,30 +53,22 @@ public:
|
||||
CreateFontEntry(const FontListEntry& aFLE);
|
||||
|
||||
// Create a font entry for a given freetype face; if it is an installed font,
|
||||
// also record the filename and index.
|
||||
// also record the filename and index
|
||||
// aFontData (if non-NULL) is NS_Malloc'ed data that aFace depends on,
|
||||
// to be freed after the face is destroyed
|
||||
static FT2FontEntry*
|
||||
CreateFontEntry(FT_Face aFace,
|
||||
const char *aFilename, uint8_t aIndex,
|
||||
CreateFontEntry(FT_Face aFace, const char *aFilename, uint8_t aIndex,
|
||||
const nsAString& aName,
|
||||
const uint8_t *aFontData = nullptr);
|
||||
|
||||
virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold);
|
||||
|
||||
// Create (if necessary) and return the cairo_font_face for this font.
|
||||
// This may fail and return null, so caller must be prepared to handle this.
|
||||
cairo_font_face_t *CairoFontFace();
|
||||
|
||||
// Create a cairo_scaled_font for this face, with the given style.
|
||||
// This may fail and return null, so caller must be prepared to handle this.
|
||||
cairo_scaled_font_t *CreateScaledFont(const gfxFontStyle *aStyle);
|
||||
|
||||
nsresult ReadCMAP();
|
||||
|
||||
virtual hb_blob_t* GetFontTable(uint32_t aTableTag) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsresult CopyFontTable(uint32_t aTableTag,
|
||||
FallibleTArray<uint8_t>& aBuffer) MOZ_OVERRIDE;
|
||||
|
||||
@ -94,7 +85,7 @@ public:
|
||||
cairo_font_face_t *mFontFace;
|
||||
|
||||
nsCString mFilename;
|
||||
uint8_t mFTFontIndex;
|
||||
uint8_t mFTFontIndex;
|
||||
};
|
||||
|
||||
class FT2FontFamily : public gfxFontFamily
|
||||
@ -133,26 +124,16 @@ protected:
|
||||
void AppendFaceFromFontListEntry(const FontListEntry& aFLE,
|
||||
bool isStdFile);
|
||||
|
||||
void AppendFacesFromFontFile(const nsCString& aFileName,
|
||||
void AppendFacesFromFontFile(nsCString& aFileName,
|
||||
bool isStdFile = false,
|
||||
FontNameCache *aCache = nullptr);
|
||||
|
||||
void AppendFacesFromOmnijarEntry(nsZipArchive *aReader,
|
||||
const nsCString& aEntryName,
|
||||
FontNameCache *aCache,
|
||||
bool aJarChanged);
|
||||
|
||||
void AppendFacesFromCachedFaceList(const nsCString& aFileName,
|
||||
void AppendFacesFromCachedFaceList(nsCString& aFileName,
|
||||
bool isStdFile,
|
||||
const nsCString& aFaceList);
|
||||
|
||||
void AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
|
||||
bool aStdFile, FT_Face aFace, nsCString& aFaceList);
|
||||
nsCString& aFaceList);
|
||||
|
||||
void FindFonts();
|
||||
|
||||
void FindFontsInOmnijar(FontNameCache *aCache);
|
||||
|
||||
#ifdef ANDROID
|
||||
void FindFontsInDir(const nsCString& aDir, FontNameCache* aFNC);
|
||||
#endif
|
||||
|
@ -557,6 +557,12 @@ gfxFT2Font::~gfxFT2Font()
|
||||
{
|
||||
}
|
||||
|
||||
cairo_font_face_t *
|
||||
gfxFT2Font::CairoFontFace()
|
||||
{
|
||||
return GetFontEntry()->CairoFontFace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the font in the gfxFont cache. If we don't find it, create one.
|
||||
* In either case, add a ref, append it to the aFonts array, and return it ---
|
||||
|
@ -25,6 +25,8 @@ public: // new functions
|
||||
bool aNeedsBold);
|
||||
virtual ~gfxFT2Font ();
|
||||
|
||||
cairo_font_face_t *CairoFontFace();
|
||||
|
||||
FT2FontEntry *GetFontEntry();
|
||||
|
||||
static already_AddRefed<gfxFT2Font>
|
||||
|
@ -442,33 +442,6 @@ gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
|
||||
return entry->ShareTableAndGetBlob(*aBuffer, &mFontTableCache);
|
||||
}
|
||||
|
||||
static int
|
||||
DirEntryCmp(const void* aKey, const void* aItem)
|
||||
{
|
||||
int32_t tag = *static_cast<const int32_t*>(aKey);
|
||||
const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem);
|
||||
return tag - int32_t(entry->tag);
|
||||
}
|
||||
|
||||
hb_blob_t*
|
||||
gfxFontEntry::GetTableFromFontData(const void* aFontData, uint32_t aTableTag)
|
||||
{
|
||||
const SFNTHeader* header =
|
||||
reinterpret_cast<const SFNTHeader*>(aFontData);
|
||||
const TableDirEntry* dir =
|
||||
reinterpret_cast<const TableDirEntry*>(header + 1);
|
||||
dir = static_cast<const TableDirEntry*>
|
||||
(bsearch(&aTableTag, dir, uint16_t(header->numTables),
|
||||
sizeof(TableDirEntry), DirEntryCmp));
|
||||
if (dir) {
|
||||
return hb_blob_create(reinterpret_cast<const char*>(aFontData) +
|
||||
dir->offset, dir->length,
|
||||
HB_MEMORY_MODE_READONLY, nullptr, nullptr);
|
||||
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hb_blob_t *
|
||||
gfxFontEntry::GetFontTable(uint32_t aTag)
|
||||
{
|
||||
|
@ -508,14 +508,7 @@ protected:
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Return a blob that wraps a table found within a buffer of font data.
|
||||
// The blob does NOT own its data; caller guarantees that the buffer
|
||||
// will remain valid at least as long as the blob.
|
||||
// Returns null if the specified table is not found.
|
||||
// This method assumes aFontData is valid 'sfnt' data; before using this,
|
||||
// caller is responsible to do any sanitization/validation necessary.
|
||||
hb_blob_t* GetTableFromFontData(const void* aFontData, uint32_t aTableTag);
|
||||
|
||||
protected:
|
||||
// Shaper-specific face objects, shared by all instantiations of the same
|
||||
// physical font, regardless of size.
|
||||
// Usually, only one of these will actually be created for any given font
|
||||
|
@ -627,6 +627,14 @@ bool gfxDownloadedFcFontEntry::SetCairoFace(cairo_font_face_t *aFace)
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
DirEntryCmp(const void* aKey, const void* aItem)
|
||||
{
|
||||
int32_t tag = *static_cast<const int32_t*>(aKey);
|
||||
const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem);
|
||||
return tag - int32_t(entry->tag);
|
||||
}
|
||||
|
||||
hb_blob_t *
|
||||
gfxDownloadedFcFontEntry::GetFontTable(uint32_t aTableTag)
|
||||
{
|
||||
@ -634,7 +642,18 @@ gfxDownloadedFcFontEntry::GetFontTable(uint32_t aTableTag)
|
||||
// so we can just return a blob that "wraps" the appropriate chunk of it.
|
||||
// The blob should not attempt to free its data, as the entire sfnt data
|
||||
// will be freed when the font entry is deleted.
|
||||
return GetTableFromFontData(mFontData, aTableTag);
|
||||
const SFNTHeader* header = reinterpret_cast<const SFNTHeader*>(mFontData);
|
||||
const TableDirEntry* dir = reinterpret_cast<const TableDirEntry*>(header + 1);
|
||||
dir = static_cast<const TableDirEntry*>
|
||||
(bsearch(&aTableTag, dir, uint16_t(header->numTables),
|
||||
sizeof(TableDirEntry), DirEntryCmp));
|
||||
if (dir) {
|
||||
return hb_blob_create(reinterpret_cast<const char*>(mFontData) +
|
||||
dir->offset, dir->length,
|
||||
HB_MEMORY_MODE_READONLY, nullptr, nullptr);
|
||||
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -253,11 +253,6 @@ pref("gfx.color_management.enablev4", false);
|
||||
pref("gfx.downloadable_fonts.enabled", true);
|
||||
pref("gfx.downloadable_fonts.fallback_delay", 3000);
|
||||
|
||||
#ifdef ANDROID
|
||||
pref("gfx.bundled_fonts.enabled", true);
|
||||
pref("gfx.bundled_fonts.force-enabled", false);
|
||||
#endif
|
||||
|
||||
pref("gfx.filter.nearest.force-enabled", false);
|
||||
|
||||
// prefs controlling the font (name/cmap) loader that runs shortly after startup
|
||||
@ -3326,82 +3321,73 @@ pref("font.name-list.sans-serif.he", "Droid Sans Hebrew, Open Sans, Droid Sans")
|
||||
pref("font.name.serif.ja", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.ja", "Open Sans");
|
||||
pref("font.name.monospace.ja", "MotoyaLMaru");
|
||||
pref("font.name-list.serif.ja", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.ja", "Open Sans, Roboto, Droid Sans, MotoyaLMaru, MotoyaLCedar, Droid Sans Japanese");
|
||||
pref("font.name-list.monospace.ja", "MotoyaLMaru, MotoyaLCedar, Droid Sans Mono");
|
||||
|
||||
pref("font.name.serif.ko", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.ko", "Open Sans");
|
||||
pref("font.name.monospace.ko", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.ko", "Droid Serif, HYSerif");
|
||||
pref("font.name-list.serif.ko", "HYSerif");
|
||||
pref("font.name-list.sans-serif.ko", "SmartGothic, NanumGothic, DroidSansFallback, Droid Sans Fallback");
|
||||
|
||||
pref("font.name.serif.th", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.th", "Open Sans");
|
||||
pref("font.name.monospace.th", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.th", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.th", "Droid Sans Thai, Open Sans, Droid Sans");
|
||||
|
||||
pref("font.name.serif.tr", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.tr", "Open Sans");
|
||||
pref("font.name.monospace.tr", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.tr", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.tr", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.x-baltic", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.x-baltic", "Open Sans");
|
||||
pref("font.name.monospace.x-baltic", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.x-baltic", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.x-baltic", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.x-central-euro", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.x-central-euro", "Open Sans");
|
||||
pref("font.name.monospace.x-central-euro", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.x-central-euro", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.x-central-euro", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.x-cyrillic", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.x-cyrillic", "Open Sans");
|
||||
pref("font.name.monospace.x-cyrillic", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.x-cyrillic", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.x-cyrillic", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.x-unicode", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.x-unicode", "Open Sans");
|
||||
pref("font.name.monospace.x-unicode", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.x-unicode", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.x-unicode", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.x-user-def", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.x-user-def", "Open Sans");
|
||||
pref("font.name.monospace.x-user-def", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.x-user-def", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.x-user-def", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.x-western", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.x-western", "Open Sans");
|
||||
pref("font.name.monospace.x-western", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.x-western", "Droid Serif");
|
||||
pref("font.name-list.sans-serif.x-western", "Open Sans, Roboto, Droid Sans");
|
||||
|
||||
pref("font.name.serif.zh-CN", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.zh-CN", "Open Sans");
|
||||
pref("font.name.monospace.zh-CN", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.zh-CN", "Droid Serif, Droid Sans Fallback");
|
||||
pref("font.name-list.serif.zh-CN", "Droid Sans Fallback");
|
||||
pref("font.name-list.sans-serif.zh-CN", "Roboto, Droid Sans, Droid Sans Fallback");
|
||||
pref("font.name-list.monospace.zh-CN", "Droid Sans Fallback");
|
||||
|
||||
pref("font.name.serif.zh-HK", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.zh-HK", "Open Sans");
|
||||
pref("font.name.monospace.zh-HK", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.zh-HK", "Droid Serif, Droid Sans Fallback");
|
||||
pref("font.name-list.serif.zh-HK", "Droid Sans Fallback");
|
||||
pref("font.name-list.sans-serif.zh-HK", "Roboto, Droid Sans, Droid Sans Fallback");
|
||||
pref("font.name-list.monospace.zh-HK", "Droid Sans Fallback");
|
||||
|
||||
pref("font.name.serif.zh-TW", "Charis SIL Compact");
|
||||
pref("font.name.sans-serif.zh-TW", "Open Sans");
|
||||
pref("font.name.monospace.zh-TW", "Droid Sans Mono");
|
||||
pref("font.name-list.serif.zh-TW", "Droid Serif, Droid Sans Fallback");
|
||||
pref("font.name-list.serif.zh-TW", "Droid Sans Fallback");
|
||||
pref("font.name-list.sans-serif.zh-TW", "Roboto, Droid Sans, Droid Sans Fallback");
|
||||
pref("font.name-list.monospace.zh-TW", "Droid Sans Fallback");
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user