try changing dpi instead of font size

This commit is contained in:
John Cupitt 2017-09-18 23:01:38 +01:00
parent a578fd5d87
commit 3cd0d5f161
2 changed files with 161 additions and 339 deletions

View File

@ -16,7 +16,7 @@
* - add @spacing
* 29/5/17
* - don't set "font" if unset, it breaks caching
* 16/7/17
* 16/7/17 gargsms
* - implement auto fitting of text inside bounds
*/
@ -47,6 +47,10 @@
*/
/*
*/
#define DEBUG
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
@ -84,15 +88,6 @@ typedef struct _VipsText {
typedef VipsCreateClass VipsTextClass;
typedef struct _FontSizeList FontSizeList;
struct _FontSizeList {
int deviation;
int size;
long area;
FontSizeList *next;
};
G_DEFINE_TYPE( VipsText, vips_text, VIPS_TYPE_CREATE );
/* Just have one of these and reuse it.
@ -107,12 +102,6 @@ static PangoFontMap *vips_text_fontmap = NULL;
*/
static GMutex *vips_text_lock = NULL;
/* The maximum deviation to tolerate for autofitting the text
*
* TODO: Should the user be able to change this?
*/
static int const MAX_TOLERANCE = 10;
static void
vips_text_dispose( GObject *gobject )
{
@ -170,133 +159,108 @@ text_layout_new( PangoContext *context,
}
static int
digits_in_num( int f )
text_get_extents( VipsText *text, VipsRect *extents )
{
int digits = 0;
if( f == 0 )
return 1;
while( f ) {
f /= 10;
digits++;
}
return digits;
PangoRectangle logical_rect;
pango_ft2_font_map_set_resolution(
PANGO_FT2_FONT_MAP( vips_text_fontmap ), text->dpi, text->dpi );
text->context = pango_font_map_create_context(
PANGO_FONT_MAP( vips_text_fontmap ) );
if( !(text->layout = text_layout_new( text->context,
text->text, text->font,
text->width, text->spacing, text->align )) )
return( -1 );
pango_layout_get_extents( text->layout, NULL, &logical_rect );
extents->left = PANGO_PIXELS( logical_rect.x );
extents->top = PANGO_PIXELS( logical_rect.y );
extents->width = PANGO_PIXELS( logical_rect.width );
extents->height = PANGO_PIXELS( logical_rect.height );
#ifdef DEBUG
printf( "text_get_extents: dpi = %d, "
"left = %d, top = %d, width = %d, height = %d\n",
text->dpi,
extents->left, extents->top, extents->width, extents->height );
#endif /*DEBUG*/
return( 0 );
}
/* Adjust text->dpi to try to fit to the bounding box.
*/
static int
determine_deviation( int width, int height, PangoRectangle rect ) {
int rect_width = PANGO_PIXELS( rect.width );
int rect_height = PANGO_PIXELS( rect.height );
int dw = (int)( 100 * (double)abs( rect_width - width ) / width );
int dh = (int)( 100 * (double)abs( rect_height - height ) / height );
if( dw && dh ) {
return dw * dh;
}
return dw ? dw : dh;
}
static gboolean
search_flist( FontSizeList *flist, int size )
vips_text_autofit( VipsText *text )
{
FontSizeList *entry = flist;
while( entry->next != NULL ) {
if( entry->size == size )
return TRUE;
entry = entry->next;
}
return FALSE;
}
VipsRect target;
VipsRect previous_target;
VipsRect extents;
VipsRect previous_extents;
int previous_dpi;
int lower;
int upper;
static FontSizeList *
least_deviation_flist( FontSizeList *flist )
{
FontSizeList *entry = flist;
/* This works for all practical purposes
/* First, repeatedly double or halve dpi until we pass the correct
* value. This will give us a lower and upper bound.
*/
long smallest = 1999999999;
FontSizeList *least;
while( entry->next != NULL ) {
if( entry->deviation < smallest ) {
smallest = entry->deviation;
least = entry;
target.width = text->width;
target.height = text->height;
previous_dpi = -1;
for(;;) {
if( text_get_extents( text, &extents ) )
return( -1 );
target.left = extents.left;
target.top = extents.top;
if( previous_dpi == -1 ) {
previous_dpi = text->dpi;
previous_extents = extents;
previous_target = target;
}
entry = entry->next;
}
return least;
}
static void
append_to_flist( FontSizeList *flist, FontSizeList *nflist )
{
FontSizeList *entry = flist;
while( entry->next != NULL ) {
entry = entry->next;
}
entry->next = nflist;
}
static PangoRectangle
fit_to_bounds( VipsText *text,
char *name, int size, PangoRectangle rect, FontSizeList *flist, gboolean coarse )
{
int buf_size = strlen( name ) + digits_in_num( size ) + 2;
int deviation;
char buf[ buf_size ];
long font_area = (long)PANGO_PIXELS( rect.width ) *
(long)PANGO_PIXELS( rect.height );
long allowed_area = (long)text->width * (long)text->height;
FontSizeList *nflist = (FontSizeList *) malloc( sizeof( FontSizeList ) );
if( coarse ) {
/* A factor of X increase in font size causes X^2 increase in the area
* occupied by the text
/* Too small last time, still too small.
*/
size = (int)((double)size * sqrt( (double)allowed_area / font_area ));
} else {
if( allowed_area > font_area ) {
size++;
} else {
size--;
}
if( vips_rect_includesrect( &target, &extents ) &&
vips_rect_includesrect( &previous_target,
&previous_extents ) )
text->dpi *= 2;
/* Too big last time, still too big.
*/
else if( vips_rect_includesrect( &extents, &target ) &&
vips_rect_includesrect( &previous_extents,
&previous_target ) )
text->dpi /= 2;
/* We now straddle the target!
*/
else
break;
}
snprintf( buf, buf_size, "%s %d", name, size );
lower = VIPS_MIN( text->dpi, previous_dpi );
upper = VIPS_MAX( text->dpi, previous_dpi );
text->layout = text_layout_new( text->context,
text->text, buf, text->width, text->spacing, text->align );
pango_layout_get_extents( text->layout, NULL, &rect );
deviation = determine_deviation( text->width, text->height, rect );
nflist->size = size;
nflist->deviation = deviation;
nflist->area = PANGO_PIXELS( rect.width ) * PANGO_PIXELS( rect.height );
nflist->next = NULL;
append_to_flist( flist, nflist );
/* If we have been through this font size before, find the one with the
* smallest deviation and then fit in small adjustments
/* Refine lower and upper until they are almost touching.
*/
if( search_flist( flist, size ) ) {
if( coarse ) {
return fit_to_bounds( text, name, size, rect,
least_deviation_flist( flist ), FALSE );
} else {
/* We cannot do better than this because we will
* cycle through sizes again
*/
return rect;
}
while( upper - lower > 2 ) {
text->dpi = (upper + lower) / 2;
if( text_get_extents( text, &extents ) )
return( -1 );
target.left = extents.left;
target.top = extents.top;
if( vips_rect_includesrect( &target, &extents ) )
lower = text->dpi;
else
upper = text->dpi;
}
if( deviation > MAX_TOLERANCE ) {
return fit_to_bounds( text, name, size, rect, flist, coarse );
} else {
return rect;
}
text->dpi = lower;
return( 0 );
}
static int
@ -305,18 +269,9 @@ vips_text_build( VipsObject *object )
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsCreate *create = VIPS_CREATE( object );
VipsText *text = (VipsText *) object;
FontSizeList *flist = (FontSizeList *) malloc( sizeof( FontSizeList ) );
PangoRectangle logical_rect;
int left;
int top;
int width;
int height;
VipsRect extents;
int y;
int deviation = 0;
int font_size = 0;
char *last;
gboolean is_font_size_provided = TRUE;
if( VIPS_OBJECT_CLASS( vips_text_parent_class )->build( object ) )
return( -1 );
@ -327,168 +282,35 @@ vips_text_build( VipsObject *object )
return( -1 );
}
char *font_name[ strlen( text->font ) + 1 ];
/* Extract font size from provided argument
*/
last = strrchr( text->font, ' ' );
/* Happens for a single word font names
*/
if( last != '\0' ) {
font_size = atol( last );
}
if( font_size ) {
strncat( font_name, text->font, last - text->font );
} else {
/* Font was more than 1 word. "Fira Code" would have last
* pointing to "Code", leading atol to output 0
* Fix font_name back to the original in this case
*/
strcpy( font_name, text->font );
font_size = text->height;
is_font_size_provided = FALSE;
}
g_mutex_lock( vips_text_lock );
if( !vips_text_fontmap )
vips_text_fontmap = pango_ft2_font_map_new();
pango_ft2_font_map_set_resolution(
PANGO_FT2_FONT_MAP( vips_text_fontmap ), text->dpi, text->dpi );
text->context = pango_font_map_create_context(
PANGO_FONT_MAP( vips_text_fontmap ) );
if( !(text->layout = text_layout_new( text->context,
text->text, text->font,
text->width, text->spacing, text->align )) ) {
g_mutex_unlock( vips_text_lock );
return( -1 );
}
pango_layout_get_extents( text->layout, NULL, &logical_rect );
if( !is_font_size_provided ) {
if( text->height && text->width ) {
deviation = determine_deviation( text->width, text->height, logical_rect );
}
if( deviation > MAX_TOLERANCE ) {
flist->size = font_size;
flist->deviation = deviation;
flist->area = PANGO_PIXELS( logical_rect.width ) * PANGO_PIXELS( logical_rect.height );
flist->next = NULL;
logical_rect = fit_to_bounds( text, font_name, font_size, logical_rect, flist, TRUE );
}
pango_layout_get_extents( text->layout, NULL, &logical_rect );
}
#ifdef DEBUG
printf( "logical left = %d, top = %d, width = %d, height = %d\n",
PANGO_PIXELS( logical_rect.x ),
PANGO_PIXELS( logical_rect.y ),
PANGO_PIXELS( logical_rect.width ),
PANGO_PIXELS( logical_rect.height ) );
#endif /*DEBUG*/
left = PANGO_PIXELS( logical_rect.x );
top = PANGO_PIXELS( logical_rect.y );
width = PANGO_PIXELS( logical_rect.width );
height = PANGO_PIXELS( logical_rect.height );
/* Match the layout to fit the exact dimensions requested
* We also apply gravity here
/* If our caller set height and not dpi, we adjust dpi until
* we get a fit.
*/
if( text->width && text->height ) {
/* Since the layout is bigger than the requested dimensions, we
* scale the layout font description by the same scale
* This seems like the only way to resize the layout before it
* is rendered. We cannot reliably do resizing after rendering
* because we lose the lock, and we need to rely on vips_resize
*/
if( !is_font_size_provided && ( width > text->width || height > text->height ) ) {
double scale_w = (double)text->width / width;
double scale_h = (double)text->height / height;
double scale = scale_w > scale_h ? scale_h : scale_w;
PangoFontDescription *temp_fd = pango_font_description_copy(
pango_layout_get_font_description( text->layout ) );
int fz = pango_font_description_get_size( temp_fd );
pango_font_description_set_size( temp_fd, (int)(fz * scale) );
pango_layout_set_font_description( text->layout, temp_fd );
pango_font_description_free( temp_fd );
pango_layout_get_extents( text->layout, &logical_rect, NULL );
width = PANGO_PIXELS( logical_rect.width );
height = PANGO_PIXELS( logical_rect.height );
}
int padl = 0;
int padt = 0;
switch( text->gravity ) {
case VIPS_GRAVITY_CENTER:
padl = ( text->width - width ) / 2;
padt = ( text->height - height ) / 2;
break;
case VIPS_GRAVITY_NORTH:
padl = ( text->width - width ) / 2;
padt = 0;
break;
case VIPS_GRAVITY_EAST:
padl = text->width - width;
padt = ( text->height - height ) / 2;
break;
case VIPS_GRAVITY_SOUTH:
padl = ( text->width - width ) / 2;
padt = text->height - height;
break;
case VIPS_GRAVITY_WEST:
padl = 0;
padt = ( text->height - height ) / 2;
break;
case VIPS_GRAVITY_NORTH_EAST:
padl = text->width - width;
padt = 0;
break;
case VIPS_GRAVITY_SOUTH_EAST:
padl = text->width - width;
padt = text->height - height;
break;
case VIPS_GRAVITY_SOUTH_WEST:
padl = 0;
padt = text->height - height;
break;
case VIPS_GRAVITY_NORTH_WEST:
padl = 0;
padt = 0;
break;
default:
break;
}
left = left - ( width > text->width ? 0 : padl ) + PANGO_PIXELS( logical_rect.x );
top = top - ( height > text->height ? 0 : padt ) + PANGO_PIXELS( logical_rect.y );
width = text->width > width ? text->width : width;
height = text->height > height ? text->height : height;
if( vips_object_argument_isset( object, "height" ) &&
!vips_object_argument_isset( object, "dpi" ) ) {
if( vips_text_autofit( text ) )
return( -1 );
}
if( text_get_extents( text, &extents ) )
return( -1 );
/* Can happen for "", for example.
*/
if( width == 0 || height == 0 ) {
if( extents.width == 0 ||
extents.height == 0 ) {
vips_error( class->nickname, "%s", _( "no text to render" ) );
g_mutex_unlock( vips_text_lock );
return( -1 );
}
text->bitmap.width = width;
text->bitmap.width = extents.width;
text->bitmap.pitch = (text->bitmap.width + 3) & ~3;
text->bitmap.rows = height;
text->bitmap.rows = extents.height;
if( !(text->bitmap.buffer =
VIPS_ARRAY( NULL,
text->bitmap.pitch * text->bitmap.rows, VipsPel )) ) {
@ -502,7 +324,7 @@ vips_text_build( VipsObject *object )
if( pango_layout_get_width( text->layout ) != -1 )
pango_ft2_render_layout( &text->bitmap, text->layout,
-left, -top );
-extents.left, -extents.top );
else
pango_ft2_render_layout( &text->bitmap, text->layout, 0, 0 );

View File

@ -6,14 +6,53 @@
G_BEGIN_DECLS
/* enumerations from "../../../libvips/include/vips/resample.h" */
GType vips_kernel_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_KERNEL (vips_kernel_get_type())
GType vips_size_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_SIZE (vips_size_get_type())
/* enumerations from "../../../libvips/include/vips/arithmetic.h" */
GType vips_operation_math_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_MATH (vips_operation_math_get_type())
GType vips_operation_math2_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_MATH2 (vips_operation_math2_get_type())
GType vips_operation_round_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_ROUND (vips_operation_round_get_type())
GType vips_operation_relational_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_RELATIONAL (vips_operation_relational_get_type())
GType vips_operation_boolean_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_BOOLEAN (vips_operation_boolean_get_type())
GType vips_operation_complex_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_COMPLEX (vips_operation_complex_get_type())
GType vips_operation_complex2_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_COMPLEX2 (vips_operation_complex2_get_type())
GType vips_operation_complexget_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_COMPLEXGET (vips_operation_complexget_get_type())
/* enumerations from "../../../libvips/include/vips/basic.h" */
GType vips_precision_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_PRECISION (vips_precision_get_type())
/* enumerations from "../../../libvips/include/vips/colour.h" */
GType vips_intent_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_INTENT (vips_intent_get_type())
GType vips_pcs_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_PCS (vips_pcs_get_type())
/* enumerations from "../../../libvips/include/vips/conversion.h" */
GType vips_extend_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_EXTEND (vips_extend_get_type())
GType vips_direction_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_DIRECTION (vips_direction_get_type())
GType vips_align_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ALIGN (vips_align_get_type())
GType vips_angle_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ANGLE (vips_angle_get_type())
GType vips_angle45_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ANGLE45 (vips_angle45_get_type())
GType vips_interesting_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_INTERESTING (vips_interesting_get_type())
/* enumerations from "../../../libvips/include/vips/convolution.h" */
GType vips_combine_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_COMBINE (vips_combine_get_type())
/* enumerations from "../../../libvips/include/vips/create.h" */
GType vips_gravity_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_GRAVITY (vips_gravity_get_type())
/* enumerations from "../../../libvips/include/vips/draw.h" */
GType vips_combine_mode_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_COMBINE_MODE (vips_combine_mode_get_type())
/* enumerations from "../../../libvips/include/vips/foreign.h" */
GType vips_foreign_flags_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type())
@ -35,39 +74,6 @@ GType vips_foreign_dz_depth_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_DZ_DEPTH (vips_foreign_dz_depth_get_type())
GType vips_foreign_dz_container_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_DZ_CONTAINER (vips_foreign_dz_container_get_type())
/* enumerations from "../../../libvips/include/vips/arithmetic.h" */
GType vips_operation_math_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_MATH (vips_operation_math_get_type())
GType vips_operation_math2_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_MATH2 (vips_operation_math2_get_type())
GType vips_operation_round_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_ROUND (vips_operation_round_get_type())
GType vips_operation_relational_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_RELATIONAL (vips_operation_relational_get_type())
GType vips_operation_boolean_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_BOOLEAN (vips_operation_boolean_get_type())
GType vips_operation_complex_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_COMPLEX (vips_operation_complex_get_type())
GType vips_operation_complex2_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_COMPLEX2 (vips_operation_complex2_get_type())
GType vips_operation_complexget_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_COMPLEXGET (vips_operation_complexget_get_type())
/* enumerations from "../../../libvips/include/vips/conversion.h" */
GType vips_extend_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_EXTEND (vips_extend_get_type())
GType vips_direction_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_DIRECTION (vips_direction_get_type())
GType vips_align_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ALIGN (vips_align_get_type())
GType vips_angle_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ANGLE (vips_angle_get_type())
GType vips_angle45_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ANGLE45 (vips_angle45_get_type())
GType vips_interesting_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_INTERESTING (vips_interesting_get_type())
/* enumerations from "../../../libvips/include/vips/util.h" */
GType vips_token_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_TOKEN (vips_token_get_type())
/* enumerations from "../../../libvips/include/vips/image.h" */
GType vips_demand_style_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_DEMAND_STYLE (vips_demand_style_get_type())
@ -81,29 +87,23 @@ GType vips_coding_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_CODING (vips_coding_get_type())
GType vips_access_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ACCESS (vips_access_get_type())
/* enumerations from "../../../libvips/include/vips/colour.h" */
GType vips_intent_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_INTENT (vips_intent_get_type())
GType vips_pcs_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_PCS (vips_pcs_get_type())
/* enumerations from "../../../libvips/include/vips/operation.h" */
GType vips_operation_flags_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_FLAGS (vips_operation_flags_get_type())
/* enumerations from "../../../libvips/include/vips/convolution.h" */
GType vips_combine_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_COMBINE (vips_combine_get_type())
/* enumerations from "../../../libvips/include/vips/morphology.h" */
GType vips_operation_morphology_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_MORPHOLOGY (vips_operation_morphology_get_type())
/* enumerations from "../../../libvips/include/vips/draw.h" */
GType vips_combine_mode_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_COMBINE_MODE (vips_combine_mode_get_type())
/* enumerations from "../../../libvips/include/vips/basic.h" */
GType vips_precision_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_PRECISION (vips_precision_get_type())
/* enumerations from "../../../libvips/include/vips/object.h" */
GType vips_argument_flags_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_ARGUMENT_FLAGS (vips_argument_flags_get_type())
/* enumerations from "../../../libvips/include/vips/operation.h" */
GType vips_operation_flags_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_FLAGS (vips_operation_flags_get_type())
/* enumerations from "../../../libvips/include/vips/resample.h" */
GType vips_kernel_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_KERNEL (vips_kernel_get_type())
GType vips_size_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_SIZE (vips_size_get_type())
/* enumerations from "../../../libvips/include/vips/util.h" */
GType vips_token_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_TOKEN (vips_token_get_type())
G_END_DECLS
#endif /*VIPS_ENUM_TYPES_H*/