Merge branch 'dwrite-coverage-map' into 'master'

DWrite: Get glyph coverage using IDWriteGlyphRunAnalysis

See merge request cairo/cairo!602
This commit is contained in:
Emmanuele Bassi 2025-02-21 17:32:28 +00:00
commit bdd12408a7
2 changed files with 462 additions and 74 deletions

View file

@ -47,6 +47,7 @@
#include "cairo-scaled-font-subsets-private.h"
#include "cairo-dwrite.h"
#include <utility>
#include <float.h>
#include <wincodec.h>
@ -226,21 +227,6 @@ RefPtr<IDWriteRenderingParams> DWriteFactory::mDefaultRenderingParams;
RefPtr<ID2D1Factory> D2DFactory::mFactoryInstance;
RefPtr<ID2D1DCRenderTarget> D2DFactory::mRenderTarget;
static int
_quality_from_antialias_mode(cairo_antialias_t antialias)
{
switch (antialias) {
case CAIRO_ANTIALIAS_NONE:
return NONANTIALIASED_QUALITY;
case CAIRO_ANTIALIAS_FAST:
case CAIRO_ANTIALIAS_GRAY:
return ANTIALIASED_QUALITY;
default:
break;
}
return CLEARTYPE_QUALITY;
}
static RefPtr<IDWriteRenderingParams>
_create_rendering_params(IDWriteRenderingParams *params,
const cairo_font_options_t *options,
@ -1333,61 +1319,117 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s
return CAIRO_INT_STATUS_SUCCESS;
}
// Helper for OS versions up to Windows 8
static cairo_int_status_t
_cairo_dwrite_scaled_font_init_glyph_surface(cairo_dwrite_scaled_font_t *scaled_font,
cairo_scaled_glyph_t *scaled_glyph)
init_glyph_surface_fallback_a8 (cairo_dwrite_scaled_font_t *scaled_font,
cairo_scaled_glyph_t *scaled_glyph,
int width,
int height,
double x1,
double y1,
DWRITE_MATRIX *matrix,
DWRITE_GLYPH_RUN *run)
{
cairo_int_status_t status;
cairo_win32_surface_t *surface;
cairo_t *cr;
cairo_surface_t *image;
int width, height;
double x1, y1, x2, y2;
RefPtr<IWICBitmap> bitmap;
HRESULT hr;
x1 = _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.x);
y1 = _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.y);
x2 = _cairo_fixed_integer_ceil (scaled_glyph->bbox.p2.x);
y2 = _cairo_fixed_integer_ceil (scaled_glyph->bbox.p2.y);
width = (int)(x2 - x1);
height = (int)(y2 - y1);
hr = WICImagingFactory::Instance()->CreateBitmap ((UINT)width,
(UINT)height,
GUID_WICPixelFormat8bppAlpha,
WICBitmapCacheOnLoad,
&bitmap);
if (FAILED(hr))
return _cairo_dwrite_error (hr, "CreateBitmap failed");
D2D1_RENDER_TARGET_PROPERTIES properties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(
DXGI_FORMAT_A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED),
0,
0,
D2D1_RENDER_TARGET_USAGE_NONE,
D2D1_FEATURE_LEVEL_DEFAULT);
RefPtr<ID2D1RenderTarget> rt;
hr = D2DFactory::Instance()->CreateWicBitmapRenderTarget (bitmap, properties, &rt);
if (FAILED(hr))
return _cairo_dwrite_error (hr, "CreateWicBitmapRenderTarget failed");
RefPtr<ID2D1SolidColorBrush> brush;
hr = rt->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 1.0), &brush);
rt->BeginDraw();
rt->SetTransform(*(D2D1_MATRIX_3X2_F*)matrix);
rt->DrawGlyphRun({0, 0}, run, brush, scaled_font->measuring_mode);
hr = rt->EndDraw();
if (FAILED(hr))
return _cairo_dwrite_error (hr, "EndDraw failed");
// TODO: rt->Flush()?
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
if (cairo_surface_status (surface))
return CAIRO_INT_STATUS_UNSUPPORTED;
// Tell pixman that it should use component alpha blending when the surface is
// used as a source
pixman_image_set_component_alpha (((cairo_image_surface_t*)surface)->pixman_image, TRUE);
int stride = cairo_image_surface_get_stride (surface);
WICRect rect = { 0, 0, width, height };
bitmap->CopyPixels(&rect,
stride,
height * stride,
cairo_image_surface_get_data (surface));
cairo_surface_mark_dirty (surface);
cairo_surface_set_device_offset (surface, -x1, -y1);
_cairo_scaled_glyph_set_surface (scaled_glyph,
&scaled_font->base,
(cairo_image_surface_t*)surface);
return CAIRO_INT_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_dwrite_scaled_font_init_glyph_surface (cairo_dwrite_scaled_font_t *scaled_font,
cairo_scaled_glyph_t *scaled_glyph)
{
HRESULT hr;
double x1 = _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.x);
double y1 = _cairo_fixed_integer_floor (scaled_glyph->bbox.p1.y);
double x2 = _cairo_fixed_integer_ceil (scaled_glyph->bbox.p2.x);
double y2 = _cairo_fixed_integer_ceil (scaled_glyph->bbox.p2.y);
int width = (int)(x2 - x1);
int height = (int)(y2 - y1);
if (width <= 0)
width = 1;
if (height <= 0)
height = 1;
DWRITE_GLYPH_RUN run;
FLOAT advance = 0;
UINT16 index = (UINT16)_cairo_scaled_glyph_index (scaled_glyph);
DWRITE_GLYPH_OFFSET offset;
double x = -x1 + .25 * _cairo_scaled_glyph_xphase (scaled_glyph);
double y = -y1 + .25 * _cairo_scaled_glyph_yphase (scaled_glyph);
RECT area;
DWRITE_MATRIX matrix;
surface = (cairo_win32_surface_t *)
cairo_win32_surface_create_with_dib (CAIRO_FORMAT_RGB24, width, height);
cr = cairo_create (&surface->base);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_paint (cr);
status = (cairo_int_status_t)cairo_status (cr);
cairo_destroy(cr);
if (status)
goto FAIL;
/*
* We transform by the inverse transformation here. This will put our glyph
/* We transform by the inverse transformation here. This will put our glyph
* locations in the space in which we draw. Which is later transformed by
* the transformation matrix that we use. This will transform the
* glyph positions back to where they were before when drawing, but the
* glyph shapes will be transformed by the transformation matrix.
*/
* glyph shapes will be transformed by the transformation matrix. */
cairo_matrix_transform_point(&scaled_font->mat_inverse, &x, &y);
offset.advanceOffset = (FLOAT)x;
/* Y-axis is inverted */
offset.ascenderOffset = -(FLOAT)y;
offset.ascenderOffset = -(FLOAT)y; /* Y axis is inverted */
area.top = 0;
area.bottom = height;
area.left = 0;
area.right = width;
DWRITE_MATRIX matrix = _cairo_dwrite_matrix_from_matrix(&scaled_font->mat);
DWRITE_GLYPH_RUN run;
run.glyphCount = 1;
run.glyphAdvances = &advance;
run.fontFace = scaled_font->dwriteface;
@ -1397,29 +1439,378 @@ _cairo_dwrite_scaled_font_init_glyph_surface(cairo_dwrite_scaled_font_t *scaled_
run.isSideways = FALSE;
run.glyphOffsets = &offset;
matrix = _cairo_dwrite_matrix_from_matrix(&scaled_font->mat);
// Reduce the many Cairo antialias values to the
// three we actually care about: NONE, GRAY, RGB
enum {
ANTIALIAS_NONE,
ANTIALIAS_GRAY,
ANTIALIAS_CLEARTYPE,
} antialias = ANTIALIAS_CLEARTYPE;
status = _dwrite_draw_glyphs_to_gdi_surface_gdi (surface, &matrix, &run,
RGB(0,0,0), scaled_font, area);
if (status)
goto FAIL;
switch (scaled_font->antialias_mode) {
case CAIRO_ANTIALIAS_NONE:
antialias = ANTIALIAS_NONE;
break;
case CAIRO_ANTIALIAS_FAST:
case CAIRO_ANTIALIAS_GRAY:
antialias = ANTIALIAS_GRAY;
break;
case CAIRO_ANTIALIAS_DEFAULT:
case CAIRO_ANTIALIAS_GOOD:
case CAIRO_ANTIALIAS_BEST:
case CAIRO_ANTIALIAS_SUBPIXEL:
antialias = ANTIALIAS_CLEARTYPE;
break;
}
GdiFlush();
// Set DWrite rendering options
image = _cairo_compute_glyph_mask (&surface->base, _quality_from_antialias_mode(scaled_font->antialias_mode));
status = (cairo_int_status_t)image->status;
if (status)
goto FAIL;
DWRITE_RENDERING_MODE rendering_mode;
DWRITE_RENDERING_MODE1 rendering_mode1;
DWRITE_TEXT_ANTIALIAS_MODE text_antialias_mode;
DWRITE_TEXTURE_TYPE texture_type;
cairo_surface_set_device_offset (image, -x1, -y1);
switch (antialias) {
case ANTIALIAS_NONE:
rendering_mode1 = DWRITE_RENDERING_MODE1_ALIASED;
rendering_mode = DWRITE_RENDERING_MODE_ALIASED;
text_antialias_mode = DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE;
texture_type = DWRITE_TEXTURE_ALIASED_1x1;
break;
case ANTIALIAS_GRAY:
rendering_mode1 = DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC;
rendering_mode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
text_antialias_mode = DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE;
texture_type = DWRITE_TEXTURE_ALIASED_1x1;
break;
case ANTIALIAS_CLEARTYPE:
rendering_mode1 = DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC;
rendering_mode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
text_antialias_mode = DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE;
texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1;
break;
}
DWRITE_GRID_FIT_MODE grid_fit_mode = DWRITE_GRID_FIT_MODE_DEFAULT;
switch (cairo_font_options_get_hint_style (&scaled_font->base.options)) {
case CAIRO_HINT_STYLE_DEFAULT:
grid_fit_mode = DWRITE_GRID_FIT_MODE_DEFAULT;
case CAIRO_HINT_STYLE_NONE:
grid_fit_mode = DWRITE_GRID_FIT_MODE_DISABLED;
case CAIRO_HINT_STYLE_SLIGHT:
case CAIRO_HINT_STYLE_MEDIUM:
case CAIRO_HINT_STYLE_FULL:
grid_fit_mode = DWRITE_GRID_FIT_MODE_ENABLED;
}
cairo_subpixel_order_t subpixel_order;
subpixel_order = cairo_font_options_get_subpixel_order (&scaled_font->base.options);
bool subpixel_order_is_vertical = false;
if (antialias == ANTIALIAS_CLEARTYPE) {
switch (subpixel_order) {
case CAIRO_SUBPIXEL_ORDER_DEFAULT:
case CAIRO_SUBPIXEL_ORDER_RGB:
case CAIRO_SUBPIXEL_ORDER_BGR:
break;
case CAIRO_SUBPIXEL_ORDER_VRGB:
case CAIRO_SUBPIXEL_ORDER_VBGR:
subpixel_order_is_vertical = true;
break;
}
}
if (subpixel_order_is_vertical) {
// DirectWrite does not support vertical pixel geometries.
// As a workaround, apply a simmetry which swaps x and y
// coordinates, then re-swap while copying the back into
// the image surface
// swap the two rows
std::swap (matrix.m11, matrix.m21);
std::swap (matrix.m12, matrix.m22);
}
RefPtr<IDWriteGlyphRunAnalysis> dwrite_glyph_run_analysis;
if (DWriteFactory::Instance3()) {
hr = DWriteFactory::Instance3()->CreateGlyphRunAnalysis(&run,
&matrix,
rendering_mode1,
scaled_font->measuring_mode,
grid_fit_mode,
text_antialias_mode,
0, // baselineOriginX,
0, // baselineOriginY,
&dwrite_glyph_run_analysis);
}
else if (DWriteFactory::Instance2()) {
hr = DWriteFactory::Instance2()->CreateGlyphRunAnalysis(&run,
&matrix,
rendering_mode,
scaled_font->measuring_mode,
grid_fit_mode,
text_antialias_mode,
0, // baselineOriginX,
0, // baselineOriginY,
&dwrite_glyph_run_analysis);
}
else {
if (antialias == ANTIALIAS_GRAY) {
// IDWriteGlyphRunAnalysis supports gray-scale antialiasing only when
// created from IDWriteFactory2 or later. If we have IDWriteFactory
// only, fallback to rendering with Direct2D on A8 targets.
return init_glyph_surface_fallback_a8 (scaled_font, scaled_glyph,
width, height, x1, y1, &matrix, &run);
}
hr = DWriteFactory::Instance()->CreateGlyphRunAnalysis(&run, 1,
&matrix,
rendering_mode,
scaled_font->measuring_mode,
0, // baselineOriginX,
0, // baselineOriginY,
&dwrite_glyph_run_analysis);
}
if (FAILED(hr))
return CAIRO_INT_STATUS_UNSUPPORTED;
cairo_format_t surface_format = antialias == ANTIALIAS_NONE ? CAIRO_FORMAT_A1 :
antialias == ANTIALIAS_GRAY ? CAIRO_FORMAT_A8 :
CAIRO_FORMAT_ARGB32;
cairo_surface_t *surface = cairo_image_surface_create (surface_format, width, height);
if (cairo_surface_status (surface))
return CAIRO_INT_STATUS_UNSUPPORTED;
// Tell pixman that it should use component alpha blending when the surface is
// used as a source
pixman_image_set_component_alpha (((cairo_image_surface_t*)surface)->pixman_image, TRUE);
// That's probably not needed right after creation
cairo_surface_flush (surface);
unsigned char *surface_data = cairo_image_surface_get_data (surface);
int surface_stride = cairo_image_surface_get_stride (surface);
UINT32 dwrite_data_size;
BYTE *dwrite_data;
RECT dwrite_rect = {
0, // left
0, // top
width, // right
height // bottom
};
if (subpixel_order_is_vertical) {
std::swap (dwrite_rect.right,
dwrite_rect.bottom);
}
// Whether IDWriteGlyphRunAnalysis::CreateAlphaTexture() can render directly
// on the cairo image surface (because the pixel formats match) or a separate
// buffer is needed
bool render_is_direct;
switch (antialias) {
case ANTIALIAS_NONE:
{
dwrite_data_size = width * height; //TODO: check overflow
dwrite_data = (BYTE*) _cairo_malloc (dwrite_data_size);
if (!dwrite_data)
return CAIRO_INT_STATUS_UNSUPPORTED;
render_is_direct = false;
break;
}
case ANTIALIAS_GRAY:
{
// The image surface may have a stride that's bigger than width-
// account for that by passing stride as width to DWrite. Note:
// stride is a byte-size, but here pixel-size is exactly 1 byte.
dwrite_rect.right = cairo_image_surface_get_stride (surface);
dwrite_data_size = dwrite_rect.right * height; //TODO: check overflow
dwrite_data = static_cast<BYTE*>(surface_data);
render_is_direct = true;
break;
}
case ANTIALIAS_CLEARTYPE:
{
dwrite_data_size = 3 * width * height; //TODO: check overflow
dwrite_data = (BYTE*) _cairo_malloc (dwrite_data_size);
if (!dwrite_data)
return CAIRO_INT_STATUS_UNSUPPORTED;
render_is_direct = false;
break;
}
}
hr = dwrite_glyph_run_analysis->CreateAlphaTexture(texture_type, &dwrite_rect, dwrite_data, dwrite_data_size);
if (FAILED (hr))
return CAIRO_INT_STATUS_UNSUPPORTED;
// Most of the code here was copied and adapted from cairoft-font.c
switch (antialias) {
case ANTIALIAS_NONE:
{
unsigned char *src = static_cast<unsigned char*>(dwrite_data);
unsigned char *dst = surface_data;
for (int i = 0; i < height; i++) {
unsigned char *d = dst;
for (int j = 0; j < width / 8; j++) {
*d = (src[0] ? (1 << 0) : 0) +
(src[1] ? (1 << 1) : 0) +
(src[2] ? (1 << 2) : 0) +
(src[3] ? (1 << 3) : 0) +
(src[4] ? (1 << 4) : 0) +
(src[5] ? (1 << 5) : 0) +
(src[6] ? (1 << 6) : 0) +
(src[7] ? (1 << 7) : 0);
d++;
src += 8;
}
if (width % 8 != 0) {
*d = 0;
for (int k = 0; k < width % 8; k++) {
*d += (src[k] ? (1 << k) : 0);
}
d++;
src += (width % 8);
}
dst += surface_stride;
}
break;
}
case ANTIALIAS_GRAY:
{
// Nothing to do
break;
}
case ANTIALIAS_CLEARTYPE:
{
unsigned char *src = static_cast<unsigned char*>(dwrite_data);
unsigned char *dst = surface_data;
// The alpha channel is unused for component-alpha blending.
// Here we set the alpha channel anyway so that things work
// even in case of normal blending (but one likely gets some
// color fringing)
switch (subpixel_order) {
case CAIRO_SUBPIXEL_ORDER_DEFAULT:
case CAIRO_SUBPIXEL_ORDER_RGB:
{
for (int i = 0; i < height; i++) {
UINT32 *d = reinterpret_cast<UINT32*>(dst);
for (int j = 0; j < width; j++) {
// CAIRO_FORMAT_ARGB32 is BGRA on little-endian
*d = (src[2] << 0) +
(src[1] << 8) +
(src[0] << 16) +
(src[1] << 24); // unused
d++;
src += 3;
}
dst += surface_stride;
}
}
break;
case CAIRO_SUBPIXEL_ORDER_BGR:
{
for (int i = 0; i < height; i++) {
UINT32 *d = reinterpret_cast<UINT32*>(dst);
for (int j = 0; j < width; j++) {
*d = (src[0] << 0) +
(src[1] << 8) +
(src[2] << 16) +
(src[1] << 24); // unused
d++;
src += 3;
}
dst += surface_stride;
}
}
break;
case CAIRO_SUBPIXEL_ORDER_VRGB:
{
size_t p;
for (int i = 0; i < height; i++) {
UINT32 *d = reinterpret_cast<UINT32*>(dst);
p = i * 3;
for (int j = 0; j < width; j++) {
*d = (src[p + 2] << 0) +
(src[p + 1] << 8) +
(src[p + 0] << 16) +
(src[p + 1] << 24); // unused
d++;
p += 3 * height;
}
dst += surface_stride;
}
}
break;
case CAIRO_SUBPIXEL_ORDER_VBGR:
{
size_t p;
for (int i = 0; i < height; i++) {
UINT32 *d = reinterpret_cast<UINT32*>(dst);
p = i * 3;
for (int j = 0; j < width; j++) {
*d = (src[p + 0] << 0) +
(src[p + 1] << 8) +
(src[p + 2] << 16) +
(src[p + 1] << 24); // unused
d++;
p += 3 * height;
}
dst += surface_stride;
}
}
break;
}
break;
}
}
// That's probably not needed. cairoft-font.c doesn't do that
cairo_surface_mark_dirty (surface);
if (!render_is_direct) {
free (dwrite_data);
}
cairo_surface_set_device_offset (surface, -x1, -y1);
_cairo_scaled_glyph_set_surface (scaled_glyph,
&scaled_font->base,
(cairo_image_surface_t *) image);
&scaled_font->base,
(cairo_image_surface_t*)surface);
FAIL:
cairo_surface_destroy (&surface->base);
return status;
return CAIRO_INT_STATUS_SUCCESS;
}
static cairo_int_status_t
@ -1828,6 +2219,7 @@ _cairo_dwrite_show_glyphs_on_surface(void *surface,
cairo_dwrite_scaled_font_t *dwritesf = reinterpret_cast<cairo_dwrite_scaled_font_t*>(scaled_font);
cairo_win32_surface_t *dst = reinterpret_cast<cairo_win32_surface_t*>(surface);
cairo_int_status_t status;
/* We can only handle dwrite fonts */
if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_DWRITE)
return CAIRO_INT_STATUS_UNSUPPORTED;

View file

@ -191,10 +191,6 @@ cairo_bool_t
_cairo_win32_surface_get_extents (void *abstract_surface,
cairo_rectangle_int_t *rectangle);
cairo_surface_t *
_cairo_compute_glyph_mask (cairo_surface_t *surface,
int quality);
uint32_t
_cairo_win32_flags_for_dc (HDC dc, cairo_format_t format);