diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-23 04:05:01 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-23 04:05:01 +0000 |
commit | d07b6b5b992faad3506ac004503c11d25930cdca (patch) | |
tree | a5a795b8980d90e4a10208ad4f8eabe140ee3e5e /gfx | |
parent | f2a13d89ca22cdbe3808bcb14e02d5baee8d33b3 (diff) | |
download | chromium_src-d07b6b5b992faad3506ac004503c11d25930cdca.zip chromium_src-d07b6b5b992faad3506ac004503c11d25930cdca.tar.gz chromium_src-d07b6b5b992faad3506ac004503c11d25930cdca.tar.bz2 |
Move app/gfx/canvas and app/gfx/font to gfx/.
TBR=darin
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/1132006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42312 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'gfx')
-rw-r--r-- | gfx/canvas.cc | 277 | ||||
-rw-r--r-- | gfx/canvas.h | 238 | ||||
-rw-r--r-- | gfx/canvas_linux.cc | 281 | ||||
-rw-r--r-- | gfx/canvas_mac.mm | 78 | ||||
-rw-r--r-- | gfx/canvas_paint.h | 20 | ||||
-rw-r--r-- | gfx/canvas_win.cc | 286 | ||||
-rw-r--r-- | gfx/font.h | 287 | ||||
-rw-r--r-- | gfx/font_gtk.cc | 156 | ||||
-rw-r--r-- | gfx/font_mac.mm | 93 | ||||
-rw-r--r-- | gfx/font_skia.cc | 255 | ||||
-rw-r--r-- | gfx/font_unittest.cc | 80 | ||||
-rw-r--r-- | gfx/font_win.cc | 224 | ||||
-rw-r--r-- | gfx/gfx.gyp | 16 |
13 files changed, 2291 insertions, 0 deletions
diff --git a/gfx/canvas.cc b/gfx/canvas.cc new file mode 100644 index 0000000..0099e4c --- /dev/null +++ b/gfx/canvas.cc @@ -0,0 +1,277 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas.h" + +#include <limits> + +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "gfx/font.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace gfx { + +bool Canvas::GetClipRect(gfx::Rect* r) { + SkRect clip; + if (!getClipBounds(&clip)) { + if (r) + r->SetRect(0, 0, 0, 0); + return false; + } + r->SetRect(SkScalarRound(clip.fLeft), SkScalarRound(clip.fTop), + SkScalarRound(clip.fRight - clip.fLeft), + SkScalarRound(clip.fBottom - clip.fTop)); + return true; +} + +bool Canvas::ClipRectInt(int x, int y, int w, int h) { + SkRect new_clip; + new_clip.set(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(x + w), SkIntToScalar(y + h)); + return clipRect(new_clip); +} + +bool Canvas::IntersectsClipRectInt(int x, int y, int w, int h) { + SkRect clip; + return getClipBounds(&clip) && + clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w), + SkIntToScalar(y + h)); +} + +void Canvas::TranslateInt(int x, int y) { + translate(SkIntToScalar(x), SkIntToScalar(y)); +} + +void Canvas::ScaleInt(int x, int y) { + scale(SkIntToScalar(x), SkIntToScalar(y)); +} + +void Canvas::FillRectInt(const SkColor& color, int x, int y, int w, int h) { + SkPaint paint; + paint.setColor(color); + paint.setStyle(SkPaint::kFill_Style); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + FillRectInt(x, y, w, h, paint); +} + +void Canvas::FillRectInt(int x, int y, int w, int h, const SkPaint& paint) { + SkIRect rc = {x, y, x + w, y + h}; + drawIRect(rc, paint); +} + +void Canvas::DrawRectInt(const SkColor& color, int x, int y, int w, int h) { + DrawRectInt(color, x, y, w, h, SkXfermode::kSrcOver_Mode); +} + +void Canvas::DrawRectInt(const SkColor& color, int x, int y, int w, int h, + SkXfermode::Mode mode) { + SkPaint paint; + paint.setColor(color); + paint.setStyle(SkPaint::kStroke_Style); + // Set a stroke width of 0, which will put us down the stroke rect path. If + // we set a stroke width of 1, for example, this will internally create a + // path and fill it, which causes problems near the edge of the canvas. + paint.setStrokeWidth(SkIntToScalar(0)); + paint.setXfermodeMode(mode); + + SkIRect rc = {x, y, x + w, y + h}; + drawIRect(rc, paint); +} + +void Canvas::DrawLineInt(const SkColor& color, int x1, int y1, int x2, int y2) { + SkPaint paint; + paint.setColor(color); + paint.setStrokeWidth(SkIntToScalar(1)); + drawLine(SkIntToScalar(x1), SkIntToScalar(y1), SkIntToScalar(x2), + SkIntToScalar(y2), paint); +} + +void Canvas::DrawFocusRect(int x, int y, int width, int height) { + // Create a 2D bitmap containing alternating on/off pixels - we do this + // so that you never get two pixels of the same color around the edges + // of the focus rect (this may mean that opposing edges of the rect may + // have a dot pattern out of phase to each other). + static SkBitmap* dots = NULL; + if (!dots) { + int col_pixels = 32; + int row_pixels = 32; + + dots = new SkBitmap; + dots->setConfig(SkBitmap::kARGB_8888_Config, col_pixels, row_pixels); + dots->allocPixels(); + dots->eraseARGB(0, 0, 0, 0); + + uint32_t* dot = dots->getAddr32(0, 0); + for (int i = 0; i < row_pixels; i++) { + for (int u = 0; u < col_pixels; u++) { + if ((u % 2 + i % 2) % 2 != 0) { + dot[i * row_pixels + u] = SK_ColorGRAY; + } + } + } + } + + // First the horizontal lines. + + // Make a shader for the bitmap with an origin of the box we'll draw. This + // shader is refcounted and will have an initial refcount of 1. + SkShader* shader = SkShader::CreateBitmapShader( + *dots, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); + // Assign the shader to the paint & release our reference. The paint will + // now own the shader and the shader will be destroyed when the paint goes + // out of scope. + SkPaint paint; + paint.setShader(shader); + shader->unref(); + + SkRect rect; + rect.set(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(x + width), SkIntToScalar(y + 1)); + drawRect(rect, paint); + rect.set(SkIntToScalar(x), SkIntToScalar(y + height - 1), + SkIntToScalar(x + width), SkIntToScalar(y + height)); + drawRect(rect, paint); + + rect.set(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(x + 1), SkIntToScalar(y + height)); + drawRect(rect, paint); + rect.set(SkIntToScalar(x + width - 1), SkIntToScalar(y), + SkIntToScalar(x + width), SkIntToScalar(y + height)); + drawRect(rect, paint); +} + +void Canvas::DrawBitmapInt(const SkBitmap& bitmap, int x, int y) { + drawBitmap(bitmap, SkIntToScalar(x), SkIntToScalar(y)); +} + +void Canvas::DrawBitmapInt(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint) { + drawBitmap(bitmap, SkIntToScalar(x), SkIntToScalar(y), &paint); +} + +void Canvas::DrawBitmapInt(const SkBitmap& bitmap, int src_x, int src_y, + int src_w, int src_h, int dest_x, int dest_y, + int dest_w, int dest_h, + bool filter) { + SkPaint p; + DrawBitmapInt(bitmap, src_x, src_y, src_w, src_h, dest_x, dest_y, + dest_w, dest_h, filter, p); +} + +void Canvas::DrawBitmapInt(const SkBitmap& bitmap, int src_x, int src_y, + int src_w, int src_h, int dest_x, int dest_y, + int dest_w, int dest_h, + bool filter, const SkPaint& paint) { + DLOG_ASSERT(src_x + src_w < std::numeric_limits<int16_t>::max() && + src_y + src_h < std::numeric_limits<int16_t>::max()); + if (src_w <= 0 || src_h <= 0 || dest_w <= 0 || dest_h <= 0) { + NOTREACHED() << "Attempting to draw bitmap to/from an empty rect!"; + return; + } + + if (!IntersectsClipRectInt(dest_x, dest_y, dest_w, dest_h)) + return; + + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + dest_w), + SkIntToScalar(dest_y + dest_h) }; + + if (src_w == dest_w && src_h == dest_h) { + // Workaround for apparent bug in Skia that causes image to occasionally + // shift. + SkIRect src_rect = { src_x, src_y, src_x + src_w, src_y + src_h }; + drawBitmapRect(bitmap, &src_rect, dest_rect, &paint); + return; + } + + // Make a bitmap shader that contains the bitmap we want to draw. This is + // basically what SkCanvas.drawBitmap does internally, but it gives us + // more control over quality and will use the mipmap in the source image if + // it has one, whereas drawBitmap won't. + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(static_cast<float>(dest_w) / src_w), + SkFloatToScalar(static_cast<float>(dest_h) / src_h)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + shader->setLocalMatrix(shader_scale); + + // Set up our paint to use the shader & release our reference (now just owned + // by the paint). + SkPaint p(paint); + p.setFilterBitmap(filter); + p.setShader(shader); + shader->unref(); + + // The rect will be filled by the bitmap. + drawRect(dest_rect, p); +} + +void Canvas::DrawStringInt(const std::wstring& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h) { + DrawStringInt(text, font, color, x, y, w, h, + gfx::Canvas::DefaultCanvasTextAlignment()); +} + +void Canvas::DrawStringInt(const std::wstring& text, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect) { + DrawStringInt(text, font, color, display_rect.x(), display_rect.y(), + display_rect.width(), display_rect.height()); +} + +void Canvas::TileImageInt(const SkBitmap& bitmap, int x, int y, int w, int h) { + TileImageInt(bitmap, 0, 0, x, y, w, h); +} + +void Canvas::TileImageInt(const SkBitmap& bitmap, int src_x, int src_y, + int dest_x, int dest_y, int w, int h) { + if (!IntersectsClipRectInt(dest_x, dest_y, w, h)) + return; + + SkPaint paint; + + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + paint.setShader(shader); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + + // CreateBitmapShader returns a Shader with a reference count of one, we + // need to unref after paint takes ownership of the shader. + shader->unref(); + save(); + translate(SkIntToScalar(dest_x - src_x), SkIntToScalar(dest_y - src_y)); + ClipRectInt(src_x, src_y, w, h); + drawPaint(paint); + restore(); +} + +SkBitmap Canvas::ExtractBitmap() const { + const SkBitmap& device_bitmap = getDevice()->accessBitmap(false); + + // Make a bitmap to return, and a canvas to draw into it. We don't just want + // to call extractSubset or the copy constuctor, since we want an actual copy + // of the bitmap. + SkBitmap result; + device_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config); + return result; +} + +// static +int Canvas::DefaultCanvasTextAlignment() { + if (!base::i18n::IsRTL()) + return gfx::Canvas::TEXT_ALIGN_LEFT; + return gfx::Canvas::TEXT_ALIGN_RIGHT; +} + +} // namespace gfx diff --git a/gfx/canvas.h b/gfx/canvas.h new file mode 100644 index 0000000..ffc30fb --- /dev/null +++ b/gfx/canvas.h @@ -0,0 +1,238 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GFX_CANVAS_H_ +#define GFX_CANVAS_H_ + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> + +#include "base/basictypes.h" +#include "skia/ext/platform_canvas.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +typedef struct _cairo cairo_t; +typedef struct _GdkPixbuf GdkPixbuf; +#endif + +namespace gfx { + +class Font; +class Rect; + +// Canvas is a SkCanvas subclass that provides a number of methods for common +// operations used throughout an application built using base/gfx and app/gfx. +// +// All methods that take integer arguments (as is used throughout views) +// end with Int. If you need to use methods provided by the superclass +// you'll need to do a conversion. In particular you'll need to use +// macro SkIntToScalar(xxx), or if converting from a scalar to an integer +// SkScalarRound. +// +// A handful of methods in this class are overloaded providing an additional +// argument of type SkXfermode::Mode. SkXfermode::Mode specifies how the +// source and destination colors are combined. Unless otherwise specified, +// the variant that does not take a SkXfermode::Mode uses a transfer mode +// of kSrcOver_Mode. +class Canvas : public skia::PlatformCanvas { + public: + // Specifies the alignment for text rendered with the DrawStringInt method. + enum { + TEXT_ALIGN_LEFT = 1, + TEXT_ALIGN_CENTER = 2, + TEXT_ALIGN_RIGHT = 4, + TEXT_VALIGN_TOP = 8, + TEXT_VALIGN_MIDDLE = 16, + TEXT_VALIGN_BOTTOM = 32, + + // Specifies the text consists of multiple lines. + MULTI_LINE = 64, + + // By default DrawStringInt does not process the prefix ('&') character + // specially. That is, the string "&foo" is rendered as "&foo". When + // rendering text from a resource that uses the prefix character for + // mnemonics, the prefix should be processed and can be rendered as an + // underline (SHOW_PREFIX), or not rendered at all (HIDE_PREFIX). + SHOW_PREFIX = 128, + HIDE_PREFIX = 256, + + // Prevent ellipsizing + NO_ELLIPSIS = 512, + + // Specifies if words can be split by new lines. + // This only works with MULTI_LINE. + CHARACTER_BREAK = 1024, + + // Instructs DrawStringInt() to render the text using RTL directionality. + // In most cases, passing this flag is not necessary because information + // about the text directionality is going to be embedded within the string + // in the form of special Unicode characters. However, we don't insert + // directionality characters into strings if the locale is LTR because some + // platforms (for example, an English Windows XP with no RTL fonts + // installed) don't support these characters. Thus, this flag should be + // used to render text using RTL directionality when the locale is LTR. + FORCE_RTL_DIRECTIONALITY = 2048, + }; + + // Creates an empty Canvas. Callers must use initialize before using the + // canvas. + Canvas(); + + Canvas(int width, int height, bool is_opaque); + + virtual ~Canvas(); + + // Retrieves the clip rectangle and sets it in the specified rectangle if any. + // Returns true if the clip rect is non-empty. + bool GetClipRect(gfx::Rect* clip_rect); + + // Wrapper function that takes integer arguments. + // Returns true if the clip is non-empty. + // See clipRect for specifics. + bool ClipRectInt(int x, int y, int w, int h); + + // Test whether the provided rectangle intersects the current clip rect. + bool IntersectsClipRectInt(int x, int y, int w, int h); + + // Wrapper function that takes integer arguments. + // See translate() for specifics. + void TranslateInt(int x, int y); + + // Wrapper function that takes integer arguments. + // See scale() for specifics. + void ScaleInt(int x, int y); + + // Fills the given rectangle with the given paint's parameters. + void FillRectInt(int x, int y, int w, int h, const SkPaint& paint); + + // Fills the specified region with the specified color using a transfer + // mode of SkXfermode::kSrcOver_Mode. + void FillRectInt(const SkColor& color, int x, int y, int w, int h); + + // Draws a single pixel rect in the specified region with the specified + // color, using a transfer mode of SkXfermode::kSrcOver_Mode. + // + // NOTE: if you need a single pixel line, use DraLineInt. + void DrawRectInt(const SkColor& color, int x, int y, int w, int h); + + // Draws a single pixel rect in the specified region with the specified + // color and transfer mode. + // + // NOTE: if you need a single pixel line, use DraLineInt. + void DrawRectInt(const SkColor& color, int x, int y, int w, int h, + SkXfermode::Mode mode); + + // Draws a single pixel line with the specified color. + void DrawLineInt(const SkColor& color, int x1, int y1, int x2, int y2); + + // Draws a bitmap with the origin at the specified location. The upper left + // corner of the bitmap is rendered at the specified location. + void DrawBitmapInt(const SkBitmap& bitmap, int x, int y); + + // Draws a bitmap with the origin at the specified location, using the + // specified paint. The upper left corner of the bitmap is rendered at the + // specified location. + void DrawBitmapInt(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint); + + // Draws a portion of a bitmap in the specified location. The src parameters + // correspond to the region of the bitmap to draw in the region defined + // by the dest coordinates. + // + // If the width or height of the source differs from that of the destination, + // the bitmap will be scaled. When scaling down, it is highly recommended + // that you call buildMipMap(false) on your bitmap to ensure that it has + // a mipmap, which will result in much higher-quality output. Set |filter| + // to use filtering for bitmaps, otherwise the nearest-neighbor algorithm + // is used for resampling. + // + // An optional custom SkPaint can be provided. + void DrawBitmapInt(const SkBitmap& bitmap, int src_x, int src_y, int src_w, + int src_h, int dest_x, int dest_y, int dest_w, int dest_h, + bool filter); + void DrawBitmapInt(const SkBitmap& bitmap, int src_x, int src_y, int src_w, + int src_h, int dest_x, int dest_y, int dest_w, int dest_h, + bool filter, const SkPaint& paint); + + // Draws text with the specified color, font and location. The text is + // aligned to the left, vertically centered, clipped to the region. If the + // text is too big, it is truncated and '...' is added to the end. + void DrawStringInt(const std::wstring& text, const gfx::Font& font, + const SkColor& color, int x, int y, int w, int h); + void DrawStringInt(const std::wstring& text, const gfx::Font& font, + const SkColor& color, const gfx::Rect& display_rect); + + // Draws text with the specified color, font and location. The last argument + // specifies flags for how the text should be rendered. It can be one of + // TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT. + void DrawStringInt(const std::wstring& text, const gfx::Font& font, + const SkColor& color, int x, int y, int w, int h, + int flags); + +#ifdef OS_WIN // Only implemented on Windows for now. + // Draws text with a 1-pixel halo around it of the given color. It allows + // ClearType to be drawn to an otherwise transparenct bitmap for drag images. + // Drag images have only 1-bit of transparency, so we don't do any fancy + // blurring. + void DrawStringWithHalo(const std::wstring& text, const gfx::Font& font, + const SkColor& text_color, const SkColor& halo_color, + int x, int y, int w, int h, int flags); +#endif + + // Draws a dotted gray rectangle used for focus purposes. + void DrawFocusRect(int x, int y, int width, int height); + + // Tiles the image in the specified region. + void TileImageInt(const SkBitmap& bitmap, int x, int y, int w, int h); + void TileImageInt(const SkBitmap& bitmap, int src_x, int src_y, + int dest_x, int dest_y, int w, int h); + + // Extracts a bitmap from the contents of this canvas. + SkBitmap ExtractBitmap() const; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Applies current matrix on the canvas to the cairo context. This should be + // invoked anytime you plan on drawing directly to the cairo context. Be + // sure and set the matrix back to the identity when done. + void ApplySkiaMatrixToCairoContext(cairo_t* cr); + + // Draw the pixbuf in its natural size at (x, y). + void DrawGdkPixbuf(GdkPixbuf* pixbuf, int x, int y); +#endif + + // Compute the size required to draw some text with the provided font. + // Attempts to fit the text with the provided width and height. Increases + // height and then width as needed to make the text fit. This method + // supports multiple lines. + static void SizeStringInt(const std::wstring& test, const gfx::Font& font, + int *width, int* height, int flags); + + // Returns the default text alignment to be used when drawing text on a + // gfx::Canvas based on the directionality of the system locale language. This + // function is used by gfx::Canvas::DrawStringInt when the text alignment is + // not specified. + // + // This function returns either gfx::Canvas::TEXT_ALIGN_LEFT or + // gfx::Canvas::TEXT_ALIGN_RIGHT. + static int DefaultCanvasTextAlignment(); + + private: +#if defined(OS_WIN) + // Draws text with the specified color, font and location. The text is + // aligned to the left, vertically centered, clipped to the region. If the + // text is too big, it is truncated and '...' is added to the end. + void DrawStringInt(const std::wstring& text, HFONT font, + const SkColor& color, int x, int y, int w, int h, + int flags); +#endif + + DISALLOW_COPY_AND_ASSIGN(Canvas); +}; + +} // namespace gfx; + +#endif // GFX_CANVAS_H_ diff --git a/gfx/canvas_linux.cc b/gfx/canvas_linux.cc new file mode 100644 index 0000000..0981d56 --- /dev/null +++ b/gfx/canvas_linux.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas.h" + +#include <cairo/cairo.h> +#include <gtk/gtk.h> +#include <pango/pango.h> +#include <pango/pangocairo.h> + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "gfx/font.h" +#include "gfx/rect.h" + +namespace { + +const gunichar kAcceleratorChar = '&'; + +// Font settings that we initialize once and then use when drawing text in +// DrawStringInt(). +static cairo_font_options_t* cairo_font_options = NULL; + +// Returns the resolution used by pango. A negative values means the resolution +// hasn't been set. +static double GetPangoResolution() { + static double resolution; + static bool determined_resolution = false; + if (!determined_resolution) { + determined_resolution = true; + PangoContext* default_context = gdk_pango_context_get(); + resolution = pango_cairo_context_get_resolution(default_context); + g_object_unref(default_context); + } + return resolution; +} + +// Update |cairo_font_options| based on GtkSettings, allocating it if needed. +static void UpdateCairoFontOptions() { + if (!cairo_font_options) + cairo_font_options = cairo_font_options_create(); + + GtkSettings* gtk_settings = gtk_settings_get_default(); + gint antialias = 0; + gint hinting = 0; + gchar* hint_style = NULL; + gchar* rgba_style = NULL; + g_object_get(gtk_settings, + "gtk-xft-antialias", &antialias, + "gtk-xft-hinting", &hinting, + "gtk-xft-hintstyle", &hint_style, + "gtk-xft-rgba", &rgba_style, + NULL); + + // g_object_get() doesn't tell us whether the properties were present or not, + // but if they aren't (because gnome-settings-daemon isn't running), we'll get + // NULL values for the strings. + if (hint_style && rgba_style) { + if (!antialias) { + cairo_font_options_set_antialias(cairo_font_options, + CAIRO_ANTIALIAS_NONE); + } else if (strcmp(rgba_style, "none") == 0) { + cairo_font_options_set_antialias(cairo_font_options, + CAIRO_ANTIALIAS_GRAY); + } else { + cairo_font_options_set_antialias(cairo_font_options, + CAIRO_ANTIALIAS_SUBPIXEL); + cairo_subpixel_order_t cairo_subpixel_order = + CAIRO_SUBPIXEL_ORDER_DEFAULT; + if (strcmp(rgba_style, "rgb") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB; + } else if (strcmp(rgba_style, "bgr") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR; + } else if (strcmp(rgba_style, "vrgb") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB; + } else if (strcmp(rgba_style, "vbgr") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR; + } + cairo_font_options_set_subpixel_order(cairo_font_options, + cairo_subpixel_order); + } + + cairo_hint_style_t cairo_hint_style = CAIRO_HINT_STYLE_DEFAULT; + if (hinting == 0 || strcmp(hint_style, "hintnone") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_NONE; + } else if (strcmp(hint_style, "hintslight") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_SLIGHT; + } else if (strcmp(hint_style, "hintmedium") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_MEDIUM; + } else if (strcmp(hint_style, "hintfull") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_FULL; + } + cairo_font_options_set_hint_style(cairo_font_options, cairo_hint_style); + } + + if (hint_style) + g_free(hint_style); + if (rgba_style) + g_free(rgba_style); +} + +} // namespace + +namespace gfx { + +Canvas::Canvas(int width, int height, bool is_opaque) + : skia::PlatformCanvas(width, height, is_opaque) { +} + +Canvas::Canvas() : skia::PlatformCanvas() { +} + +Canvas::~Canvas() { +} + +// Pass a width > 0 to force wrapping and elliding. +static void SetupPangoLayout(PangoLayout* layout, + const std::wstring& text, + const gfx::Font& font, + int width, + int flags) { + if (!cairo_font_options) + UpdateCairoFontOptions(); + // This needs to be done early on; it has no effect when called just before + // pango_cairo_show_layout(). + pango_cairo_context_set_font_options( + pango_layout_get_context(layout), cairo_font_options); + + // Callers of DrawStringInt handle RTL layout themselves, so tell pango to not + // scope out RTL characters. + pango_layout_set_auto_dir(layout, FALSE); + + if (width > 0) + pango_layout_set_width(layout, width * PANGO_SCALE); + + if (flags & Canvas::NO_ELLIPSIS) { + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + } else { + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + } + + if (flags & Canvas::TEXT_ALIGN_CENTER) { + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + } else if (flags & Canvas::TEXT_ALIGN_RIGHT) { + pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); + } + + if (flags & Canvas::MULTI_LINE) { + pango_layout_set_wrap(layout, + (flags & Canvas::CHARACTER_BREAK) ? + PANGO_WRAP_WORD_CHAR : PANGO_WRAP_WORD); + } + + // Set the resolution to match that used by Gtk. If we don't set the + // resolution and the resolution differs from the default, Gtk and Chrome end + // up drawing at different sizes. + double resolution = GetPangoResolution(); + if (resolution > 0) { + pango_cairo_context_set_resolution(pango_layout_get_context(layout), + resolution); + } + + PangoFontDescription* desc = gfx::Font::PangoFontFromGfxFont(font); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + + // Set text and accelerator character if needed. + std::string utf8 = WideToUTF8(text); + if (flags & gfx::Canvas::HIDE_PREFIX) { + // Escape the text string to be used as markup. + gchar* escaped_text = g_markup_escape_text(utf8.c_str(), utf8.size()); + pango_layout_set_markup_with_accel(layout, + escaped_text, + strlen(escaped_text), + kAcceleratorChar, NULL); + g_free(escaped_text); + } else { + pango_layout_set_text(layout, utf8.data(), utf8.size()); + } +} + +// static +void Canvas::SizeStringInt(const std::wstring& text, + const gfx::Font& font, + int* width, int* height, int flags) { + int org_width = *width; + cairo_surface_t* surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); + cairo_t* cr = cairo_create(surface); + PangoLayout* layout = pango_cairo_create_layout(cr); + + SetupPangoLayout(layout, text, font, *width, flags); + + pango_layout_get_pixel_size(layout, width, height); + + if (org_width > 0 && flags & Canvas::MULTI_LINE && + pango_layout_is_wrapped(layout)) { + // The text wrapped. There seems to be a bug in Pango when this happens + // such that the width returned from pango_layout_get_pixel_size is too + // small. Using the width from pango_layout_get_pixel_size in this case + // results in wrapping across more lines, which requires a bigger height. + // As a workaround we use the original width, which is not necessarily + // exactly correct, but isn't wrong by much. + // + // It looks like Pango uses the size of whitespace in calculating wrapping + // but doesn't include the size of the whitespace when the extents are + // asked for. See the loop in pango-layout.c process_item that determines + // where to wrap. + *width = org_width; + } + + g_object_unref(layout); + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +void Canvas::DrawStringInt(const std::wstring& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags) { + if (w <= 0 || h <= 0) + return; + + cairo_t* cr = beginPlatformPaint(); + PangoLayout* layout = pango_cairo_create_layout(cr); + + SetupPangoLayout(layout, text, font, w, flags); + + pango_layout_set_height(layout, h * PANGO_SCALE); + + cairo_save(cr); + cairo_set_source_rgb(cr, + SkColorGetR(color) / 255.0, + SkColorGetG(color) / 255.0, + SkColorGetB(color) / 255.0); + + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + cairo_rectangle(cr, x, y, w, h); + cairo_clip(cr); + + if (flags & Canvas::TEXT_VALIGN_TOP) { + // Cairo should draw from the top left corner already. + } else if (flags & Canvas::TEXT_VALIGN_BOTTOM) { + y += (h - height); + } else { + // Vertically centered. + y += ((h - height) / 2); + } + + cairo_move_to(cr, x, y); + pango_cairo_show_layout(cr, layout); + if (font.style() & gfx::Font::UNDERLINED) { + double underline_y = + static_cast<double>(y) + height + font.underline_position(); + cairo_set_line_width(cr, font.underline_thickness()); + cairo_move_to(cr, x, underline_y); + cairo_line_to(cr, x + width, underline_y); + cairo_stroke(cr); + } + cairo_restore(cr); + + g_object_unref(layout); + // NOTE: beginPlatformPaint returned its surface, we shouldn't destroy it. +} + +void Canvas::DrawGdkPixbuf(GdkPixbuf* pixbuf, int x, int y) { + if (!pixbuf) { + NOTREACHED(); + return; + } + + cairo_t* cr = beginPlatformPaint(); + gdk_cairo_set_source_pixbuf(cr, pixbuf, x, y); + cairo_paint(cr); +} + +} // namespace gfx diff --git a/gfx/canvas_mac.mm b/gfx/canvas_mac.mm new file mode 100644 index 0000000..ebba090 --- /dev/null +++ b/gfx/canvas_mac.mm @@ -0,0 +1,78 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Cocoa/Cocoa.h> + +#include "gfx/canvas.h" + +#include "base/scoped_cftyperef.h" +#include "base/sys_string_conversions.h" +#include "gfx/font.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace gfx { + +Canvas::Canvas(int width, int height, bool is_opaque) + : skia::PlatformCanvas(width, height, is_opaque) { +} + +Canvas::Canvas() : skia::PlatformCanvas() { +} + +Canvas::~Canvas() { +} + +// static +void Canvas::SizeStringInt(const std::wstring& text, + const gfx::Font& font, + int *width, int *height, int flags) { + *width = font.GetStringWidth(text); + *height = font.height(); +} + +void Canvas::DrawStringInt(const std::wstring& text, const gfx::Font& font, + const SkColor& color, int x, int y, int w, int h, + int flags) { + if (!IntersectsClipRectInt(x, y, w, h)) + return; + + CGContextRef context = beginPlatformPaint(); + CGContextSaveGState(context); + + NSColor* ns_color = [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0 + green:SkColorGetG(color) / 255.0 + blue:SkColorGetB(color) / 255.0 + alpha:1.0]; + NSMutableParagraphStyle *ns_style = + [[[NSParagraphStyle alloc] init] autorelease]; + if (flags & TEXT_ALIGN_CENTER) + [ns_style setAlignment:NSCenterTextAlignment]; + // TODO(awalker): Implement the rest of the Canvas text flags + + NSDictionary* attributes = + [NSDictionary dictionaryWithObjectsAndKeys: + font.nativeFont(), NSFontAttributeName, + ns_color, NSForegroundColorAttributeName, + ns_style, NSParagraphStyleAttributeName, + nil]; + + NSAttributedString* ns_string = + [[[NSAttributedString alloc] initWithString:base::SysWideToNSString(text) + attributes:attributes] autorelease]; + scoped_cftyperef<CTFramesetterRef> framesetter( + CTFramesetterCreateWithAttributedString(reinterpret_cast<CFAttributedStringRef>(ns_string))); + + CGRect text_bounds = CGRectMake(x, y, w, h); + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, NULL, text_bounds); + + scoped_cftyperef<CTFrameRef> frame( + CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL)); + CTFrameDraw(frame, context); + CGContextRestoreGState(context); + endPlatformPaint(); +} + +} // namespace gfx diff --git a/gfx/canvas_paint.h b/gfx/canvas_paint.h new file mode 100644 index 0000000..a11a693 --- /dev/null +++ b/gfx/canvas_paint.h @@ -0,0 +1,20 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GFX_CANVAS_PAINT_H_ +#define GFX_CANVAS_PAINT_H_ + +#include "gfx/canvas.h" +#include "skia/ext/canvas_paint.h" + +// Define a skia::CanvasPaint type that wraps our gfx::Canvas like the +// skia::PlatformCanvasPaint wraps PlatformCanvas. + +namespace gfx { + +typedef skia::CanvasPaintT<Canvas> CanvasPaint; + +} // namespace gfx + +#endif // GFX_CANVAS_PAINT_H_ diff --git a/gfx/canvas_win.cc b/gfx/canvas_win.cc new file mode 100644 index 0000000..be9756a --- /dev/null +++ b/gfx/canvas_win.cc @@ -0,0 +1,286 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas.h" + +#include <limits> + +#include "base/i18n/rtl.h" +#include "gfx/font.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace { + +// We make sure that LTR text we draw in an RTL context is modified +// appropriately to make sure it maintains it LTR orientation. +void DoDrawText(HDC hdc, const std::wstring& text, + RECT* text_bounds, int flags) { + std::wstring localized_text; + const wchar_t* string_ptr = text.c_str(); + int string_size = static_cast<int>(text.length()); + // Only adjust string directionality if both of the following are true: + // 1. The current locale is RTL. + // 2. The string itself has RTL directionality. + if (flags & DT_RTLREADING) { + if (base::i18n::AdjustStringForLocaleDirection(text, &localized_text)) { + string_ptr = localized_text.c_str(); + string_size = static_cast<int>(localized_text.length()); + } + } + + DrawText(hdc, string_ptr, string_size, text_bounds, flags); +} + +// Compute the windows flags necessary to implement the provided text Canvas +// flags. +int ComputeFormatFlags(int flags, const std::wstring& text) { + // Setting the text alignment explicitly in case it hasn't already been set. + // This will make sure that we don't align text to the left on RTL locales + // just because no alignment flag was passed to DrawStringInt(). + if (!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER | + gfx::Canvas::TEXT_ALIGN_RIGHT | + gfx::Canvas::TEXT_ALIGN_LEFT))) { + flags |= gfx::Canvas::DefaultCanvasTextAlignment(); + } + + // horizontal alignment + int f = 0; + if (flags & gfx::Canvas::TEXT_ALIGN_CENTER) + f |= DT_CENTER; + else if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT) + f |= DT_RIGHT; + else + f |= DT_LEFT; + + // vertical alignment + if (flags & gfx::Canvas::TEXT_VALIGN_TOP) + f |= DT_TOP; + else if (flags & gfx::Canvas::TEXT_VALIGN_BOTTOM) + f |= DT_BOTTOM; + else + f |= DT_VCENTER; + + if (flags & gfx::Canvas::MULTI_LINE) { + f |= DT_WORDBREAK; + if (flags & gfx::Canvas::CHARACTER_BREAK) + f |= DT_EDITCONTROL; // Turns on character breaking (not documented) + else if (!(flags & gfx::Canvas::NO_ELLIPSIS)) + f |= DT_WORD_ELLIPSIS; + } else { + f |= DT_SINGLELINE; + } + + if (flags & gfx::Canvas::HIDE_PREFIX) + f |= DT_HIDEPREFIX; + else if ((flags & gfx::Canvas::SHOW_PREFIX) == 0) + f |= DT_NOPREFIX; + + if (!(flags & gfx::Canvas::NO_ELLIPSIS)) + f |= DT_END_ELLIPSIS; + + // In order to make sure RTL/BiDi strings are rendered correctly, we must + // pass the flag DT_RTLREADING to DrawText (when the locale's language is + // a right-to-left language) so that Windows does the right thing. + // + // In addition to correctly displaying text containing both RTL and LTR + // elements (for example, a string containing a telephone number within a + // sentence in Hebrew, or a sentence in Hebrew that contains a word in + // English) this flag also makes sure that if there is not enough space to + // display the entire string, the ellipsis is displayed on the left hand side + // of the truncated string and not on the right hand side. + // + // We make a distinction between Chrome UI strings and text coming from a web + // page. + // + // For text coming from a web page we determine the alignment based on the + // first character with strong directionality. If the directionality of the + // first character with strong directionality in the text is LTR, the + // alignment is set to DT_LEFT, and the directionality should not be set as + // DT_RTLREADING. + // + // This heuristic doesn't work for Chrome UI strings since even in RTL + // locales, some of those might start with English text but we know they're + // localized so we always want them to be right aligned, and their + // directionality should be set as DT_RTLREADING. + // + // Caveat: If the string is purely LTR, don't set DTL_RTLREADING since when + // the flag is set, LRE-PDF don't have the desired effect of rendering + // multiline English-only text as LTR. + // + // Note that if the caller is explicitly requesting displaying the text + // using RTL directionality then we respect that and pass DT_RTLREADING to + // ::DrawText even if the locale is LTR. + if ((flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY) || + (base::i18n::IsRTL() && + (f & DT_RIGHT) && base::i18n::StringContainsStrongRTLChars(text))) { + f |= DT_RTLREADING; + } + + return f; +} + +} // anonymous namespace + +namespace gfx { + +Canvas::Canvas(int width, int height, bool is_opaque) + : skia::PlatformCanvas(width, height, is_opaque) { +} + +Canvas::Canvas() : skia::PlatformCanvas() { +} + +Canvas::~Canvas() { +} + +// static +void Canvas::SizeStringInt(const std::wstring& text, + const gfx::Font& font, + int *width, int *height, int flags) { + // Clamp the max amount of text we'll measure to 2K. When the string is + // actually drawn, it will be clipped to whatever size box is provided, and + // the time to do that doesn't depend on the length being clipped off. + const int kMaxStringLength = 2048; + std::wstring clamped_string(text.substr(0, kMaxStringLength)); + + if (*width == 0) { + // If multi-line + character break are on, the computed width will be one + // character wide (useless). Furthermore, if in this case the provided text + // contains very long "words" (substrings without a word-breaking point), + // DrawText() can run extremely slowly (e.g. several seconds). So in this + // case, we turn character breaking off to get a more accurate "desired" + // width and avoid the slowdown. + if (flags & (gfx::Canvas::MULTI_LINE | gfx::Canvas::CHARACTER_BREAK)) + flags &= ~gfx::Canvas::CHARACTER_BREAK; + + // Weird undocumented behavior: if the width is 0, DoDrawText() won't + // calculate a size at all. So set it to 1, which it will then change. + if (!text.empty()) + *width = 1; + } + RECT r = { 0, 0, *width, *height }; + + HDC dc = GetDC(NULL); + HFONT old_font = static_cast<HFONT>(SelectObject(dc, font.hfont())); + DoDrawText(dc, clamped_string, &r, + ComputeFormatFlags(flags, clamped_string) | DT_CALCRECT); + SelectObject(dc, old_font); + ReleaseDC(NULL, dc); + + *width = r.right; + *height = r.bottom; +} + +void Canvas::DrawStringInt(const std::wstring& text, HFONT font, + const SkColor& color, int x, int y, int w, int h, + int flags) { + if (!IntersectsClipRectInt(x, y, w, h)) + return; + + RECT text_bounds = { x, y, x + w, y + h }; + HDC dc = beginPlatformPaint(); + SetBkMode(dc, TRANSPARENT); + HFONT old_font = (HFONT)SelectObject(dc, font); + COLORREF brush_color = RGB(SkColorGetR(color), SkColorGetG(color), + SkColorGetB(color)); + SetTextColor(dc, brush_color); + + int f = ComputeFormatFlags(flags, text); + DoDrawText(dc, text, &text_bounds, f); + endPlatformPaint(); + + // Restore the old font. This way we don't have to worry if the caller + // deletes the font and the DC lives longer. + SelectObject(dc, old_font); + + // Windows will have cleared the alpha channel of the text we drew. Assume + // we're drawing to an opaque surface, or at least the text rect area is + // opaque. + getTopPlatformDevice().makeOpaque(x, y, w, h); +} + +void Canvas::DrawStringInt(const std::wstring& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, int flags) { + DrawStringInt(text, font.hfont(), color, x, y, w, h, flags); +} + +// Checks each pixel immediately adjacent to the given pixel in the bitmap. If +// any of them are not the halo color, returns true. This defines the halo of +// pixels that will appear around the text. Note that we have to check each +// pixel against both the halo color and transparent since DrawStringWithHalo +// will modify the bitmap as it goes, and clears pixels shouldn't count as +// changed. +static bool pixelShouldGetHalo(const SkBitmap& bitmap, int x, int y, + SkColor halo_color) { + if (x > 0 && + *bitmap.getAddr32(x - 1, y) != halo_color && + *bitmap.getAddr32(x - 1, y) != 0) + return true; // Touched pixel to the left. + if (x < bitmap.width() - 1 && + *bitmap.getAddr32(x + 1, y) != halo_color && + *bitmap.getAddr32(x + 1, y) != 0) + return true; // Touched pixel to the right. + if (y > 0 && + *bitmap.getAddr32(x, y - 1) != halo_color && + *bitmap.getAddr32(x, y - 1) != 0) + return true; // Touched pixel above. + if (y < bitmap.height() - 1 && + *bitmap.getAddr32(x, y + 1) != halo_color && + *bitmap.getAddr32(x, y + 1) != 0) + return true; // Touched pixel below. + return false; +} + +void Canvas::DrawStringWithHalo(const std::wstring& text, + const gfx::Font& font, + const SkColor& text_color, + const SkColor& halo_color_in, + int x, int y, int w, int h, + int flags) { + // Some callers will have semitransparent halo colors, which we don't handle + // (since the resulting image can have 1-bit transparency only). + SkColor halo_color = halo_color_in | 0xFF000000; + + // Create a temporary buffer filled with the halo color. It must leave room + // for the 1-pixel border around the text. + Canvas text_canvas(w + 2, h + 2, true); + SkPaint bkgnd_paint; + bkgnd_paint.setColor(halo_color); + text_canvas.FillRectInt(0, 0, w + 2, h + 2, bkgnd_paint); + + // Draw the text into the temporary buffer. This will have correct + // ClearType since the background color is the same as the halo color. + text_canvas.DrawStringInt(text, font, text_color, 1, 1, w, h, flags); + + // Windows will have cleared the alpha channel for the pixels it drew. Make it + // opaque. We have to do this first since pixelShouldGetHalo will check for + // 0 to see if a pixel has been modified to transparent, and black text that + // Windows draw will look transparent to it! + text_canvas.getTopPlatformDevice().makeOpaque(0, 0, w + 2, h + 2); + + uint32_t halo_premul = SkPreMultiplyColor(halo_color); + SkBitmap& text_bitmap = const_cast<SkBitmap&>( + text_canvas.getTopPlatformDevice().accessBitmap(true)); + for (int cur_y = 0; cur_y < h + 2; cur_y++) { + uint32_t* text_row = text_bitmap.getAddr32(0, cur_y); + for (int cur_x = 0; cur_x < w + 2; cur_x++) { + if (text_row[cur_x] == halo_premul) { + // This pixel was not touched by the text routines. See if it borders + // a touched pixel in any of the 4 directions (not diagonally). + if (!pixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul)) + text_row[cur_x] = 0; // Make transparent. + } else { + text_row[cur_x] |= 0xff << SK_A32_SHIFT; // Make opaque. + } + } + } + + // Draw the halo bitmap with blur. + drawBitmap(text_bitmap, SkIntToScalar(x - 1), SkIntToScalar(y - 1)); +} + +} // namespace gfx diff --git a/gfx/font.h b/gfx/font.h new file mode 100644 index 0000000..7395a4d --- /dev/null +++ b/gfx/font.h @@ -0,0 +1,287 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GFX_FONT_H_ +#define GFX_FONT_H_ + +#include "build/build_config.h" + +#include <string> + +#if defined(OS_WIN) +typedef struct HFONT__* HFONT; +#elif !defined(OS_MACOSX) +#include "third_party/skia/include/core/SkRefCnt.h" +class SkPaint; +class SkTypeface; +#endif + +#if defined(OS_WIN) +typedef struct HFONT__* NativeFont; +#elif defined(OS_MACOSX) +#ifdef __OBJC__ +@class NSFont; +#else +class NSFont; +#endif +typedef NSFont* NativeFont; +#else +typedef struct _PangoFontDescription PangoFontDescription; +class SkTypeface; +typedef SkTypeface* NativeFont; +#endif + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" + +namespace gfx { + +// Font provides a wrapper around an underlying font. Copy and assignment +// operators are explicitly allowed, and cheap. +class Font { + public: + // The following constants indicate the font style. + enum { + NORMAL = 0, + BOLD = 1, + ITALIC = 2, + UNDERLINED = 4, + }; + + // Creates a Font given font name (e.g. arial), font size (e.g. 12). + // Skia actually expects a family name and not a font name. + static Font CreateFont(const std::wstring& font_name, int font_size); + + ~Font() { } + + // Returns a new Font derived from the existing font. + // size_deta is the size to add to the current font. For example, a value + // of 5 results in a font 5 units bigger than this font. + Font DeriveFont(int size_delta) const { + return DeriveFont(size_delta, style()); + } + + // Returns a new Font derived from the existing font. + // size_delta is the size to add to the current font. See the single + // argument version of this method for an example. + // The style parameter specifies the new style for the font, and is a + // bitmask of the values: BOLD, ITALIC and UNDERLINED. + Font DeriveFont(int size_delta, int style) const; + + // Returns the number of vertical pixels needed to display characters from + // the specified font. This may include some leading, i.e. height may be + // greater than just ascent + descent. Specifically, the Windows and Mac + // implementations include leading and the Linux one does not. This may + // need to be revisited in the future. + int height() const; + + // Returns the baseline, or ascent, of the font. + int baseline() const; + + // Returns the average character width for the font. + int ave_char_width() const; + + // Returns the number of horizontal pixels needed to display the specified + // string. + int GetStringWidth(const std::wstring& text) const; + + // Returns the expected number of horizontal pixels needed to display + // the specified length of characters. + // Call GetStringWidth() to retrieve the actual number. + int GetExpectedTextWidth(int length) const; + + // Returns the style of the font. + int style() const; + + // Font Name. + // It is actually a font family name, because Skia expects a family name + // and not a font name. + const std::wstring& FontName() const; + + // Font Size. + int FontSize(); + + NativeFont nativeFont() const; + + // Creates a font with the default name and style. + Font(); + +#if defined(OS_WIN) + // Creates a Font from the specified HFONT. The supplied HFONT is effectively + // copied. + static Font CreateFont(HFONT hfont); + + // Returns the handle to the underlying HFONT. This is used by gfx::Canvas to + // draw text. + HFONT hfont() const { return font_ref_->hfont(); } + + // Dialog units to pixels conversion. + // See http://support.microsoft.com/kb/145994 for details. + int horizontal_dlus_to_pixels(int dlus) { + return dlus * font_ref_->dlu_base_x() / 4; + } + int vertical_dlus_to_pixels(int dlus) { + return dlus * font_ref_->height() / 8; + } + + // Callback that returns the minimum height that should be used for + // gfx::Fonts. Optional. If not specified, the minimum font size is 0. + typedef int (*GetMinimumFontSizeCallback)(); + static GetMinimumFontSizeCallback get_minimum_font_size_callback; + + // Callback that adjusts a LOGFONT to meet suitability requirements of the + // embedding application. Optional. If not specified, no adjustments are + // performed other than clamping to a minimum font height if + // |get_minimum_font_size_callback| is specified. + typedef void (*AdjustFontCallback)(LOGFONT* lf); + static AdjustFontCallback adjust_font_callback; + +#elif !defined(OS_MACOSX) + static Font CreateFont(PangoFontDescription* desc); + // We need a copy constructor and assignment operator to deal with + // the Skia reference counting. + Font(const Font& other); + Font& operator=(const Font& other); + // Setup a Skia context to use the current typeface + void PaintSetup(SkPaint* paint) const; + + // Converts |gfx_font| to a new pango font. Free the returned font with + // pango_font_description_free(). + static PangoFontDescription* PangoFontFromGfxFont(const gfx::Font& gfx_font); + + // Position as an offset from the height of the drawn text, used to draw + // an underline. This is a negative number, so the underline would be + // drawn at y + height + underline_position; + double underline_position() const; + // The thickness to draw the underline. + double underline_thickness() const; +#endif + + private: + +#if defined(OS_WIN) + // Chrome text drawing bottoms out in the Windows GDI functions that take an + // HFONT (an opaque handle into Windows). To avoid lots of GDI object + // allocation and destruction, Font indirectly refers to the HFONT by way of + // an HFontRef. That is, every Font has an HFontRef, which has an HFONT. + // + // HFontRef is reference counted. Upon deletion, it deletes the HFONT. + // By making HFontRef maintain the reference to the HFONT, multiple + // HFontRefs can share the same HFONT, and Font can provide value semantics. + class HFontRef : public base::RefCounted<HFontRef> { + public: + // This constructor takes control of the HFONT, and will delete it when + // the HFontRef is deleted. + HFontRef(HFONT hfont, + int height, + int baseline, + int ave_char_width, + int style, + int dlu_base_x); + + // Accessors + HFONT hfont() const { return hfont_; } + int height() const { return height_; } + int baseline() const { return baseline_; } + int ave_char_width() const { return ave_char_width_; } + int style() const { return style_; } + int dlu_base_x() const { return dlu_base_x_; } + const std::wstring& font_name() const { return font_name_; } + + private: + friend class base::RefCounted<HFontRef>; + + ~HFontRef(); + + const HFONT hfont_; + const int height_; + const int baseline_; + const int ave_char_width_; + const int style_; + // Constants used in converting dialog units to pixels. + const int dlu_base_x_; + std::wstring font_name_; + + DISALLOW_COPY_AND_ASSIGN(HFontRef); + }; + + // Returns the base font ref. This should ONLY be invoked on the + // UI thread. + static HFontRef* GetBaseFontRef(); + + // Creates and returns a new HFONTRef from the specified HFONT. + static HFontRef* CreateHFontRef(HFONT font); + + explicit Font(HFontRef* font_ref) : font_ref_(font_ref) { } + + // Reference to the base font all fonts are derived from. + static HFontRef* base_font_ref_; + + // Indirect reference to the HFontRef, which references the underlying HFONT. + scoped_refptr<HFontRef> font_ref_; +#elif !defined(OS_MACOSX) + explicit Font(SkTypeface* typeface, const std::wstring& name, + int size, int style); + // Calculate and cache the font metrics. + void calculateMetrics(); + // Make |this| a copy of |other|. + void CopyFont(const Font& other); + + // The default font, used for the default constructor. + static Font* default_font_; + + // Return the scale factor for fonts that account for DPI. We clamp the + // max DPI to prevent large fonts from overflowing UI elements. + static float GetPangoScaleFactor(); + + // The average width of a character, initialized and cached if needed. + double avg_width() const; + + // Potentially slow call to get pango metrics (avg width, underline info). + void InitPangoMetrics(); + + // These two both point to the same SkTypeface. We use the SkAutoUnref to + // handle the reference counting, but without @typeface_ we would have to + // cast the SkRefCnt from @typeface_helper_ every time. + scoped_ptr<SkAutoUnref> typeface_helper_; + SkTypeface *typeface_; + + // Additional information about the face + // Skia actually expects a family name and not a font name. + std::wstring font_family_; + int font_size_; + int style_; + + // Cached metrics, generated at construction + int height_; + int ascent_; + + // The pango metrics are much more expensive so we wait until we need them + // to compute them. + bool pango_metrics_inited_; + double avg_width_; + double underline_position_; + double underline_thickness_; +#else // OS_MACOSX + explicit Font(const std::wstring& font_name, int font_size, int style); + + // Calculate and cache the font metrics. + void calculateMetrics(); + + std::wstring font_name_; + int font_size_; + int style_; + + // Cached metrics, generated at construction + int height_; + int ascent_; + int avg_width_; +#endif + +}; + +} // namespace gfx + +#endif // GFX_FONT_H_ diff --git a/gfx/font_gtk.cc b/gfx/font_gtk.cc new file mode 100644 index 0000000..7dee3a1 --- /dev/null +++ b/gfx/font_gtk.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include <algorithm> +#include <fontconfig/fontconfig.h> +#include <gtk/gtk.h> + +#include "base/logging.h" +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" + +namespace gfx { + +Font* Font::default_font_ = NULL; + +// Find the best match font for |family_name| in the same way as Skia +// to make sure CreateFont() successfully creates a default font. In +// Skia, it only checks the best match font. If it failed to find +// one, SkTypeface will be NULL for that font family. It eventually +// causes a segfault. For example, family_name = "Sans" and system +// may have various fonts. The first font family in FcPattern will be +// "DejaVu Sans" but a font family returned by FcFontMatch will be "VL +// PGothic". In this case, SkTypeface for "Sans" returns NULL even if +// the system has a font for "Sans" font family. See FontMatch() in +// skia/ports/SkFontHost_fontconfig.cpp for more detail. +static std::wstring FindBestMatchFontFamilyName(const char* family_name) { + FcPattern* pattern = FcPatternCreate(); + FcValue fcvalue; + fcvalue.type = FcTypeString; + char* family_name_copy = strdup(family_name); + fcvalue.u.s = reinterpret_cast<FcChar8*>(family_name_copy); + FcPatternAdd(pattern, FC_FAMILY, fcvalue, 0); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + FcResult result; + FcPattern* match = FcFontMatch(0, pattern, &result); + DCHECK(match) << "Could not find font: " << family_name; + FcChar8* match_family; + FcPatternGetString(match, FC_FAMILY, 0, &match_family); + + std::wstring font_family = UTF8ToWide( + reinterpret_cast<char*>(match_family)); + FcPatternDestroy(match); + FcPatternDestroy(pattern); + free(family_name_copy); + return font_family; +} + +// Pango scales font sizes. This returns the scale factor. See +// pango_cairo_context_set_resolution for details. +// NOTE: this isn't entirely accurate, in that Pango also consults the +// FC_PIXEL_SIZE first (see get_font_size in pangocairo-fcfont), but this +// seems to give us the same sizes as used by Pango for all our fonts in both +// English and Thai. +float Font::GetPangoScaleFactor() { + static float scale_factor = 0; + static bool determined_scale = false; + if (!determined_scale) { + PangoContext* context = gdk_pango_context_get(); + scale_factor = pango_cairo_context_get_resolution(context); + // Until we switch to vector graphics, force the max DPI to 96.0. + scale_factor = std::min(scale_factor, 96.f); + g_object_unref(context); + if (scale_factor <= 0) + scale_factor = 1; + else + scale_factor /= 72.0; + determined_scale = true; + } + return scale_factor; +} + +// static +Font Font::CreateFont(PangoFontDescription* desc) { + gint size = pango_font_description_get_size(desc); + const char* family_name = pango_font_description_get_family(desc); + + // Find best match font for |family_name| to make sure we can get + // a SkTypeface for the default font. + // TODO(agl): remove this. + std::wstring font_family = FindBestMatchFontFamilyName(family_name); + + Font font = CreateFont(font_family, size / PANGO_SCALE); + int style = 0; + if (pango_font_description_get_weight(desc) == PANGO_WEIGHT_BOLD) { + // TODO(davemoore) What should we do about other weights? We currently + // only support BOLD. + style |= BOLD; + } + if (pango_font_description_get_style(desc) == PANGO_STYLE_ITALIC) { + // TODO(davemoore) What about PANGO_STYLE_OBLIQUE? + style |= ITALIC; + } + if (style != 0) { + font = font.DeriveFont(0, style); + } + return Font(font); +} + +// Get the default gtk system font (name and size). +Font::Font() { + if (default_font_ == NULL) { + GtkSettings* settings = gtk_settings_get_default(); + + gchar* font_name = NULL; + g_object_get(settings, "gtk-font-name", &font_name, NULL); + + // Temporary CHECK for helping track down + // http://code.google.com/p/chromium/issues/detail?id=12530 + CHECK(font_name) << " Unable to get gtk-font-name for default font."; + + PangoFontDescription* desc = + pango_font_description_from_string(font_name); + default_font_ = new Font(CreateFont(desc)); + pango_font_description_free(desc); + g_free(font_name); + + DCHECK(default_font_); + } + + CopyFont(*default_font_); +} + +// static +PangoFontDescription* Font::PangoFontFromGfxFont( + const gfx::Font& gfx_font) { + gfx::Font font = gfx_font; // Copy so we can call non-const methods. + PangoFontDescription* pfd = pango_font_description_new(); + pango_font_description_set_family(pfd, WideToUTF8(font.FontName()).c_str()); + // Set the absolute size to avoid overflowing UI elements. + pango_font_description_set_absolute_size(pfd, + font.FontSize() * PANGO_SCALE * Font::GetPangoScaleFactor()); + + switch (font.style()) { + case gfx::Font::NORMAL: + // Nothing to do, should already be PANGO_STYLE_NORMAL. + break; + case gfx::Font::BOLD: + pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD); + break; + case gfx::Font::ITALIC: + pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC); + break; + case gfx::Font::UNDERLINED: + // TODO(deanm): How to do underlined? Where do we use it? Probably have + // to paint it ourselves, see pango_font_metrics_get_underline_position. + break; + } + + return pfd; +} + +} // namespace gfx diff --git a/gfx/font_mac.mm b/gfx/font_mac.mm new file mode 100644 index 0000000..fcc85833 --- /dev/null +++ b/gfx/font_mac.mm @@ -0,0 +1,93 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include <Cocoa/Cocoa.h> + +#include "base/logging.h" +#include "base/scoped_nsobject.h" +#include "base/sys_string_conversions.h" + +namespace gfx { + +// static +Font Font::CreateFont(const std::wstring& font_name, int font_size) { + return Font(font_name, font_size, NORMAL); +} + +Font::Font(const std::wstring& font_name, int font_size, int style) + : font_name_(font_name), + font_size_(font_size), + style_(style) { + calculateMetrics(); +} + +Font::Font() + : font_size_([NSFont systemFontSize]), + style_(NORMAL) { + NSFont* system_font = [NSFont systemFontOfSize:font_size_]; + font_name_ = base::SysNSStringToWide([system_font fontName]); + calculateMetrics(); +} + +void Font::calculateMetrics() { + NSFont* font = nativeFont(); + scoped_nsobject<NSLayoutManager> layout_manager( + [[NSLayoutManager alloc] init]); + height_ = [layout_manager defaultLineHeightForFont:font]; + ascent_ = [font ascender]; + avg_width_ = [font boundingRectForGlyph:[font glyphWithName:@"x"]].size.width; +} + +Font Font::DeriveFont(int size_delta, int style) const { + return Font(font_name_, font_size_ + size_delta, style); +} + +int Font::height() const { + return height_; +} + +int Font::baseline() const { + return ascent_; +} + +int Font::ave_char_width() const { + return avg_width_; +} + +int Font::GetStringWidth(const std::wstring& text) const { + NSFont* font = nativeFont(); + NSString* ns_string = base::SysWideToNSString(text); + NSDictionary* attributes = + [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; + NSSize string_size = [ns_string sizeWithAttributes:attributes]; + return string_size.width; +} + +int Font::GetExpectedTextWidth(int length) const { + return length * avg_width_; +} + +int Font::style() const { + return style_; +} + +const std::wstring& Font::FontName() const { + return font_name_; +} + +int Font::FontSize() { + return font_size_; +} + +NativeFont Font::nativeFont() const { + // TODO(pinkerton): apply |style_| to font. http://crbug.com/34667 + // We could cache this, but then we'd have to conditionally change the + // dtor just for MacOS. Not sure if we want to/need to do that. + return [NSFont fontWithName:base::SysWideToNSString(font_name_) + size:font_size_]; +} + +} // namespace gfx diff --git a/gfx/font_skia.cc b/gfx/font_skia.cc new file mode 100644 index 0000000..fffc53e --- /dev/null +++ b/gfx/font_skia.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include <gdk/gdk.h> +#include <map> +#include <pango/pango.h> + +#include "base/logging.h" +#include "base/string_piece.h" +#include "base/sys_string_conversions.h" +#include "gfx/canvas.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/core/SkPaint.h" + +namespace { + +// The font family name which is used when a user's application font for +// GNOME/KDE is a non-scalable one. The name should be listed in the +// IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp. +const char* kFallbackFontFamilyName = "sans"; + +// Retrieves the pango metrics for a pango font description. Caches the metrics +// and never frees them. The metrics objects are relatively small and +// very expensive to look up. +static PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) { + static std::map<int, PangoFontMetrics*>* desc_to_metrics = NULL; + static PangoContext* context = NULL; + + if (!context) { + context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); + pango_context_set_language(context, pango_language_get_default()); + } + + if (!desc_to_metrics) { + desc_to_metrics = new std::map<int, PangoFontMetrics*>(); + } + + int desc_hash = pango_font_description_hash(desc); + std::map<int, PangoFontMetrics*>::iterator i = + desc_to_metrics->find(desc_hash); + + if (i == desc_to_metrics->end()) { + PangoFontMetrics* metrics = pango_context_get_metrics(context, desc, NULL); + (*desc_to_metrics)[desc_hash] = metrics; + return metrics; + } else { + return i->second; + } +} + +} // namespace + +namespace gfx { + +Font::Font(const Font& other) { + CopyFont(other); +} + +Font& Font::operator=(const Font& other) { + CopyFont(other); + return *this; +} + +Font::Font(SkTypeface* tf, const std::wstring& font_family, int font_size, + int style) + : typeface_helper_(new SkAutoUnref(tf)), + typeface_(tf), + font_family_(font_family), + font_size_(font_size), + style_(style), + pango_metrics_inited_(false), + avg_width_(0.0), + underline_position_(0.0), + underline_thickness_(0.0) { + tf->ref(); + calculateMetrics(); +} + +void Font::calculateMetrics() { + SkPaint paint; + SkPaint::FontMetrics metrics; + PaintSetup(&paint); + paint.getFontMetrics(&metrics); + + ascent_ = SkScalarCeil(-metrics.fAscent); + height_ = ascent_ + SkScalarCeil(metrics.fDescent); + +} + +void Font::CopyFont(const Font& other) { + typeface_helper_.reset(new SkAutoUnref(other.typeface_)); + typeface_ = other.typeface_; + typeface_->ref(); + font_family_ = other.font_family_; + font_size_ = other.font_size_; + style_ = other.style_; + height_ = other.height_; + ascent_ = other.ascent_; + pango_metrics_inited_ = other.pango_metrics_inited_; + avg_width_ = other.avg_width_; + underline_position_ = other.underline_position_; + underline_thickness_ = other.underline_thickness_; +} + +int Font::height() const { + return height_; +} + +int Font::baseline() const { + return ascent_; +} + +int Font::ave_char_width() const { + return SkScalarRound(avg_width()); +} + +Font Font::CreateFont(const std::wstring& font_family, int font_size) { + DCHECK_GT(font_size, 0); + std::wstring fallback; + + SkTypeface* tf = SkTypeface::CreateFromName( + base::SysWideToUTF8(font_family).c_str(), SkTypeface::kNormal); + if (!tf) { + // A non-scalable font such as .pcf is specified. Falls back to a default + // scalable font. + tf = SkTypeface::CreateFromName( + kFallbackFontFamilyName, SkTypeface::kNormal); + CHECK(tf) << "Could not find any font: " + << base::SysWideToUTF8(font_family) + << ", " << kFallbackFontFamilyName; + fallback = base::SysUTF8ToWide(kFallbackFontFamilyName); + } + SkAutoUnref tf_helper(tf); + + return Font( + tf, fallback.empty() ? font_family : fallback, font_size, NORMAL); +} + +Font Font::DeriveFont(int size_delta, int style) const { + // If the delta is negative, if must not push the size below 1 + if (size_delta < 0) { + DCHECK_LT(-size_delta, font_size_); + } + + if (style == style_) { + // Fast path, we just use the same typeface at a different size + return Font(typeface_, font_family_, font_size_ + size_delta, style_); + } + + // If the style has changed we may need to load a new face + int skstyle = SkTypeface::kNormal; + if (BOLD & style) + skstyle |= SkTypeface::kBold; + if (ITALIC & style) + skstyle |= SkTypeface::kItalic; + + SkTypeface* tf = SkTypeface::CreateFromName( + base::SysWideToUTF8(font_family_).c_str(), + static_cast<SkTypeface::Style>(skstyle)); + SkAutoUnref tf_helper(tf); + + return Font(tf, font_family_, font_size_ + size_delta, style); +} + +void Font::PaintSetup(SkPaint* paint) const { + paint->setAntiAlias(false); + paint->setSubpixelText(false); + paint->setTextSize(SkFloatToScalar(font_size_ * Font::GetPangoScaleFactor())); + paint->setTypeface(typeface_); + paint->setFakeBoldText((BOLD & style_) && !typeface_->isBold()); + paint->setTextSkewX((ITALIC & style_) && !typeface_->isItalic() ? + -SK_Scalar1/4 : 0); +} + +int Font::GetStringWidth(const std::wstring& text) const { + int width = 0, height = 0; + + Canvas::SizeStringInt(text, *this, &width, &height, 0); + return width; +} + +void Font::InitPangoMetrics() { + if (!pango_metrics_inited_) { + pango_metrics_inited_ = true; + PangoFontDescription* pango_desc = PangoFontFromGfxFont(*this); + PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc); + + underline_position_ = + pango_font_metrics_get_underline_position(pango_metrics); + underline_position_ /= PANGO_SCALE; + + // todo(davemoore) Come up with a better solution. + // This is a hack, but without doing this the underlines + // we get end up fuzzy. So we align to the midpoint of a pixel. + underline_position_ /= 2; + + underline_thickness_ = + pango_font_metrics_get_underline_thickness(pango_metrics); + underline_thickness_ /= PANGO_SCALE; + + // First get the pango based width + double pango_width = + pango_font_metrics_get_approximate_char_width(pango_metrics); + pango_width /= PANGO_SCALE; + + // Yes, this is how Microsoft recommends calculating the dialog unit + // conversions. + int text_width = GetStringWidth( + L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + double dialog_units = (text_width / 26 + 1) / 2; + avg_width_ = std::min(pango_width, dialog_units); + pango_font_description_free(pango_desc); + } +} + +double Font::avg_width() const { + const_cast<Font*>(this)->InitPangoMetrics(); + return avg_width_; +} + +double Font::underline_position() const { + const_cast<Font*>(this)->InitPangoMetrics(); + return underline_position_; +} + +double Font::underline_thickness() const { + const_cast<Font*>(this)->InitPangoMetrics(); + return underline_thickness_; +} + +int Font::GetExpectedTextWidth(int length) const { + double char_width = const_cast<Font*>(this)->avg_width(); + return round(static_cast<float>(length) * char_width); +} + +int Font::style() const { + return style_; +} + +const std::wstring& Font::FontName() const { + return font_family_; +} + +int Font::FontSize() { + return font_size_; +} + +NativeFont Font::nativeFont() const { + return typeface_; +} + +} // namespace gfx diff --git a/gfx/font_unittest.cc b/gfx/font_unittest.cc new file mode 100644 index 0000000..d2310f8 --- /dev/null +++ b/gfx/font_unittest.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using gfx::Font; + +class FontTest : public testing::Test { +}; + +TEST_F(FontTest, LoadArial) { + Font cf(Font::CreateFont(L"Arial", 16)); + ASSERT_TRUE(cf.nativeFont()); + ASSERT_EQ(cf.style(), Font::NORMAL); + ASSERT_EQ(cf.FontSize(), 16); + ASSERT_EQ(cf.FontName(), L"Arial"); +} + +TEST_F(FontTest, LoadArialBold) { + Font cf(Font::CreateFont(L"Arial", 16)); + Font bold(cf.DeriveFont(0, Font::BOLD)); + ASSERT_TRUE(bold.nativeFont()); + ASSERT_EQ(bold.style(), Font::BOLD); +} + +TEST_F(FontTest, Ascent) { + Font cf(Font::CreateFont(L"Arial", 16)); + ASSERT_GT(cf.baseline(), 2); + ASSERT_LE(cf.baseline(), 22); +} + +TEST_F(FontTest, Height) { + Font cf(Font::CreateFont(L"Arial", 16)); + ASSERT_GE(cf.height(), 16); + // TODO(akalin): Figure out why height is so large on Linux. + ASSERT_LE(cf.height(), 26); +} + +TEST_F(FontTest, AvgWidths) { + Font cf(Font::CreateFont(L"Arial", 16)); + ASSERT_EQ(cf.GetExpectedTextWidth(0), 0); + ASSERT_GT(cf.GetExpectedTextWidth(1), cf.GetExpectedTextWidth(0)); + ASSERT_GT(cf.GetExpectedTextWidth(2), cf.GetExpectedTextWidth(1)); + ASSERT_GT(cf.GetExpectedTextWidth(3), cf.GetExpectedTextWidth(2)); +} + +TEST_F(FontTest, Widths) { + Font cf(Font::CreateFont(L"Arial", 16)); + ASSERT_EQ(cf.GetStringWidth(L""), 0); + ASSERT_GT(cf.GetStringWidth(L"a"), cf.GetStringWidth(L"")); + ASSERT_GT(cf.GetStringWidth(L"ab"), cf.GetStringWidth(L"a")); + ASSERT_GT(cf.GetStringWidth(L"abc"), cf.GetStringWidth(L"ab")); +} + +#if defined(OS_WIN) +// TODO(beng): re-enable evening of 3/22. +TEST_F(FontTest, DISABLED_DeriveFontResizesIfSizeTooSmall) { + // This creates font of height -8. + Font cf(Font::CreateFont(L"Arial", 6)); + Font derived_font = cf.DeriveFont(-4); + LOGFONT font_info; + GetObject(derived_font.hfont(), sizeof(LOGFONT), &font_info); + EXPECT_EQ(-5, font_info.lfHeight); +} + +TEST_F(FontTest, DISABLED_DeriveFontKeepsOriginalSizeIfHeightOk) { + // This creates font of height -8. + Font cf(Font::CreateFont(L"Arial", 6)); + Font derived_font = cf.DeriveFont(-2); + LOGFONT font_info; + GetObject(derived_font.hfont(), sizeof(LOGFONT), &font_info); + EXPECT_EQ(-6, font_info.lfHeight); +} +#endif +} // anonymous namespace diff --git a/gfx/font_win.cc b/gfx/font_win.cc new file mode 100644 index 0000000..b55057c --- /dev/null +++ b/gfx/font_win.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include <windows.h> +#include <math.h> + +#include <algorithm> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/win_util.h" + +namespace gfx { + +// static +Font::HFontRef* Font::base_font_ref_; + +// static +Font::AdjustFontCallback Font::adjust_font_callback = NULL; +Font::GetMinimumFontSizeCallback Font::get_minimum_font_size_callback = NULL; + +// If the tmWeight field of a TEXTMETRIC structure has a value >= this, the +// font is bold. +static const int kTextMetricWeightBold = 700; + +// Returns either minimum font allowed for a current locale or +// lf_height + size_delta value. +static int AdjustFontSize(int lf_height, int size_delta) { + if (lf_height < 0) { + lf_height -= size_delta; + } else { + lf_height += size_delta; + } + int min_font_size = 0; + if (Font::get_minimum_font_size_callback) + min_font_size = Font::get_minimum_font_size_callback(); + // Make sure lf_height is not smaller than allowed min font size for current + // locale. + if (abs(lf_height) < min_font_size) { + return lf_height < 0 ? -min_font_size : min_font_size; + } else { + return lf_height; + } +} + +// +// Font +// + +Font::Font() + : font_ref_(GetBaseFontRef()) { +} + +int Font::height() const { + return font_ref_->height(); +} + +int Font::baseline() const { + return font_ref_->baseline(); +} + +int Font::ave_char_width() const { + return font_ref_->ave_char_width(); +} + +int Font::GetExpectedTextWidth(int length) const { + return length * std::min(font_ref_->dlu_base_x(), ave_char_width()); +} + +int Font::style() const { + return font_ref_->style(); +} + +NativeFont Font::nativeFont() const { + return hfont(); +} + +// static +Font Font::CreateFont(HFONT font) { + DCHECK(font); + LOGFONT font_info; + GetObject(font, sizeof(LOGFONT), &font_info); + return Font(CreateHFontRef(CreateFontIndirect(&font_info))); +} + +Font Font::CreateFont(const std::wstring& font_name, int font_size) { + HDC hdc = GetDC(NULL); + long lf_height = -MulDiv(font_size, GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(NULL, hdc); + HFONT hf = ::CreateFont(lf_height, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + font_name.c_str()); + return Font::CreateFont(hf); +} + +// static +Font::HFontRef* Font::GetBaseFontRef() { + if (base_font_ref_ == NULL) { + NONCLIENTMETRICS metrics; + win_util::GetNonClientMetrics(&metrics); + + if (adjust_font_callback) + adjust_font_callback(&metrics.lfMessageFont); + metrics.lfMessageFont.lfHeight = + AdjustFontSize(metrics.lfMessageFont.lfHeight, 0); + HFONT font = CreateFontIndirect(&metrics.lfMessageFont); + DLOG_ASSERT(font); + base_font_ref_ = Font::CreateHFontRef(font); + // base_font_ref_ is global, up the ref count so it's never deleted. + base_font_ref_->AddRef(); + } + return base_font_ref_; +} + +const std::wstring& Font::FontName() const { + return font_ref_->font_name(); +} + +int Font::FontSize() { + LOGFONT font_info; + GetObject(hfont(), sizeof(LOGFONT), &font_info); + long lf_height = font_info.lfHeight; + HDC hdc = GetDC(NULL); + int device_caps = GetDeviceCaps(hdc, LOGPIXELSY); + int font_size = 0; + if (device_caps != 0) { + float font_size_float = -static_cast<float>(lf_height)*72/device_caps; + font_size = static_cast<int>(::ceil(font_size_float - 0.5)); + } + ReleaseDC(NULL, hdc); + return font_size; +} + +Font::HFontRef::HFontRef(HFONT hfont, + int height, + int baseline, + int ave_char_width, + int style, + int dlu_base_x) + : hfont_(hfont), + height_(height), + baseline_(baseline), + ave_char_width_(ave_char_width), + style_(style), + dlu_base_x_(dlu_base_x) { + DLOG_ASSERT(hfont); + + LOGFONT font_info; + GetObject(hfont_, sizeof(LOGFONT), &font_info); + font_name_ = std::wstring(font_info.lfFaceName); +} + +Font::HFontRef::~HFontRef() { + DeleteObject(hfont_); +} + +Font Font::DeriveFont(int size_delta, int style) const { + LOGFONT font_info; + GetObject(hfont(), sizeof(LOGFONT), &font_info); + font_info.lfHeight = AdjustFontSize(font_info.lfHeight, size_delta); + font_info.lfUnderline = ((style & UNDERLINED) == UNDERLINED); + font_info.lfItalic = ((style & ITALIC) == ITALIC); + font_info.lfWeight = (style & BOLD) ? FW_BOLD : FW_NORMAL; + + HFONT hfont = CreateFontIndirect(&font_info); + return Font(CreateHFontRef(hfont)); +} + +int Font::GetStringWidth(const std::wstring& text) const { + int width = 0; + HDC dc = GetDC(NULL); + HFONT previous_font = static_cast<HFONT>(SelectObject(dc, hfont())); + SIZE size; + if (GetTextExtentPoint32(dc, text.c_str(), static_cast<int>(text.size()), + &size)) { + width = size.cx; + } else { + width = 0; + } + SelectObject(dc, previous_font); + ReleaseDC(NULL, dc); + return width; +} + +Font::HFontRef* Font::CreateHFontRef(HFONT font) { + TEXTMETRIC font_metrics; + HDC screen_dc = GetDC(NULL); + HFONT previous_font = static_cast<HFONT>(SelectObject(screen_dc, font)); + int last_map_mode = SetMapMode(screen_dc, MM_TEXT); + GetTextMetrics(screen_dc, &font_metrics); + // Yes, this is how Microsoft recommends calculating the dialog unit + // conversions. + SIZE ave_text_size; + GetTextExtentPoint32(screen_dc, + L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + 52, &ave_text_size); + const int dlu_base_x = (ave_text_size.cx / 26 + 1) / 2; + // To avoid the DC referencing font_handle_, select the previous font. + SelectObject(screen_dc, previous_font); + SetMapMode(screen_dc, last_map_mode); + ReleaseDC(NULL, screen_dc); + + const int height = std::max(1, static_cast<int>(font_metrics.tmHeight)); + const int baseline = std::max(1, static_cast<int>(font_metrics.tmAscent)); + const int ave_char_width = + std::max(1, static_cast<int>(font_metrics.tmAveCharWidth)); + int style = 0; + if (font_metrics.tmItalic) { + style |= Font::ITALIC; + } + if (font_metrics.tmUnderlined) { + style |= Font::UNDERLINED; + } + if (font_metrics.tmWeight >= kTextMetricWeightBold) { + style |= Font::BOLD; + } + + return new HFontRef(font, height, baseline, ave_char_width, style, + dlu_base_x); +} + +} // namespace gfx diff --git a/gfx/gfx.gyp b/gfx/gfx.gyp index 8a33525..4b7d612 100644 --- a/gfx/gfx.gyp +++ b/gfx/gfx.gyp @@ -20,6 +20,7 @@ 'codec/jpeg_codec_unittest.cc', 'codec/png_codec_unittest.cc', 'color_utils_unittest.cc', + 'font_unittest.cc', 'insets_unittest.cc', 'rect_unittest.cc', 'run_all_unittests.cc', @@ -61,6 +62,11 @@ 'sources': [ 'blit.cc', 'blit.h', + 'canvas.cc', + 'canvas.h', + 'canvas_linux.cc', + 'canvas_mac.mm', + 'canvas_win.cc', 'codec/jpeg_codec.cc', 'codec/jpeg_codec.h', 'codec/png_codec.cc', @@ -68,6 +74,11 @@ 'color_utils.cc', 'color_utils.h', 'favicon_size.h', + 'font.h', + 'font_gtk.cc', + 'font_mac.mm', + 'font_skia.cc', + 'font_win.cc', 'gfx_paths.cc', 'gfx_paths.h', 'insets.cc', @@ -104,6 +115,11 @@ ], }], ['OS=="linux" or OS=="freebsd" or OS=="openbsd"', { + 'dependencies': [ + # font_gtk.cc uses fontconfig. + # TODO(evanm): I think this is wrong; it should just use GTK. + '../build/linux/system.gyp:fontconfig', + ], 'sources': [ 'gtk_native_view_id_manager.cc', 'gtk_native_view_id_manager.h', |