зеркало из https://github.com/mozilla/gecko-dev.git
473 строки
15 KiB
Diff
473 строки
15 KiB
Diff
From: George Wright <george@mozilla.com>
|
|
Date: Wed, 1 Aug 2012 16:43:15 -0400
|
|
Subject: Bug 736276 - Add a new SkFontHost that takes a cairo_scaled_font_t r=karl
|
|
|
|
|
|
diff --git a/gfx/skia/Makefile.in b/gfx/skia/Makefile.in
|
|
index 5ebbd2e..7c8cdbf 100644
|
|
--- a/gfx/skia/Makefile.in
|
|
+++ b/gfx/skia/Makefile.in
|
|
@@ -60,15 +60,15 @@ VPATH += \
|
|
$(NULL)
|
|
|
|
ifeq (android,$(MOZ_WIDGET_TOOLKIT))
|
|
-OS_CXXFLAGS += $(CAIRO_FT_CFLAGS)
|
|
+OS_CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(CAIRO_FT_CFLAGS)
|
|
endif
|
|
|
|
ifeq (gtk2,$(MOZ_WIDGET_TOOLKIT))
|
|
-OS_CXXFLAGS += $(MOZ_PANGO_CFLAGS)
|
|
+OS_CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(MOZ_PANGO_CFLAGS) $(CAIRO_FT_CFLAGS)
|
|
endif
|
|
|
|
ifeq (qt,$(MOZ_WIDGET_TOOLKIT))
|
|
-OS_CXXFLAGS += $(MOZ_PANGO_CFLAGS)
|
|
+OS_CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(MOZ_PANGO_CFLAGS) $(CAIRO_FT_CFLAGS)
|
|
ifeq (Linux,$(OS_TARGET))
|
|
DEFINES += -DSK_USE_POSIX_THREADS=1
|
|
endif
|
|
diff --git a/gfx/skia/include/ports/SkTypeface_cairo.h b/gfx/skia/include/ports/SkTypeface_cairo.h
|
|
new file mode 100644
|
|
index 0000000..7e44f04
|
|
--- /dev/null
|
|
+++ b/gfx/skia/include/ports/SkTypeface_cairo.h
|
|
@@ -0,0 +1,11 @@
|
|
+#ifndef SkTypeface_cairo_DEFINED
|
|
+#define SkTypeface_cairo_DEFINED
|
|
+
|
|
+#include <cairo.h>
|
|
+
|
|
+#include "SkTypeface.h"
|
|
+
|
|
+SK_API extern SkTypeface* SkCreateTypefaceFromCairoFont(cairo_font_face_t* fontFace, SkTypeface::Style style, bool isFixedWidth);
|
|
+
|
|
+#endif
|
|
+
|
|
diff --git a/gfx/skia/moz.build b/gfx/skia/moz.build
|
|
index 9ceba59..66efd52 100644
|
|
--- a/gfx/skia/moz.build
|
|
+++ b/gfx/skia/moz.build
|
|
@@ -171,10 +171,12 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|
'SkTime_win.cpp',
|
|
]
|
|
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
|
|
+ EXPORTS.skia += [
|
|
+ 'include/ports/SkTypeface_cairo.h',
|
|
+ ]
|
|
CPP_SOURCES += [
|
|
- 'SkFontHost_FreeType.cpp',
|
|
+ 'SkFontHost_cairo.cpp',
|
|
'SkFontHost_FreeType_common.cpp',
|
|
- 'SkFontHost_linux.cpp',
|
|
'SkThread_pthread.cpp',
|
|
'SkThreadUtils_pthread.cpp',
|
|
'SkThreadUtils_pthread_linux.cpp',
|
|
@@ -183,14 +185,15 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
|
|
]
|
|
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'qt':
|
|
CPP_SOURCES += [
|
|
- 'SkFontHost_FreeType.cpp',
|
|
+ 'SkFontHost_cairo.cpp',
|
|
'SkFontHost_FreeType_common.cpp',
|
|
'SkOSFile.cpp',
|
|
]
|
|
if CONFIG['OS_TARGET'] == 'Linux':
|
|
+ EXPORTS.skia += [
|
|
+ 'include/ports/SkTypeface_cairo.h',
|
|
+ ]
|
|
CPP_SOURCES += [
|
|
- 'SkFontHost_linux.cpp',
|
|
- 'SkFontHost_tables.cpp',
|
|
'SkThread_pthread.cpp',
|
|
'SkThreadUtils_pthread.cpp',
|
|
'SkThreadUtils_pthread_linux.cpp',
|
|
@@ -204,11 +207,13 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
|
|
# Separate 'if' from above, since the else below applies to all != 'android'
|
|
# toolkits.
|
|
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
|
|
+ EXPORTS.skia += [
|
|
+ 'include/ports/SkTypeface_cairo.h',
|
|
+ ]
|
|
CPP_SOURCES += [
|
|
'ashmem.cpp',
|
|
'SkDebug_android.cpp',
|
|
- 'SkFontHost_android_old.cpp',
|
|
- 'SkFontHost_FreeType.cpp',
|
|
+ 'SkFontHost_cairo.cpp',
|
|
'SkFontHost_FreeType_common.cpp',
|
|
'SkImageRef_ashmem.cpp',
|
|
'SkTime_Unix.cpp',
|
|
diff --git a/gfx/skia/src/ports/SkFontHost_cairo.cpp b/gfx/skia/src/ports/SkFontHost_cairo.cpp
|
|
new file mode 100644
|
|
index 0000000..bb5b778
|
|
--- /dev/null
|
|
+++ b/gfx/skia/src/ports/SkFontHost_cairo.cpp
|
|
@@ -0,0 +1,364 @@
|
|
+
|
|
+/*
|
|
+ * Copyright 2012 Mozilla Foundation
|
|
+ *
|
|
+ * Use of this source code is governed by a BSD-style license that can be
|
|
+ * found in the LICENSE file.
|
|
+ */
|
|
+
|
|
+#include "cairo.h"
|
|
+#include "cairo-ft.h"
|
|
+
|
|
+#include "SkFontHost_FreeType_common.h"
|
|
+
|
|
+#include "SkAdvancedTypefaceMetrics.h"
|
|
+#include "SkFontHost.h"
|
|
+#include "SkPath.h"
|
|
+#include "SkScalerContext.h"
|
|
+#include "SkTypefaceCache.h"
|
|
+
|
|
+#include <ft2build.h>
|
|
+#include FT_FREETYPE_H
|
|
+
|
|
+static cairo_user_data_key_t kSkTypefaceKey;
|
|
+
|
|
+class SkScalerContext_CairoFT : public SkScalerContext_FreeType_Base {
|
|
+public:
|
|
+ SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc);
|
|
+ virtual ~SkScalerContext_CairoFT();
|
|
+
|
|
+protected:
|
|
+ virtual unsigned generateGlyphCount() SK_OVERRIDE;
|
|
+ virtual uint16_t generateCharToGlyph(SkUnichar uniChar) SK_OVERRIDE;
|
|
+ virtual void generateAdvance(SkGlyph* glyph) SK_OVERRIDE;
|
|
+ virtual void generateMetrics(SkGlyph* glyph) SK_OVERRIDE;
|
|
+ virtual void generateImage(const SkGlyph& glyph) SK_OVERRIDE;
|
|
+ virtual void generatePath(const SkGlyph& glyph, SkPath* path) SK_OVERRIDE;
|
|
+ virtual void generateFontMetrics(SkPaint::FontMetrics* mx,
|
|
+ SkPaint::FontMetrics* my) SK_OVERRIDE;
|
|
+ virtual SkUnichar generateGlyphToChar(uint16_t glyph) SK_OVERRIDE;
|
|
+private:
|
|
+ cairo_scaled_font_t* fScaledFont;
|
|
+ uint32_t fLoadGlyphFlags;
|
|
+};
|
|
+
|
|
+class CairoLockedFTFace {
|
|
+public:
|
|
+ CairoLockedFTFace(cairo_scaled_font_t* scaledFont)
|
|
+ : fScaledFont(scaledFont)
|
|
+ , fFace(cairo_ft_scaled_font_lock_face(scaledFont))
|
|
+ {}
|
|
+
|
|
+ ~CairoLockedFTFace()
|
|
+ {
|
|
+ cairo_ft_scaled_font_unlock_face(fScaledFont);
|
|
+ }
|
|
+
|
|
+ FT_Face getFace()
|
|
+ {
|
|
+ return fFace;
|
|
+ }
|
|
+
|
|
+private:
|
|
+ cairo_scaled_font_t* fScaledFont;
|
|
+ FT_Face fFace;
|
|
+};
|
|
+
|
|
+class SkCairoFTTypeface : public SkTypeface {
|
|
+public:
|
|
+ static SkTypeface* CreateTypeface(cairo_font_face_t* fontFace, SkTypeface::Style style, bool isFixedWidth) {
|
|
+ SkASSERT(fontFace != NULL);
|
|
+ SkASSERT(cairo_font_face_get_type(fontFace) == CAIRO_FONT_TYPE_FT);
|
|
+
|
|
+ SkFontID newId = SkTypefaceCache::NewFontID();
|
|
+
|
|
+ return SkNEW_ARGS(SkCairoFTTypeface, (fontFace, style, newId, isFixedWidth));
|
|
+ }
|
|
+
|
|
+ cairo_font_face_t* getFontFace() {
|
|
+ return fFontFace;
|
|
+ }
|
|
+
|
|
+ virtual SkStream* onOpenStream(int*) const SK_OVERRIDE { return NULL; }
|
|
+
|
|
+ virtual SkAdvancedTypefaceMetrics*
|
|
+ onGetAdvancedTypefaceMetrics(SkAdvancedTypefaceMetrics::PerGlyphInfo,
|
|
+ const uint32_t*, uint32_t) const SK_OVERRIDE
|
|
+ {
|
|
+ SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetAdvancedTypefaceMetrics unimplemented\n"));
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ virtual SkScalerContext* onCreateScalerContext(const SkDescriptor* desc) const SK_OVERRIDE
|
|
+ {
|
|
+ return SkNEW_ARGS(SkScalerContext_CairoFT, (const_cast<SkCairoFTTypeface*>(this), desc));
|
|
+ }
|
|
+
|
|
+ virtual void onFilterRec(SkScalerContextRec*) const SK_OVERRIDE
|
|
+ {
|
|
+ SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onFilterRec unimplemented\n"));
|
|
+ }
|
|
+
|
|
+ virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const SK_OVERRIDE
|
|
+ {
|
|
+ SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetFontDescriptor unimplemented\n"));
|
|
+ }
|
|
+
|
|
+
|
|
+private:
|
|
+
|
|
+ SkCairoFTTypeface(cairo_font_face_t* fontFace, SkTypeface::Style style, SkFontID id, bool isFixedWidth)
|
|
+ : SkTypeface(style, id, isFixedWidth)
|
|
+ , fFontFace(fontFace)
|
|
+ {
|
|
+ cairo_font_face_set_user_data(fFontFace, &kSkTypefaceKey, this, NULL);
|
|
+ cairo_font_face_reference(fFontFace);
|
|
+ }
|
|
+
|
|
+ ~SkCairoFTTypeface()
|
|
+ {
|
|
+ cairo_font_face_set_user_data(fFontFace, &kSkTypefaceKey, NULL, NULL);
|
|
+ cairo_font_face_destroy(fFontFace);
|
|
+ }
|
|
+
|
|
+ cairo_font_face_t* fFontFace;
|
|
+};
|
|
+
|
|
+SkTypeface* SkCreateTypefaceFromCairoFont(cairo_font_face_t* fontFace, SkTypeface::Style style, bool isFixedWidth)
|
|
+{
|
|
+ SkTypeface* typeface = reinterpret_cast<SkTypeface*>(cairo_font_face_get_user_data(fontFace, &kSkTypefaceKey));
|
|
+
|
|
+ if (typeface) {
|
|
+ typeface->ref();
|
|
+ } else {
|
|
+ typeface = SkCairoFTTypeface::CreateTypeface(fontFace, style, isFixedWidth);
|
|
+ SkTypefaceCache::Add(typeface, style);
|
|
+ }
|
|
+
|
|
+ return typeface;
|
|
+}
|
|
+
|
|
+SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
|
|
+ const char famillyName[],
|
|
+ SkTypeface::Style style)
|
|
+{
|
|
+ SkDEBUGFAIL("SkFontHost::FindTypeface unimplemented");
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream*)
|
|
+{
|
|
+ SkDEBUGFAIL("SkFontHost::CreateTypeface unimplemented");
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+SkTypeface* SkFontHost::CreateTypefaceFromFile(char const*)
|
|
+{
|
|
+ SkDEBUGFAIL("SkFontHost::CreateTypefaceFromFile unimplemented");
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
+
|
|
+static bool isLCD(const SkScalerContext::Rec& rec) {
|
|
+ switch (rec.fMaskFormat) {
|
|
+ case SkMask::kLCD16_Format:
|
|
+ case SkMask::kLCD32_Format:
|
|
+ return true;
|
|
+ default:
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
+SkScalerContext_CairoFT::SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc)
|
|
+ : SkScalerContext_FreeType_Base(typeface, desc)
|
|
+{
|
|
+ SkMatrix matrix;
|
|
+ fRec.getSingleMatrix(&matrix);
|
|
+
|
|
+ cairo_font_face_t* fontFace = static_cast<SkCairoFTTypeface*>(typeface)->getFontFace();
|
|
+
|
|
+ cairo_matrix_t fontMatrix, ctMatrix;
|
|
+ cairo_matrix_init(&fontMatrix, matrix.getScaleX(), matrix.getSkewY(), matrix.getSkewX(), matrix.getScaleY(), 0.0, 0.0);
|
|
+ cairo_matrix_init_scale(&ctMatrix, 1.0, 1.0);
|
|
+
|
|
+ // We need to ensure that the font options match for hinting, as generateMetrics()
|
|
+ // uses the fScaledFont which uses these font options
|
|
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
|
|
+
|
|
+ FT_Int32 loadFlags = FT_LOAD_DEFAULT;
|
|
+
|
|
+ if (SkMask::kBW_Format == fRec.fMaskFormat) {
|
|
+ // See http://code.google.com/p/chromium/issues/detail?id=43252#c24
|
|
+ loadFlags = FT_LOAD_TARGET_MONO;
|
|
+ if (fRec.getHinting() == SkPaint::kNo_Hinting) {
|
|
+ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_NONE);
|
|
+ loadFlags = FT_LOAD_NO_HINTING;
|
|
+ }
|
|
+ } else {
|
|
+ switch (fRec.getHinting()) {
|
|
+ case SkPaint::kNo_Hinting:
|
|
+ loadFlags = FT_LOAD_NO_HINTING;
|
|
+ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_NONE);
|
|
+ break;
|
|
+ case SkPaint::kSlight_Hinting:
|
|
+ loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT
|
|
+ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_SLIGHT);
|
|
+ break;
|
|
+ case SkPaint::kNormal_Hinting:
|
|
+ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_MEDIUM);
|
|
+ if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) {
|
|
+ loadFlags = FT_LOAD_FORCE_AUTOHINT;
|
|
+ }
|
|
+ break;
|
|
+ case SkPaint::kFull_Hinting:
|
|
+ cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_FULL);
|
|
+ if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) {
|
|
+ loadFlags = FT_LOAD_FORCE_AUTOHINT;
|
|
+ }
|
|
+ if (isLCD(fRec)) {
|
|
+ if (SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag)) {
|
|
+ loadFlags = FT_LOAD_TARGET_LCD_V;
|
|
+ } else {
|
|
+ loadFlags = FT_LOAD_TARGET_LCD;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting());
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fScaledFont = cairo_scaled_font_create(fontFace, &fontMatrix, &ctMatrix, fontOptions);
|
|
+
|
|
+ if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) {
|
|
+ loadFlags |= FT_LOAD_NO_BITMAP;
|
|
+ }
|
|
+
|
|
+ // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct
|
|
+ // advances, as fontconfig and cairo do.
|
|
+ // See http://code.google.com/p/skia/issues/detail?id=222.
|
|
+ loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
|
|
+
|
|
+ fLoadGlyphFlags = loadFlags;
|
|
+}
|
|
+
|
|
+SkScalerContext_CairoFT::~SkScalerContext_CairoFT()
|
|
+{
|
|
+ cairo_scaled_font_destroy(fScaledFont);
|
|
+}
|
|
+
|
|
+unsigned SkScalerContext_CairoFT::generateGlyphCount()
|
|
+{
|
|
+ CairoLockedFTFace faceLock(fScaledFont);
|
|
+ return faceLock.getFace()->num_glyphs;
|
|
+}
|
|
+
|
|
+uint16_t SkScalerContext_CairoFT::generateCharToGlyph(SkUnichar uniChar)
|
|
+{
|
|
+ CairoLockedFTFace faceLock(fScaledFont);
|
|
+ return SkToU16(FT_Get_Char_Index(faceLock.getFace(), uniChar));
|
|
+}
|
|
+
|
|
+void SkScalerContext_CairoFT::generateAdvance(SkGlyph* glyph)
|
|
+{
|
|
+ generateMetrics(glyph);
|
|
+}
|
|
+
|
|
+void SkScalerContext_CairoFT::generateMetrics(SkGlyph* glyph)
|
|
+{
|
|
+ SkASSERT(fScaledFont != NULL);
|
|
+ cairo_text_extents_t extents;
|
|
+ cairo_glyph_t cairoGlyph = { glyph->getGlyphID(fBaseGlyphCount), 0.0, 0.0 };
|
|
+ cairo_scaled_font_glyph_extents(fScaledFont, &cairoGlyph, 1, &extents);
|
|
+
|
|
+ glyph->fAdvanceX = SkDoubleToFixed(extents.x_advance);
|
|
+ glyph->fAdvanceY = SkDoubleToFixed(extents.y_advance);
|
|
+ glyph->fWidth = SkToU16(SkScalarCeil(extents.width));
|
|
+ glyph->fHeight = SkToU16(SkScalarCeil(extents.height));
|
|
+ glyph->fLeft = SkToS16(SkScalarCeil(extents.x_bearing));
|
|
+ glyph->fTop = SkToS16(SkScalarCeil(extents.y_bearing));
|
|
+ glyph->fLsbDelta = 0;
|
|
+ glyph->fRsbDelta = 0;
|
|
+}
|
|
+
|
|
+void SkScalerContext_CairoFT::generateImage(const SkGlyph& glyph)
|
|
+{
|
|
+ SkASSERT(fScaledFont != NULL);
|
|
+ CairoLockedFTFace faceLock(fScaledFont);
|
|
+ FT_Face face = faceLock.getFace();
|
|
+
|
|
+ FT_Error err = FT_Load_Glyph(face, glyph.getGlyphID(fBaseGlyphCount), fLoadGlyphFlags);
|
|
+
|
|
+ if (err != 0) {
|
|
+ memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ generateGlyphImage(face, glyph);
|
|
+}
|
|
+
|
|
+void SkScalerContext_CairoFT::generatePath(const SkGlyph& glyph, SkPath* path)
|
|
+{
|
|
+ SkASSERT(fScaledFont != NULL);
|
|
+ CairoLockedFTFace faceLock(fScaledFont);
|
|
+ FT_Face face = faceLock.getFace();
|
|
+
|
|
+ SkASSERT(&glyph && path);
|
|
+
|
|
+ uint32_t flags = fLoadGlyphFlags;
|
|
+ flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
|
|
+ flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline)
|
|
+
|
|
+ FT_Error err = FT_Load_Glyph(face, glyph.getGlyphID(fBaseGlyphCount), flags);
|
|
+
|
|
+ if (err != 0) {
|
|
+ path->reset();
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ generateGlyphPath(face, path);
|
|
+}
|
|
+
|
|
+void SkScalerContext_CairoFT::generateFontMetrics(SkPaint::FontMetrics* mx,
|
|
+ SkPaint::FontMetrics* my)
|
|
+{
|
|
+ SkDEBUGCODE(SkDebugf("SkScalerContext_CairoFT::generateFontMetrics unimplemented\n"));
|
|
+}
|
|
+
|
|
+SkUnichar SkScalerContext_CairoFT::generateGlyphToChar(uint16_t glyph)
|
|
+{
|
|
+ SkASSERT(fScaledFont != NULL);
|
|
+ CairoLockedFTFace faceLock(fScaledFont);
|
|
+ FT_Face face = faceLock.getFace();
|
|
+
|
|
+ FT_UInt glyphIndex;
|
|
+ SkUnichar charCode = FT_Get_First_Char(face, &glyphIndex);
|
|
+ while (glyphIndex != 0) {
|
|
+ if (glyphIndex == glyph) {
|
|
+ return charCode;
|
|
+ }
|
|
+ charCode = FT_Get_Next_Char(face, charCode, &glyphIndex);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef SK_BUILD_FOR_ANDROID
|
|
+SkTypeface* SkAndroidNextLogicalTypeface(SkFontID currFontID,
|
|
+ SkFontID origFontID) {
|
|
+ return NULL;
|
|
+}
|
|
+#endif
|
|
+
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
+
|
|
+#include "SkFontMgr.h"
|
|
+
|
|
+SkFontMgr* SkFontMgr::Factory() {
|
|
+ // todo
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
--
|
|
1.7.11.7
|
|
|