diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch')
-rw-r--r-- | gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch b/gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch new file mode 100644 index 0000000000..ad6e181274 --- /dev/null +++ b/gfx/skia/patches/archive/0004-Bug-777614-Re-apply-bug-719872-Fix-crash-on-Android-.patch @@ -0,0 +1,684 @@ +From 0d730a94e9f6676d5cde45f955fe025a4549817e Mon Sep 17 00:00:00 2001 +From: George Wright <gw@gwright.org.uk> +Date: Thu, 23 Aug 2012 16:45:38 -0400 +Subject: [PATCH 4/9] Bug 777614 - Re-apply bug 719872 - Fix crash on Android + by reverting to older FontHost r=nrc + +--- + gfx/skia/src/ports/SkFontHost_android_old.cpp | 664 ++++++++++++++++++++++++++ + 1 file changed, 664 insertions(+) + create mode 100644 gfx/skia/src/ports/SkFontHost_android_old.cpp + +diff --git a/gfx/skia/src/ports/SkFontHost_android_old.cpp b/gfx/skia/src/ports/SkFontHost_android_old.cpp +new file mode 100644 +index 0000000..b5c4f3c +--- /dev/null ++++ b/gfx/skia/src/ports/SkFontHost_android_old.cpp +@@ -0,0 +1,664 @@ ++ ++/* ++ * Copyright 2006 The Android Open Source Project ++ * ++ * Use of this source code is governed by a BSD-style license that can be ++ * found in the LICENSE file. ++ */ ++ ++ ++#include "SkFontHost.h" ++#include "SkDescriptor.h" ++#include "SkMMapStream.h" ++#include "SkPaint.h" ++#include "SkString.h" ++#include "SkStream.h" ++#include "SkThread.h" ++#include "SkTSearch.h" ++#include <stdio.h> ++ ++#define FONT_CACHE_MEMORY_BUDGET (768 * 1024) ++ ++#ifndef SK_FONT_FILE_PREFIX ++ #define SK_FONT_FILE_PREFIX "/fonts/" ++#endif ++ ++bool find_name_and_attributes(SkStream* stream, SkString* name, SkTypeface::Style* style, ++ bool* isFixedWidth); ++ ++static void GetFullPathForSysFonts(SkString* full, const char name[]) { ++ full->set(getenv("ANDROID_ROOT")); ++ full->append(SK_FONT_FILE_PREFIX); ++ full->append(name); ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++struct FamilyRec; ++ ++/* This guy holds a mapping of a name -> family, used for looking up fonts. ++ Since it is stored in a stretchy array that doesn't preserve object ++ semantics, we don't use constructor/destructors, but just have explicit ++ helpers to manage our internal bookkeeping. ++*/ ++struct NameFamilyPair { ++ const char* fName; // we own this ++ FamilyRec* fFamily; // we don't own this, we just reference it ++ ++ void construct(const char name[], FamilyRec* family) { ++ fName = strdup(name); ++ fFamily = family; // we don't own this, so just record the referene ++ } ++ ++ void destruct() { ++ free((char*)fName); ++ // we don't own family, so just ignore our reference ++ } ++}; ++ ++// we use atomic_inc to grow this for each typeface we create ++static int32_t gUniqueFontID; ++ ++// this is the mutex that protects these globals ++static SkMutex gFamilyMutex; ++static FamilyRec* gFamilyHead; ++static SkTDArray<NameFamilyPair> gNameList; ++ ++struct FamilyRec { ++ FamilyRec* fNext; ++ SkTypeface* fFaces[4]; ++ ++ FamilyRec() ++ { ++ fNext = gFamilyHead; ++ memset(fFaces, 0, sizeof(fFaces)); ++ gFamilyHead = this; ++ } ++}; ++ ++static SkTypeface* find_best_face(const FamilyRec* family, ++ SkTypeface::Style style) { ++ SkTypeface* const* faces = family->fFaces; ++ ++ if (faces[style] != NULL) { // exact match ++ return faces[style]; ++ } ++ // look for a matching bold ++ style = (SkTypeface::Style)(style ^ SkTypeface::kItalic); ++ if (faces[style] != NULL) { ++ return faces[style]; ++ } ++ // look for the plain ++ if (faces[SkTypeface::kNormal] != NULL) { ++ return faces[SkTypeface::kNormal]; ++ } ++ // look for anything ++ for (int i = 0; i < 4; i++) { ++ if (faces[i] != NULL) { ++ return faces[i]; ++ } ++ } ++ // should never get here, since the faces list should not be empty ++ SkASSERT(!"faces list is empty"); ++ return NULL; ++} ++ ++static FamilyRec* find_family(const SkTypeface* member) { ++ FamilyRec* curr = gFamilyHead; ++ while (curr != NULL) { ++ for (int i = 0; i < 4; i++) { ++ if (curr->fFaces[i] == member) { ++ return curr; ++ } ++ } ++ curr = curr->fNext; ++ } ++ return NULL; ++} ++ ++/* Returns the matching typeface, or NULL. If a typeface is found, its refcnt ++ is not modified. ++ */ ++static SkTypeface* find_from_uniqueID(uint32_t uniqueID) { ++ FamilyRec* curr = gFamilyHead; ++ while (curr != NULL) { ++ for (int i = 0; i < 4; i++) { ++ SkTypeface* face = curr->fFaces[i]; ++ if (face != NULL && face->uniqueID() == uniqueID) { ++ return face; ++ } ++ } ++ curr = curr->fNext; ++ } ++ return NULL; ++} ++ ++/* Remove reference to this face from its family. If the resulting family ++ is empty (has no faces), return that family, otherwise return NULL ++*/ ++static FamilyRec* remove_from_family(const SkTypeface* face) { ++ FamilyRec* family = find_family(face); ++ SkASSERT(family->fFaces[face->style()] == face); ++ family->fFaces[face->style()] = NULL; ++ ++ for (int i = 0; i < 4; i++) { ++ if (family->fFaces[i] != NULL) { // family is non-empty ++ return NULL; ++ } ++ } ++ return family; // return the empty family ++} ++ ++// maybe we should make FamilyRec be doubly-linked ++static void detach_and_delete_family(FamilyRec* family) { ++ FamilyRec* curr = gFamilyHead; ++ FamilyRec* prev = NULL; ++ ++ while (curr != NULL) { ++ FamilyRec* next = curr->fNext; ++ if (curr == family) { ++ if (prev == NULL) { ++ gFamilyHead = next; ++ } else { ++ prev->fNext = next; ++ } ++ SkDELETE(family); ++ return; ++ } ++ prev = curr; ++ curr = next; ++ } ++ SkASSERT(!"Yikes, couldn't find family in our list to remove/delete"); ++} ++ ++static SkTypeface* find_typeface(const char name[], SkTypeface::Style style) { ++ NameFamilyPair* list = gNameList.begin(); ++ int count = gNameList.count(); ++ ++ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); ++ ++ if (index >= 0) { ++ return find_best_face(list[index].fFamily, style); ++ } ++ return NULL; ++} ++ ++static SkTypeface* find_typeface(const SkTypeface* familyMember, ++ SkTypeface::Style style) { ++ const FamilyRec* family = find_family(familyMember); ++ return family ? find_best_face(family, style) : NULL; ++} ++ ++static void add_name(const char name[], FamilyRec* family) { ++ SkAutoAsciiToLC tolc(name); ++ name = tolc.lc(); ++ ++ NameFamilyPair* list = gNameList.begin(); ++ int count = gNameList.count(); ++ ++ int index = SkStrLCSearch(&list[0].fName, count, name, sizeof(list[0])); ++ ++ if (index < 0) { ++ list = gNameList.insert(~index); ++ list->construct(name, family); ++ } ++} ++ ++static void remove_from_names(FamilyRec* emptyFamily) ++{ ++#ifdef SK_DEBUG ++ for (int i = 0; i < 4; i++) { ++ SkASSERT(emptyFamily->fFaces[i] == NULL); ++ } ++#endif ++ ++ SkTDArray<NameFamilyPair>& list = gNameList; ++ ++ // must go backwards when removing ++ for (int i = list.count() - 1; i >= 0; --i) { ++ NameFamilyPair* pair = &list[i]; ++ if (pair->fFamily == emptyFamily) { ++ pair->destruct(); ++ list.remove(i); ++ } ++ } ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++class FamilyTypeface : public SkTypeface { ++public: ++ FamilyTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ bool isFixedWidth) ++ : SkTypeface(style, sk_atomic_inc(&gUniqueFontID) + 1, isFixedWidth) { ++ fIsSysFont = sysFont; ++ ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyRec* rec = NULL; ++ if (familyMember) { ++ rec = find_family(familyMember); ++ SkASSERT(rec); ++ } else { ++ rec = SkNEW(FamilyRec); ++ } ++ rec->fFaces[style] = this; ++ } ++ ++ virtual ~FamilyTypeface() { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ // remove us from our family. If the family is now empty, we return ++ // that and then remove that family from the name list ++ FamilyRec* family = remove_from_family(this); ++ if (NULL != family) { ++ remove_from_names(family); ++ detach_and_delete_family(family); ++ } ++ } ++ ++ bool isSysFont() const { return fIsSysFont; } ++ ++ virtual SkStream* openStream() = 0; ++ virtual const char* getUniqueString() const = 0; ++ virtual const char* getFilePath() const = 0; ++ ++private: ++ bool fIsSysFont; ++ ++ typedef SkTypeface INHERITED; ++}; ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++class StreamTypeface : public FamilyTypeface { ++public: ++ StreamTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ SkStream* stream, bool isFixedWidth) ++ : INHERITED(style, sysFont, familyMember, isFixedWidth) { ++ SkASSERT(stream); ++ stream->ref(); ++ fStream = stream; ++ } ++ virtual ~StreamTypeface() { ++ fStream->unref(); ++ } ++ ++ // overrides ++ virtual SkStream* openStream() { ++ // we just ref our existing stream, since the caller will call unref() ++ // when they are through ++ fStream->ref(); ++ // must rewind each time, since the caller assumes a "new" stream ++ fStream->rewind(); ++ return fStream; ++ } ++ virtual const char* getUniqueString() const { return NULL; } ++ virtual const char* getFilePath() const { return NULL; } ++ ++private: ++ SkStream* fStream; ++ ++ typedef FamilyTypeface INHERITED; ++}; ++ ++class FileTypeface : public FamilyTypeface { ++public: ++ FileTypeface(Style style, bool sysFont, SkTypeface* familyMember, ++ const char path[], bool isFixedWidth) ++ : INHERITED(style, sysFont, familyMember, isFixedWidth) { ++ SkString fullpath; ++ ++ if (sysFont) { ++ GetFullPathForSysFonts(&fullpath, path); ++ path = fullpath.c_str(); ++ } ++ fPath.set(path); ++ } ++ ++ // overrides ++ virtual SkStream* openStream() { ++ SkStream* stream = SkNEW_ARGS(SkMMAPStream, (fPath.c_str())); ++ ++ // check for failure ++ if (stream->getLength() <= 0) { ++ SkDELETE(stream); ++ // maybe MMAP isn't supported. try FILE ++ stream = SkNEW_ARGS(SkFILEStream, (fPath.c_str())); ++ if (stream->getLength() <= 0) { ++ SkDELETE(stream); ++ stream = NULL; ++ } ++ } ++ return stream; ++ } ++ virtual const char* getUniqueString() const { ++ const char* str = strrchr(fPath.c_str(), '/'); ++ if (str) { ++ str += 1; // skip the '/' ++ } ++ return str; ++ } ++ virtual const char* getFilePath() const { ++ return fPath.c_str(); ++ } ++ ++private: ++ SkString fPath; ++ ++ typedef FamilyTypeface INHERITED; ++}; ++ ++/////////////////////////////////////////////////////////////////////////////// ++/////////////////////////////////////////////////////////////////////////////// ++ ++static bool get_name_and_style(const char path[], SkString* name, ++ SkTypeface::Style* style, ++ bool* isFixedWidth, bool isExpected) { ++ SkString fullpath; ++ GetFullPathForSysFonts(&fullpath, path); ++ ++ SkMMAPStream stream(fullpath.c_str()); ++ if (stream.getLength() > 0) { ++ find_name_and_attributes(&stream, name, style, isFixedWidth); ++ return true; ++ } ++ else { ++ SkFILEStream stream(fullpath.c_str()); ++ if (stream.getLength() > 0) { ++ find_name_and_attributes(&stream, name, style, isFixedWidth); ++ return true; ++ } ++ } ++ ++ if (isExpected) { ++ SkDebugf("---- failed to open <%s> as a font\n", fullpath.c_str()); ++ } ++ return false; ++} ++ ++// used to record our notion of the pre-existing fonts ++struct FontInitRec { ++ const char* fFileName; ++ const char* const* fNames; // null-terminated list ++}; ++ ++static const char* gSansNames[] = { ++ "sans-serif", "arial", "helvetica", "tahoma", "verdana", NULL ++}; ++ ++static const char* gSerifNames[] = { ++ "serif", "times", "times new roman", "palatino", "georgia", "baskerville", ++ "goudy", "fantasy", "cursive", "ITC Stone Serif", NULL ++}; ++ ++static const char* gMonoNames[] = { ++ "monospace", "courier", "courier new", "monaco", NULL ++}; ++ ++// deliberately empty, but we use the address to identify fallback fonts ++static const char* gFBNames[] = { NULL }; ++ ++/* Fonts must be grouped by family, with the first font in a family having the ++ list of names (even if that list is empty), and the following members having ++ null for the list. The names list must be NULL-terminated ++*/ ++static const FontInitRec gSystemFonts[] = { ++ { "DroidSans.ttf", gSansNames }, ++ { "DroidSans-Bold.ttf", NULL }, ++ { "DroidSerif-Regular.ttf", gSerifNames }, ++ { "DroidSerif-Bold.ttf", NULL }, ++ { "DroidSerif-Italic.ttf", NULL }, ++ { "DroidSerif-BoldItalic.ttf", NULL }, ++ { "DroidSansMono.ttf", gMonoNames }, ++ /* These are optional, and can be ignored if not found in the file system. ++ These are appended to gFallbackFonts[] as they are seen, so we list ++ them in the order we want them to be accessed by NextLogicalFont(). ++ */ ++ { "DroidSansArabic.ttf", gFBNames }, ++ { "DroidSansHebrew.ttf", gFBNames }, ++ { "DroidSansThai.ttf", gFBNames }, ++ { "MTLmr3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "MTLc3m.ttf", gFBNames }, // Motoya Japanese Font ++ { "DroidSansJapanese.ttf", gFBNames }, ++ { "DroidSansFallback.ttf", gFBNames } ++}; ++ ++#define DEFAULT_NAMES gSansNames ++ ++// these globals are assigned (once) by load_system_fonts() ++static FamilyRec* gDefaultFamily; ++static SkTypeface* gDefaultNormal; ++ ++/* This is sized conservatively, assuming that it will never be a size issue. ++ It will be initialized in load_system_fonts(), and will be filled with the ++ fontIDs that can be used for fallback consideration, in sorted order (sorted ++ meaning element[0] should be used first, then element[1], etc. When we hit ++ a fontID==0 in the array, the list is done, hence our allocation size is ++ +1 the total number of possible system fonts. Also see NextLogicalFont(). ++ */ ++static uint32_t gFallbackFonts[SK_ARRAY_COUNT(gSystemFonts)+1]; ++ ++/* Called once (ensured by the sentinel check at the beginning of our body). ++ Initializes all the globals, and register the system fonts. ++ */ ++static void load_system_fonts() { ++ // check if we've already be called ++ if (NULL != gDefaultNormal) { ++ return; ++ } ++ ++ const FontInitRec* rec = gSystemFonts; ++ SkTypeface* firstInFamily = NULL; ++ int fallbackCount = 0; ++ ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ // if we're the first in a new family, clear firstInFamily ++ if (rec[i].fNames != NULL) { ++ firstInFamily = NULL; ++ } ++ ++ bool isFixedWidth; ++ SkString name; ++ SkTypeface::Style style; ++ ++ // we expect all the fonts, except the "fallback" fonts ++ bool isExpected = (rec[i].fNames != gFBNames); ++ if (!get_name_and_style(rec[i].fFileName, &name, &style, ++ &isFixedWidth, isExpected)) { ++ continue; ++ } ++ ++ SkTypeface* tf = SkNEW_ARGS(FileTypeface, ++ (style, ++ true, // system-font (cannot delete) ++ firstInFamily, // what family to join ++ rec[i].fFileName, ++ isFixedWidth) // filename ++ ); ++ ++ if (rec[i].fNames != NULL) { ++ // see if this is one of our fallback fonts ++ if (rec[i].fNames == gFBNames) { ++ // SkDebugf("---- adding %s as fallback[%d] fontID %d\n", ++ // rec[i].fFileName, fallbackCount, tf->uniqueID()); ++ gFallbackFonts[fallbackCount++] = tf->uniqueID(); ++ } ++ ++ firstInFamily = tf; ++ FamilyRec* family = find_family(tf); ++ const char* const* names = rec[i].fNames; ++ ++ // record the default family if this is it ++ if (names == DEFAULT_NAMES) { ++ gDefaultFamily = family; ++ } ++ // add the names to map to this family ++ while (*names) { ++ add_name(*names, family); ++ names += 1; ++ } ++ } ++ } ++ ++ // do this after all fonts are loaded. This is our default font, and it ++ // acts as a sentinel so we only execute load_system_fonts() once ++ gDefaultNormal = find_best_face(gDefaultFamily, SkTypeface::kNormal); ++ // now terminate our fallback list with the sentinel value ++ gFallbackFonts[fallbackCount] = 0; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) { ++ const char* name = ((FamilyTypeface*)face)->getUniqueString(); ++ ++ stream->write8((uint8_t)face->style()); ++ ++ if (NULL == name || 0 == *name) { ++ stream->writePackedUInt(0); ++// SkDebugf("--- fonthost serialize null\n"); ++ } else { ++ uint32_t len = strlen(name); ++ stream->writePackedUInt(len); ++ stream->write(name, len); ++// SkDebugf("--- fonthost serialize <%s> %d\n", name, face->style()); ++ } ++} ++ ++SkTypeface* SkFontHost::Deserialize(SkStream* stream) { ++ load_system_fonts(); ++ ++ int style = stream->readU8(); ++ ++ int len = stream->readPackedUInt(); ++ if (len > 0) { ++ SkString str; ++ str.resize(len); ++ stream->read(str.writable_str(), len); ++ ++ const FontInitRec* rec = gSystemFonts; ++ for (size_t i = 0; i < SK_ARRAY_COUNT(gSystemFonts); i++) { ++ if (strcmp(rec[i].fFileName, str.c_str()) == 0) { ++ // backup until we hit the fNames ++ for (int j = i; j >= 0; --j) { ++ if (rec[j].fNames != NULL) { ++ return SkFontHost::CreateTypeface(NULL, ++ rec[j].fNames[0], (SkTypeface::Style)style); ++ } ++ } ++ } ++ } ++ } ++ return NULL; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, ++ const char familyName[], ++ SkTypeface::Style style) { ++ load_system_fonts(); ++ ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ // clip to legal style bits ++ style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic); ++ ++ SkTypeface* tf = NULL; ++ ++ if (NULL != familyFace) { ++ tf = find_typeface(familyFace, style); ++ } else if (NULL != familyName) { ++// SkDebugf("======= familyName <%s>\n", familyName); ++ tf = find_typeface(familyName, style); ++ } ++ ++ if (NULL == tf) { ++ tf = find_best_face(gDefaultFamily, style); ++ } ++ ++ // we ref(), since the symantic is to return a new instance ++ tf->ref(); ++ return tf; ++} ++ ++SkStream* SkFontHost::OpenStream(uint32_t fontID) { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyTypeface* tf = (FamilyTypeface*)find_from_uniqueID(fontID); ++ SkStream* stream = tf ? tf->openStream() : NULL; ++ ++ if (stream && stream->getLength() == 0) { ++ stream->unref(); ++ stream = NULL; ++ } ++ return stream; ++} ++ ++size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, ++ int32_t* index) { ++ SkAutoMutexAcquire ac(gFamilyMutex); ++ ++ FamilyTypeface* tf = (FamilyTypeface*)find_from_uniqueID(fontID); ++ const char* src = tf ? tf->getFilePath() : NULL; ++ ++ if (src) { ++ size_t size = strlen(src); ++ if (path) { ++ memcpy(path, src, SkMin32(size, length)); ++ } ++ if (index) { ++ *index = 0; // we don't have collections (yet) ++ } ++ return size; ++ } else { ++ return 0; ++ } ++} ++ ++SkFontID SkFontHost::NextLogicalFont(SkFontID currFontID, SkFontID origFontID) { ++ load_system_fonts(); ++ ++ /* First see if fontID is already one of our fallbacks. If so, return ++ its successor. If fontID is not in our list, then return the first one ++ in our list. Note: list is zero-terminated, and returning zero means ++ we have no more fonts to use for fallbacks. ++ */ ++ const uint32_t* list = gFallbackFonts; ++ for (int i = 0; list[i] != 0; i++) { ++ if (list[i] == currFontID) { ++ return list[i+1]; ++ } ++ } ++ return list[0]; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// ++ ++SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) { ++ if (NULL == stream || stream->getLength() <= 0) { ++ return NULL; ++ } ++ ++ bool isFixedWidth; ++ SkString name; ++ SkTypeface::Style style; ++ find_name_and_attributes(stream, &name, &style, &isFixedWidth); ++ ++ if (!name.isEmpty()) { ++ return SkNEW_ARGS(StreamTypeface, (style, false, NULL, stream, isFixedWidth)); ++ } else { ++ return NULL; ++ } ++} ++ ++SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) { ++ SkStream* stream = SkNEW_ARGS(SkMMAPStream, (path)); ++ SkTypeface* face = SkFontHost::CreateTypefaceFromStream(stream); ++ // since we created the stream, we let go of our ref() here ++ stream->unref(); ++ return face; ++} ++ ++/////////////////////////////////////////////////////////////////////////////// +-- +1.7.11.4 + |