243 lines
7.7 KiB
C++
243 lines
7.7 KiB
C++
/*
|
||
* Copyright © 2026 Behdad Esfahbod
|
||
*
|
||
* This is part of HarfBuzz, a text shaping library.
|
||
*
|
||
* Permission is hereby granted, without written agreement and without
|
||
* license or royalty fees, to use, copy, modify, and distribute this
|
||
* software and its documentation for any purpose, provided that the
|
||
* above copyright notice and the following two paragraphs appear in
|
||
* all copies of this software.
|
||
*
|
||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||
* DAMAGE.
|
||
*
|
||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||
*
|
||
* Author(s): Behdad Esfahbod
|
||
*/
|
||
|
||
#include "hb-test.h"
|
||
#include "hb-raster.h"
|
||
#include <hb-ot.h>
|
||
|
||
|
||
/* ── Helpers ─────────────────────────────────────────────────────── */
|
||
|
||
/* Draw a closed rectangle by emitting raw draw callbacks. */
|
||
static void
|
||
draw_rect (hb_raster_draw_t *rdr,
|
||
float x0, float y0, float x1, float y1)
|
||
{
|
||
hb_draw_funcs_t *df = hb_raster_draw_get_funcs (rdr);
|
||
hb_draw_state_t st = HB_DRAW_STATE_DEFAULT;
|
||
|
||
hb_draw_move_to (df, rdr, &st, x0, y0);
|
||
hb_draw_line_to (df, rdr, &st, x1, y0);
|
||
hb_draw_line_to (df, rdr, &st, x1, y1);
|
||
hb_draw_line_to (df, rdr, &st, x0, y1);
|
||
hb_draw_close_path (df, rdr, &st); /* emits closing line_to(x0,y0) */
|
||
}
|
||
|
||
/* Read one A8 pixel value; returns 0 for out-of-bounds coordinates. */
|
||
static uint8_t
|
||
pixel_at (hb_raster_image_t *img, int x, int y)
|
||
{
|
||
hb_raster_extents_t ext;
|
||
hb_raster_image_get_extents (img, &ext);
|
||
|
||
int col = x - ext.x_origin;
|
||
int row = y - ext.y_origin;
|
||
|
||
if (col < 0 || row < 0 ||
|
||
(unsigned) col >= ext.width || (unsigned) row >= ext.height)
|
||
return 0;
|
||
|
||
return hb_raster_image_get_buffer (img)[row * ext.stride + col];
|
||
}
|
||
|
||
|
||
/* ── Test 1: rectangle geometry ──────────────────────────────────── */
|
||
|
||
static void
|
||
test_rectangle (void)
|
||
{
|
||
hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();
|
||
|
||
draw_rect (rdr, 2.f, 2.f, 30.f, 30.f);
|
||
|
||
hb_raster_image_t *img = hb_raster_draw_render (rdr);
|
||
g_assert_nonnull (img);
|
||
|
||
hb_raster_extents_t ext;
|
||
hb_raster_image_get_extents (img, &ext);
|
||
g_assert_cmpint (ext.x_origin, ==, 2);
|
||
g_assert_cmpint (ext.y_origin, ==, 2);
|
||
g_assert_cmpuint (ext.width, ==, 28);
|
||
g_assert_cmpuint (ext.height, ==, 28);
|
||
|
||
/* Center: fully inside → full coverage */
|
||
g_assert_cmpint (pixel_at (img, 16, 16), ==, 255);
|
||
|
||
/* Well outside the box → zero */
|
||
g_assert_cmpint (pixel_at (img, 0, 0), ==, 0); /* out of image */
|
||
g_assert_cmpint (pixel_at (img, 31, 31), ==, 0); /* out of image */
|
||
|
||
/* Left-most and right-most pixel columns: still fully inside
|
||
because integer-coordinate box edges align with pixel boundaries */
|
||
g_assert_cmpint (pixel_at (img, 2, 16), ==, 255);
|
||
g_assert_cmpint (pixel_at (img, 29, 16), ==, 255);
|
||
|
||
/* One pixel beyond the box right edge: out of image */
|
||
g_assert_cmpint (pixel_at (img, 30, 16), ==, 0);
|
||
|
||
hb_raster_image_destroy (img);
|
||
|
||
/* render() with no accumulated edges → empty (0×0) image */
|
||
img = hb_raster_draw_render (rdr);
|
||
g_assert_nonnull (img);
|
||
hb_raster_image_get_extents (img, &ext);
|
||
g_assert_cmpuint (ext.width, ==, 0);
|
||
g_assert_cmpuint (ext.height, ==, 0);
|
||
hb_raster_image_destroy (img);
|
||
|
||
hb_raster_draw_destroy (rdr);
|
||
}
|
||
|
||
|
||
/* ── Test 2: multi-glyph accumulation ────────────────────────────── */
|
||
|
||
static void
|
||
test_accumulate (void)
|
||
{
|
||
hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();
|
||
|
||
/* Two non-overlapping boxes accumulated before a single render() */
|
||
draw_rect (rdr, 0.f, 0.f, 10.f, 10.f);
|
||
draw_rect (rdr, 20.f, 0.f, 30.f, 10.f);
|
||
|
||
hb_raster_image_t *img = hb_raster_draw_render (rdr);
|
||
g_assert_nonnull (img);
|
||
|
||
/* Both interiors fully covered */
|
||
g_assert_cmpint (pixel_at (img, 5, 5), ==, 255);
|
||
g_assert_cmpint (pixel_at (img, 25, 5), ==, 255);
|
||
|
||
/* Gap between the two boxes is empty */
|
||
g_assert_cmpint (pixel_at (img, 15, 5), ==, 0);
|
||
|
||
hb_raster_image_destroy (img);
|
||
hb_raster_draw_destroy (rdr);
|
||
}
|
||
|
||
|
||
/* ── Test 3: sub-pixel edge coverage ─────────────────────────────── */
|
||
|
||
static void
|
||
test_subpixel_edge (void)
|
||
{
|
||
hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();
|
||
|
||
/* Box with a half-pixel offset so the leftmost pixel column is split */
|
||
draw_rect (rdr, 2.5f, 2.f, 30.f, 30.f);
|
||
|
||
hb_raster_image_t *img = hb_raster_draw_render (rdr);
|
||
g_assert_nonnull (img);
|
||
|
||
/* The pixel at x=2 straddles the left edge → partial coverage */
|
||
int v = pixel_at (img, 2, 16);
|
||
g_assert_cmpint (v, >, 0);
|
||
g_assert_cmpint (v, <, 255);
|
||
|
||
/* The pixel at x=3 is fully inside */
|
||
g_assert_cmpint (pixel_at (img, 3, 16), ==, 255);
|
||
|
||
hb_raster_image_destroy (img);
|
||
hb_raster_draw_destroy (rdr);
|
||
}
|
||
|
||
|
||
/* ── Test 4: transform ───────────────────────────────────────────── */
|
||
|
||
static void
|
||
test_transform (void)
|
||
{
|
||
hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();
|
||
|
||
/* Scale by 2 — same unit-square box maps to (0,0)→(20,20) */
|
||
hb_raster_draw_set_transform (rdr, 2.f, 0.f, 0.f, 2.f, 0.f, 0.f);
|
||
draw_rect (rdr, 0.f, 0.f, 10.f, 10.f);
|
||
|
||
hb_raster_image_t *img = hb_raster_draw_render (rdr);
|
||
g_assert_nonnull (img);
|
||
|
||
hb_raster_extents_t ext;
|
||
hb_raster_image_get_extents (img, &ext);
|
||
g_assert_cmpuint (ext.width, ==, 20);
|
||
g_assert_cmpuint (ext.height, ==, 20);
|
||
g_assert_cmpint (pixel_at (img, 10, 10), ==, 255); /* center of scaled box */
|
||
|
||
hb_raster_image_destroy (img);
|
||
hb_raster_draw_destroy (rdr);
|
||
}
|
||
|
||
/* ── Test 5: transformed glyph extents helper ───────────────────── */
|
||
|
||
static void
|
||
test_set_glyph_extents_with_transform (void)
|
||
{
|
||
hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();
|
||
|
||
hb_raster_draw_set_transform (rdr, 2.f, 0.f, 0.f, 3.f, 5.f, 7.f);
|
||
|
||
hb_glyph_extents_t gext = {
|
||
1, /* x_bearing */
|
||
4, /* y_bearing */
|
||
10, /* width */
|
||
-6 /* height */
|
||
};
|
||
|
||
g_assert_true (hb_raster_draw_set_glyph_extents (rdr, &gext));
|
||
|
||
draw_rect (rdr, (float) gext.x_bearing,
|
||
(float) gext.y_bearing + gext.height,
|
||
(float) gext.x_bearing + gext.width,
|
||
(float) gext.y_bearing);
|
||
|
||
hb_raster_image_t *img = hb_raster_draw_render (rdr);
|
||
g_assert_nonnull (img);
|
||
|
||
hb_raster_extents_t ext;
|
||
hb_raster_image_get_extents (img, &ext);
|
||
g_assert_cmpint (ext.x_origin, ==, 7); /* 2*1 + 5 */
|
||
g_assert_cmpint (ext.y_origin, ==, 1); /* 3*(-2) + 7 */
|
||
g_assert_cmpuint (ext.width, ==, 20); /* 2*10 */
|
||
g_assert_cmpuint (ext.height, ==, 18); /* 3*6 */
|
||
|
||
hb_raster_image_destroy (img);
|
||
hb_raster_draw_destroy (rdr);
|
||
}
|
||
|
||
/* ── main ────────────────────────────────────────────────────────── */
|
||
|
||
int
|
||
main (int argc, char **argv)
|
||
{
|
||
hb_test_init (&argc, &argv);
|
||
|
||
hb_test_add (test_rectangle);
|
||
hb_test_add (test_accumulate);
|
||
hb_test_add (test_subpixel_edge);
|
||
hb_test_add (test_transform);
|
||
hb_test_add (test_set_glyph_extents_with_transform);
|
||
|
||
return hb_test_run ();
|
||
}
|